Skip to content

Commit

Permalink
Integrate enricher into spoc run
Browse files Browse the repository at this point in the history
Parts of the enricher are now being reused for `spoc run` to provide
additional information.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Mar 15, 2023
1 parent 22cafc0 commit 5d14ccf
Show file tree
Hide file tree
Showing 14 changed files with 813 additions and 29 deletions.
10 changes: 5 additions & 5 deletions cmd/spoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ func main() {
DefaultText: string(runner.TypeSeccomp),
},
&cli.StringFlag{
Name: runner.FlagProfile,
Aliases: []string{"p"},
Usage: "the profile to be used",
Required: true,
TakesFile: true,
Name: runner.FlagProfile,
Aliases: []string{"p"},
Usage: "the profile to be used",
DefaultText: runner.DefaultInputFile,
TakesFile: true,
},
},
},
Expand Down
25 changes: 25 additions & 0 deletions internal/pkg/cli/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cli

import (
"os"
"path/filepath"
)

// DefaultFile defines the default input and output location for profiles.
var DefaultFile = filepath.Join(os.TempDir(), "profile.yaml")
7 changes: 2 additions & 5 deletions internal/pkg/cli/recorder/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ limitations under the License.

package recorder

import (
"os"
"path/filepath"
)
import "sigs.k8s.io/security-profiles-operator/internal/pkg/cli"

