Skip to content

Commit

Permalink
bpf recorder: separate apparmor and seccomp parts, make it possible t…
Browse files Browse the repository at this point in the history
…o record both simultaneously
  • Loading branch information
mhils committed May 14, 2024
1 parent 3f0979b commit e0f88cc
Show file tree
Hide file tree
Showing 18 changed files with 11,232 additions and 13,583 deletions.
2 changes: 1 addition & 1 deletion cmd/security-profiles-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ func runDaemon(ctx *cli.Context, info *version.Info) error {
func runBPFRecorder(_ *cli.Context, info *version.Info) error {
const component = "bpf-recorder"
printInfo(component, info)
return bpfrecorder.New(ctrl.Log.WithName(component)).Run()
return bpfrecorder.New("", ctrl.Log.WithName(component), true, false).Run()
}

func runLogEnricher(_ *cli.Context, info *version.Info) error {
Expand Down
3 changes: 2 additions & 1 deletion cmd/spoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ func main() {
Aliases: []string{"t"},
Usage: "the record type",
DefaultText: fmt.Sprintf(
"%s [alternative: %s %s %s]",
"%s [alternative: %s %s %s %s]",
recorder.TypeSeccomp,
recorder.TypeRawSeccomp,
recorder.TypeApparmor,
recorder.TypeRawAppArmor,
recorder.TypeAll,
),
},
&cli.StringSliceFlag{
Expand Down
3 changes: 3 additions & 0 deletions internal/pkg/cli/recorder/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const (
type Type string

const (
// TypeApp is the type indicating that we should record all CRD profiles.
TypeAll Type = "all"

// TypeSeccomp is the type indicating that we should record a seccomp CRD
// profile.
TypeSeccomp Type = "seccomp"
Expand Down
22 changes: 6 additions & 16 deletions internal/pkg/cli/recorder/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ limitations under the License.
package recorder

import (
"context"
"encoding/json"
"io"
"os"
"os/signal"
"time"
"unsafe"

"github.com/aquasecurity/libbpfgo"
Expand All @@ -46,17 +46,15 @@ type impl interface {
UnloadBpfRecorder(*bpfrecorder.BpfRecorder)
CommandRun(*command.Command) (uint32, error)
CommandWait(*command.Command) error
WaitForPidExit(*bpfrecorder.BpfRecorder, uint32, time.Duration) error
WaitForPidExit(*bpfrecorder.BpfRecorder, context.Context, uint32) error
FindProcMountNamespace(*bpfrecorder.BpfRecorder, uint32) (uint32, error)
SyscallsIterator(*bpfrecorder.BpfRecorder) *libbpfgo.BPFMapIterator
IteratorNext(*libbpfgo.BPFMapIterator) bool
IteratorKey(*libbpfgo.BPFMapIterator) []byte
SyscallsGetValue(*bpfrecorder.BpfRecorder, uint32) ([]byte, error)
GetName(libseccomp.ScmpSyscall) (string, error)
MarshalIndent(any, string, string) ([]byte, error)
WriteFile(string, []byte, os.FileMode) error
Create(string) (*os.File, error)
CloseFile(*os.File)
Create(string) (io.WriteCloser, error)
PrintObj(printers.YAMLPrinter, runtime.Object, io.Writer) error
GoArchToSeccompArch(string) (seccomp.Arch, error)
Notify(chan<- os.Signal, ...os.Signal)
Expand All @@ -82,8 +80,8 @@ func (*defaultImpl) CommandWait(cmd *command.Command) error {
return cmd.Wait()
}

func (*defaultImpl) WaitForPidExit(b *bpfrecorder.BpfRecorder, pid uint32, timeout time.Duration) error {
return b.WaitForPidExit(pid, timeout)
func (*defaultImpl) WaitForPidExit(b *bpfrecorder.BpfRecorder, ctx context.Context, pid uint32) error {
return b.WaitForPidExit(ctx, pid)
}

func (*defaultImpl) SyscallsIterator(b *bpfrecorder.BpfRecorder) *libbpfgo.BPFMapIterator {
Expand All @@ -110,18 +108,10 @@ func (*defaultImpl) MarshalIndent(v any, prefix, indent string) ([]byte, error)
return json.MarshalIndent(v, prefix, indent)
}

func (*defaultImpl) WriteFile(name string, data []byte, perm os.FileMode) error {
return os.WriteFile(name, data, perm)
}

func (*defaultImpl) Create(name string) (*os.File, error) {
func (*defaultImpl) Create(name string) (io.WriteCloser, error) {
return os.Create(name)
}

func (*defaultImpl) CloseFile(file *os.File) {
file.Close()
}

func (*defaultImpl) PrintObj(p printers.YAMLPrinter, obj runtime.Object, w io.Writer) error {
return p.PrintObj(obj, w)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/cli/recorder/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func FromContext(ctx *cli.Context) (*Options, error) {
options.typ = Type(ctx.String(FlagType))
}
if options.typ != TypeSeccomp && options.typ != TypeRawSeccomp &&
options.typ != TypeApparmor && options.typ != TypeRawAppArmor {
options.typ != TypeApparmor && options.typ != TypeRawAppArmor && options.typ != TypeAll {
return nil, fmt.Errorf("unsupported %s: %s", FlagType, options.typ)
}

Expand Down
132 changes: 73 additions & 59 deletions internal/pkg/cli/recorder/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ limitations under the License.
package recorder

import (
"context"
"encoding/binary"
"fmt"
"io"
"log"
"os"
"path/filepath"
Expand Down Expand Up @@ -67,12 +69,20 @@ func New(options *Options) *Recorder {

// Run the Recorder.
func (r *Recorder) Run() error {
if (r.options.typ == TypeApparmor) || (r.options.typ == TypeRawAppArmor) {
r.bpfRecorder = bpfrecorder.NewAppArmor(logr.New(&cli.LogSink{}))
} else {
r.bpfRecorder = bpfrecorder.NewSeccomp(logr.New(&cli.LogSink{}))
}
r.bpfRecorder.FilterProgramName(r.options.commandOptions.Command())
recordAppArmor := ((r.options.typ == TypeApparmor) ||
(r.options.typ == TypeRawAppArmor) ||
(r.options.typ == TypeAll))
recordSeccomp := ((r.options.typ == TypeSeccomp) ||
(r.options.typ == TypeRawSeccomp) ||
(r.options.typ == TypeAll))

r.bpfRecorder = bpfrecorder.New(
r.options.commandOptions.Command(),
logr.New(&cli.LogSink{}),
recordSeccomp,
recordAppArmor,
)

if err := r.LoadBpfRecorder(r.bpfRecorder); err != nil {
return fmt.Errorf("load: %w", err)
}
Expand Down Expand Up @@ -102,28 +112,49 @@ func (r *Recorder) Run() error {
log.Printf("Command did not exit successfully: %v", err)
}

if (r.options.typ == TypeApparmor) || (r.options.typ == TypeRawAppArmor) {
log.Println("Waiting for events processor to catch up...")
if err := r.WaitForPidExit(r.bpfRecorder, pid, waitForPidExitTimeout); err != nil {
log.Printf("Did not register exit signal for pid %d: %v", pid, err)
}
log.Println("Waiting for events processor to catch up...")
ctx, cancel := context.WithTimeout(context.Background(), waitForPidExitTimeout)
defer cancel()

if err := r.WaitForPidExit(r.bpfRecorder, ctx, pid); err != nil {
log.Printf("Did not register exit signal for pid %d: %v", pid, err)
}
}

var err error
if (r.options.typ == TypeApparmor) || (r.options.typ == TypeRawAppArmor) {
err = r.processAppArmorData()
} else {
err = r.processData(mntns)
}
file, err := r.Create(r.outFile())
if err != nil {
return fmt.Errorf("build profile: %w", err)
return fmt.Errorf("create file: %w", err)
}
defer file.Close()

if recordAppArmor {
if err := r.processAppArmor(file); err != nil {
return fmt.Errorf("build apparmor profile: %w", err)
}
}
if recordSeccomp {
if err := r.processSeccomp(file, mntns); err != nil {
return fmt.Errorf("build seccomp profile: %w", err)
}
}

return nil
}

func (r *Recorder) processData(mntns uint32) error {
func (r *Recorder) outFile() string {
outFile := r.options.outputFile
if outFile == DefaultOutputFile {
if r.options.typ == TypeRawAppArmor {
outFile = strings.TrimSuffix(outFile, ".yaml") + ".json"
}
if r.options.typ == TypeRawSeccomp {
outFile = strings.TrimSuffix(outFile, ".yaml") + ".apparmor"
}
}
return outFile
}

func (r *Recorder) processSeccomp(writer io.Writer, mntns uint32) error {
log.Printf("Processing recorded data")

// A set of all observed syscalls.
Expand Down Expand Up @@ -168,15 +199,15 @@ func (r *Recorder) processData(mntns uint32) error {
}

log.Printf("Got syscalls: %s", strings.Join(syscalls, ", "))
if err := r.buildProfile(syscalls); err != nil {
if err := r.buildProfile(writer, syscalls); err != nil {
return fmt.Errorf("build profile: %w", err)
}

return nil
}

func (r *Recorder) generateAppArmorProfile() apparmorprofileapi.AppArmorAbstract {
processed := r.bpfRecorder.GetAppArmorProcessed()
processed := r.bpfRecorder.AppArmor.GetAppArmorProcessed()

abstract := apparmorprofileapi.AppArmorAbstract{}
enabled := true
Expand Down Expand Up @@ -248,22 +279,22 @@ func (r *Recorder) generateAppArmorProfile() apparmorprofileapi.AppArmorAbstract
return abstract
}

func (r *Recorder) processAppArmorData() error {
func (r *Recorder) processAppArmor(writer io.Writer) error {
abstract := r.generateAppArmorProfile()
spec := apparmorprofileapi.AppArmorProfileSpec{
Abstract: abstract,
}
defer func() {
log.Printf("Wrote apparmor profile to: %s", r.options.outputFile)
log.Printf("Wrote apparmor profile to: %s", r.outFile())
}()

if r.options.typ == TypeRawAppArmor {
return r.buildAppArmorProfileRaw(&spec)
return r.buildAppArmorProfileRaw(writer, &spec)
}
return r.buildAppArmorProfileCRD(&spec)
return r.buildAppArmorProfileCRD(writer, &spec)
}

func (r *Recorder) buildProfile(names []string) error {
func (r *Recorder) buildProfile(writer io.Writer, names []string) error {
arch, err := r.goArchToSeccompArch(runtime.GOARCH)
if err != nil {
return fmt.Errorf("get seccomp arch: %w", err)
Expand Down Expand Up @@ -291,35 +322,30 @@ func (r *Recorder) buildProfile(names []string) error {
}

defer func() {
log.Printf("Wrote seccomp profile to: %s", r.options.outputFile)
log.Printf("Wrote seccomp profile to: %s", r.outFile())
}()

if r.options.typ == TypeRawSeccomp {
return r.buildProfileRaw(&spec)
return r.buildProfileRaw(writer, &spec)
}

return r.buildProfileCRD(&spec)
return r.buildProfileCRD(writer, &spec)
}

func (r *Recorder) buildProfileRaw(spec *seccompprofileapi.SeccompProfileSpec) error {
if r.options.outputFile == DefaultOutputFile {
r.options.outputFile = strings.ReplaceAll(r.options.outputFile, ".yaml", ".json")
}

func (r *Recorder) buildProfileRaw(writer io.Writer, spec *seccompprofileapi.SeccompProfileSpec) error {
data, err := r.MarshalIndent(spec, "", " ")
if err != nil {
return fmt.Errorf("marshal JSON profile: %w", err)
}

const defaultMode os.FileMode = 0o644
if err := r.WriteFile(r.options.outputFile, data, defaultMode); err != nil {
if _, err := writer.Write(data); err != nil {
return fmt.Errorf("write JSON file: %w", err)
}

return nil
}

func (r *Recorder) buildProfileCRD(spec *seccompprofileapi.SeccompProfileSpec) error {
func (r *Recorder) buildProfileCRD(writer io.Writer, spec *seccompprofileapi.SeccompProfileSpec) error {
profile := &seccompprofileapi.SeccompProfile{
TypeMeta: metav1.TypeMeta{
Kind: "SeccompProfile",
Expand All @@ -331,21 +357,15 @@ func (r *Recorder) buildProfileCRD(spec *seccompprofileapi.SeccompProfileSpec) e
Spec: *spec,
}

file, err := r.Create(r.options.outputFile)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer r.CloseFile(file)

printer := printers.YAMLPrinter{}
if err := r.PrintObj(printer, profile, file); err != nil {
if err := r.PrintObj(printer, profile, writer); err != nil {
return fmt.Errorf("print YAML: %w", err)
}

return nil
}

func (r *Recorder) buildAppArmorProfileCRD(spec *apparmorprofileapi.AppArmorProfileSpec) error {
func (r *Recorder) buildAppArmorProfileCRD(writer io.Writer, spec *apparmorprofileapi.AppArmorProfileSpec) error {
profile := &apparmorprofileapi.AppArmorProfile{
TypeMeta: metav1.TypeMeta{
Kind: "AppArmorProfile",
Expand All @@ -357,25 +377,20 @@ func (r *Recorder) buildAppArmorProfileCRD(spec *apparmorprofileapi.AppArmorProf
Spec: *spec,
}

file, err := r.Create(r.options.outputFile)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer r.CloseFile(file)

printer := printers.YAMLPrinter{}
if err := r.PrintObj(printer, profile, file); err != nil {
if err := r.PrintObj(printer, profile, writer); err != nil {
return fmt.Errorf("print YAML: %w", err)
}

if r.options.typ == TypeAll {
if _, err := writer.Write([]byte("\n---\n")); err != nil {
return fmt.Errorf("write combined prifle: %w", err)
}
}
return nil
}

func (r *Recorder) buildAppArmorProfileRaw(spec *apparmorprofileapi.AppArmorProfileSpec) error {
if r.options.outputFile == DefaultOutputFile {
r.options.outputFile = strings.ReplaceAll(r.options.outputFile, ".yaml", ".apparmor")
}

func (r *Recorder) buildAppArmorProfileRaw(writer io.Writer, spec *apparmorprofileapi.AppArmorProfileSpec) error {
programName, err := filepath.Abs(r.options.commandOptions.Command())
if err != nil {
return fmt.Errorf("get program name: %w", err)
Expand All @@ -387,8 +402,7 @@ func (r *Recorder) buildAppArmorProfileRaw(spec *apparmorprofileapi.AppArmorProf
return fmt.Errorf("build raw apparmor profile: %w", err)
}

const defaultMode os.FileMode = 0o644
if err := r.WriteFile(r.options.outputFile, []byte(raw), defaultMode); err != nil {
if _, err := writer.Write([]byte(raw)); err != nil {
return fmt.Errorf("write AppArmor file: %w", err)
}

Expand Down
Loading

0 comments on commit e0f88cc

Please sign in to comment.