const (
// FlagOutputFile is the flag for defining the output file location.
Expand Down Expand Up @@ -52,7 +49,7 @@ const (

var (
// DefaultOutputFile defines the default output location for the recorder.
DefaultOutputFile = filepath.Join(os.TempDir(), "profile.yaml")
DefaultOutputFile = cli.DefaultFile

// DefaultBaseSyscalls are the syscalls included in every seccomp profile
// to ensure compatibility with OCI runtimes like runc and crun.
Expand Down
36 changes: 32 additions & 4 deletions internal/pkg/cli/recorder/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ limitations under the License.
package recorder

import (
"fmt"
"log"
"strings"

"github.com/go-logr/logr"
)
Expand All @@ -39,14 +41,40 @@ func (*LogSink) Enabled(level int) bool {
// The level argument is provided for optional logging. This method will
// only be called when Enabled(level) is true. See Logger.Info for more
// details.
func (*LogSink) Info(level int, msg string, keysAndValues ...interface{}) {
log.Print(msg)
func (l *LogSink) Info(level int, msg string, keysAndValues ...interface{}) {
l.Print(msg, nil, keysAndValues...)
}

// Error logs an error, with the given message and key/value pairs as
// context. See Logger.Error for more details.
func (*LogSink) Error(err error, msg string, keysAndValues ...interface{}) {
log.Print(msg)
func (l *LogSink) Error(err error, msg string, keysAndValues ...interface{}) {
l.Print(msg, err, keysAndValues...)
}

func (*LogSink) Print(msg string, err error, keysAndValues ...interface{}) {
builder := strings.Builder{}
builder.WriteString(msg)

if err != nil {
builder.WriteString(fmt.Sprintf(", err: %v", err))
}

for i, kv := range keysAndValues {
if i == 0 {
builder.WriteString(" (")
}
builder.WriteString(fmt.Sprintf("%v", kv))
//nolint:gocritic // this is intentionally an else-if-chain
if i%2 == 0 {
builder.WriteRune('=')
} else if i == len(keysAndValues)-1 {
builder.WriteRune(')')
} else {
builder.WriteString(", ")
}
}

log.Print(builder.String())
}

// WithValues returns a new LogSink with additional key/value pairs. See
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/cli/recorder/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (r *Recorder) processData(mntns uint32) error {
if currentMntns != mntns {
continue
}
log.Print("Found process mntns in bpf map")
log.Printf("Found process mntns %d in bpf map", mntns)

syscallsValue, err := r.SyscallsGetValue(r.bpfRecorder, currentMntns)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions internal/pkg/cli/runner/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ limitations under the License.

package runner

import "sigs.k8s.io/security-profiles-operator/internal/pkg/cli"

// DefaultInputFile defines the default input location for the runner.
var DefaultInputFile = cli.DefaultFile

const (
// FlagType is the flag for defining the profile type.
FlagType string = "type"
Expand Down
34 changes: 34 additions & 0 deletions internal/pkg/cli/runner/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ import (
"encoding/json"
"os"

"github.com/nxadm/tail"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/seccomp"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runtime-spec/specs-go"
libseccomp "github.com/seccomp/libseccomp-golang"
"sigs.k8s.io/yaml"

"sigs.k8s.io/security-profiles-operator/internal/pkg/cli/command"
"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/enricher"
"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/enricher/types"
)

type defaultImpl struct{}
Expand All @@ -45,6 +49,12 @@ type impl interface {
InitSeccomp(*configs.Seccomp) (int, error)
CommandRun(*command.Command) (uint32, error)
CommandWait(*command.Command) error
TailFile(string, tail.Config) (*tail.Tail, error)
Lines(*tail.Tail) chan *tail.Line
IsAuditLine(string) bool
ExtractAuditLine(string) (*types.AuditLine, error)
GetName(libseccomp.ScmpSyscall) (string, error)
PidLoad() uint32
}

func (*defaultImpl) ReadFile(name string) ([]byte, error) {
Expand Down Expand Up @@ -78,3 +88,27 @@ func (*defaultImpl) CommandRun(cmd *command.Command) (uint32, error) {
func (*defaultImpl) CommandWait(cmd *command.Command) error {
return cmd.Wait()
}

func (*defaultImpl) TailFile(filename string, config tail.Config) (*tail.Tail, error) {
return tail.TailFile(filename, config)
}

func (*defaultImpl) Lines(tailFile *tail.Tail) chan *tail.Line {
return tailFile.Lines
}

func (*defaultImpl) IsAuditLine(line string) bool {
return enricher.IsAuditLine(line)
}

func (*defaultImpl) ExtractAuditLine(line string) (*types.AuditLine, error) {
return enricher.ExtractAuditLine(line)
}

func (*defaultImpl) GetName(s libseccomp.ScmpSyscall) (string, error) {
return s.GetName()
}

func (*defaultImpl) PidLoad() uint32 {
return pid.Load()
}
10 changes: 9 additions & 1 deletion internal/pkg/cli/runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package runner

import (
"errors"
"fmt"

"github.com/urfave/cli/v2"
Expand All @@ -36,13 +37,20 @@ func Default() *Options {
return &Options{
commandOptions: command.Default(),
typ: TypeSeccomp,
profile: DefaultInputFile,
}
}

// FromContext can be used to create Options from an CLI context.
func FromContext(ctx *cli.Context) (*Options, error) {
options := Default()
options.profile = ctx.String(FlagProfile)

if ctx.IsSet(FlagProfile) {
options.profile = ctx.String(FlagProfile)
}
if options.profile == "" {
return nil, errors.New("no profile provided")
}

if ctx.IsSet(FlagType) {
options.typ = Type(ctx.String(FlagType))
Expand Down
95 changes: 94 additions & 1 deletion internal/pkg/cli/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ package runner

import (
"fmt"
"io"
"log"
"path/filepath"
"sync/atomic"

"github.com/nxadm/tail"
"github.com/opencontainers/runtime-spec/specs-go"
libseccomp "github.com/seccomp/libseccomp-golang"

seccompprofileapi "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1beta1"
"sigs.k8s.io/security-profiles-operator/internal/pkg/cli/command"
"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/enricher"
"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/enricher/types"
)

// Runner is the main structure of this package.
Expand All @@ -44,6 +50,9 @@ func New(options *Options) *Runner {
}
}

// pid is the process ID used for enricher filtering.
var pid atomic.Uint32

// Run the Runner.
func (r *Runner) Run() error {
log.Printf("Reading file %s", r.options.profile)
Expand All @@ -70,6 +79,8 @@ func (r *Runner) Run() error {
return fmt.Errorf("unmarshal JSON profile: %w", err)
}

go r.startEnricher()

log.Print("Setting up seccomp")
libConfig, err := r.SetupSeccomp(runtimeSpecConfig)
if err != nil {
Expand All @@ -82,13 +93,95 @@ func (r *Runner) Run() error {
}

cmd := command.New(r.options.commandOptions)
if _, err := r.CommandRun(cmd); err != nil {
newPid, err := r.CommandRun(cmd)
if err != nil {
return fmt.Errorf("run command: %w", err)
}
pid.Store(newPid)

if err := r.CommandWait(cmd); err != nil {
return fmt.Errorf("wait for command: %w", err)
}

return nil
}

func (r *Runner) startEnricher() {
log.Print("Starting audit log enricher")
filePath := enricher.LogFilePath()

tailFile, err := r.TailFile(
filePath,
tail.Config{
ReOpen: true,
Follow: true,
Location: &tail.SeekInfo{
Offset: 0,
Whence: io.SeekEnd,
},
},
)
if err != nil {
log.Printf("Unable to tail file: %v", err)
return
}

log.Printf("Enricher reading from file %s", filePath)
for l := range r.Lines(tailFile) {
if l.Err != nil {
log.Printf("Enricher failed to tail: %v", l.Err)
break
}

line := l.Text
if !r.IsAuditLine(line) {
continue
}

auditLine, err := r.ExtractAuditLine(line)
if err != nil {
log.Printf("Unable to extract audit line: %v", err)
continue
}

currentPid := r.PidLoad()
if currentPid != 0 && uint32(auditLine.ProcessID) == currentPid {
r.printAuditLine(auditLine)
}
}
}

func (r *Runner) printAuditLine(line *types.AuditLine) {
switch line.AuditType {
case types.AuditTypeSelinux:
r.printSelinuxLine(line)
case types.AuditTypeSeccomp:
r.printSeccompLine(line)
case types.AuditTypeApparmor:
r.printApparmorLine(line)
}
}

func (r *Runner) printSelinuxLine(line *types.AuditLine) {
log.Printf(
"SELinux: perm: %s, scontext: %s, tcontext: %s, tclass: %s",
line.Perm, line.Scontext, line.Tcontext, line.Tclass,
)
}

func (r *Runner) printSeccompLine(line *types.AuditLine) {
syscallName, err := r.GetName(libseccomp.ScmpSyscall(line.SystemCallID))
if err != nil {
log.Printf("Unable to get syscall name for id %d: %v", line.SystemCallID, err)
return
}

log.Printf("Seccomp: %s (%d)", syscallName, line.SystemCallID)
}

func (r *Runner) printApparmorLine(line *types.AuditLine) {
log.Printf(
"AppArmor: %s, operation: %s, profile: %s, name: %s, extra: %s",
line.Apparmor, line.Operation, line.Profile, line.Name, line.ExtraInfo,
)
}
Loading

0 comments on commit 5d14ccf

Please sign in to comment.