From 92641e7e2873f94e1e2102f89f25719f5e36f5c9 Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Sat, 25 May 2024 22:36:39 +0100 Subject: [PATCH] feat(aa): add initial profile validation structure. --- pkg/aa/all.go | 4 ++++ pkg/aa/apparmor.go | 13 +++++++++++++ pkg/aa/blocks.go | 4 ++++ pkg/aa/capability.go | 8 ++++++++ pkg/aa/change_profile.go | 7 +++++++ pkg/aa/dbus.go | 8 ++++++++ pkg/aa/file.go | 8 ++++++++ pkg/aa/io_uring.go | 12 +++++++++++- pkg/aa/mount.go | 25 +++++++++++++++++++++++++ pkg/aa/mqueue.go | 11 +++++++++++ pkg/aa/network.go | 17 +++++++++++++++++ pkg/aa/pivot_root.go | 4 ++++ pkg/aa/preamble.go | 20 ++++++++++++++++++++ pkg/aa/profile.go | 19 +++++++++++++++++++ pkg/aa/ptrace.go | 8 ++++++++ pkg/aa/rlimit.go | 7 +++++++ pkg/aa/rules.go | 22 ++++++++++++++++++++++ pkg/aa/signal.go | 11 +++++++++++ pkg/aa/unix.go | 12 +++++++++++- pkg/aa/userns.go | 4 ++++ 20 files changed, 222 insertions(+), 2 deletions(-) diff --git a/pkg/aa/all.go b/pkg/aa/all.go index 8c0527be5..608bfd9aa 100644 --- a/pkg/aa/all.go +++ b/pkg/aa/all.go @@ -12,6 +12,10 @@ type All struct { RuleBase } +func (r *All) Validate() error { + return nil +} + func (r *All) Less(other any) bool { return false } diff --git a/pkg/aa/apparmor.go b/pkg/aa/apparmor.go index 6ddcd7a4f..13f0b6b14 100644 --- a/pkg/aa/apparmor.go +++ b/pkg/aa/apparmor.go @@ -49,6 +49,19 @@ func (f *AppArmorProfileFile) String() string { return renderTemplate("apparmor", f) } +// Validate the profile file +func (f *AppArmorProfileFile) Validate() error { + if err := f.Preamble.Validate(); err != nil { + return err + } + for _, p := range f.Profiles { + if err := p.Validate(); err != nil { + return err + } + } + return nil +} + // GetDefaultProfile ensure a profile is always present in the profile file and // return it, as a default profile. func (f *AppArmorProfileFile) GetDefaultProfile() *Profile { diff --git a/pkg/aa/blocks.go b/pkg/aa/blocks.go index 544106db5..8b4f3ab59 100644 --- a/pkg/aa/blocks.go +++ b/pkg/aa/blocks.go @@ -16,6 +16,10 @@ type Hat struct { Rules Rules } +func (r *Hat) Validate() error { + return nil +} + func (p *Hat) Less(other any) bool { o, _ := other.(*Hat) return p.Name < o.Name diff --git a/pkg/aa/capability.go b/pkg/aa/capability.go index cedaef985..33a0a6a8e 100644 --- a/pkg/aa/capability.go +++ b/pkg/aa/capability.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "slices" ) @@ -39,6 +40,13 @@ func newCapabilityFromLog(log map[string]string) Rule { } } +func (r *Capability) Validate() error { + if err := validateValues(r.Kind(), "name", r.Names); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Capability) Less(other any) bool { o, _ := other.(*Capability) for i := 0; i < len(r.Names) && i < len(o.Names); i++ { diff --git a/pkg/aa/change_profile.go b/pkg/aa/change_profile.go index f0966bc5c..cc21322ac 100644 --- a/pkg/aa/change_profile.go +++ b/pkg/aa/change_profile.go @@ -30,6 +30,13 @@ func newChangeProfileFromLog(log map[string]string) Rule { } } +func (r *ChangeProfile) Validate() error { + if err := validateValues(r.Kind(), "mode", []string{r.ExecMode}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *ChangeProfile) Less(other any) bool { o, _ := other.(*ChangeProfile) if r.ExecMode != o.ExecMode { diff --git a/pkg/aa/dbus.go b/pkg/aa/dbus.go index 41863da99..ded2b3cb4 100644 --- a/pkg/aa/dbus.go +++ b/pkg/aa/dbus.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "slices" ) @@ -55,6 +56,13 @@ func newDbusFromLog(log map[string]string) Rule { } } +func (r *Dbus) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return validateValues(r.Kind(), "bus", []string{r.Bus}) +} + func (r *Dbus) Less(other any) bool { o, _ := other.(*Dbus) for i := 0; i < len(r.Access) && i < len(o.Access); i++ { diff --git a/pkg/aa/file.go b/pkg/aa/file.go index 12d8b96b0..18e2c5cbc 100644 --- a/pkg/aa/file.go +++ b/pkg/aa/file.go @@ -81,6 +81,10 @@ func newFileFromLog(log map[string]string) Rule { } } +func (r *File) Validate() error { + return nil +} + func (r *File) Less(other any) bool { o, _ := other.(*File) letterR := getLetterIn(fileAlphabet, r.Path) @@ -140,6 +144,10 @@ func newLinkFromLog(log map[string]string) Rule { } } +func (r *Link) Validate() error { + return nil +} + func (r *Link) Less(other any) bool { o, _ := other.(*Link) if r.Path != o.Path { diff --git a/pkg/aa/io_uring.go b/pkg/aa/io_uring.go index ce1a8ae67..13c1031b9 100644 --- a/pkg/aa/io_uring.go +++ b/pkg/aa/io_uring.go @@ -4,7 +4,10 @@ package aa -import "slices" +import ( + "fmt" + "slices" +) const tokIOURING = "io_uring" @@ -30,6 +33,13 @@ func newIOUringFromLog(log map[string]string) Rule { } } +func (r *IOUring) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *IOUring) Less(other any) bool { o, _ := other.(*IOUring) if len(r.Access) != len(o.Access) { diff --git a/pkg/aa/mount.go b/pkg/aa/mount.go index 88f00637a..4b177442c 100644 --- a/pkg/aa/mount.go +++ b/pkg/aa/mount.go @@ -42,6 +42,10 @@ func newMountConditionsFromLog(log map[string]string) MountConditions { return MountConditions{FsType: log["fstype"]} } +func (m MountConditions) Validate() error { + return validateValues(tokMOUNT, "flags", m.Options) +} + func (m MountConditions) Less(other MountConditions) bool { if m.FsType != other.FsType { return m.FsType < other.FsType @@ -71,6 +75,13 @@ func newMountFromLog(log map[string]string) Rule { } } +func (r *Mount) Validate() error { + if err := r.MountConditions.Validate(); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Mount) Less(other any) bool { o, _ := other.(*Mount) if r.Source != o.Source { @@ -120,6 +131,13 @@ func newUmountFromLog(log map[string]string) Rule { } } +func (r *Umount) Validate() error { + if err := r.MountConditions.Validate(); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Umount) Less(other any) bool { o, _ := other.(*Umount) if r.MountPoint != o.MountPoint { @@ -166,6 +184,13 @@ func newRemountFromLog(log map[string]string) Rule { } } +func (r *Remount) Validate() error { + if err := r.MountConditions.Validate(); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Remount) Less(other any) bool { o, _ := other.(*Remount) if r.MountPoint != o.MountPoint { diff --git a/pkg/aa/mqueue.go b/pkg/aa/mqueue.go index 52cf7c30d..7afb11792 100644 --- a/pkg/aa/mqueue.go +++ b/pkg/aa/mqueue.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "slices" "strings" ) @@ -47,6 +48,16 @@ func newMqueueFromLog(log map[string]string) Rule { } } +func (r *Mqueue) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + if err := validateValues(r.Kind(), "type", []string{r.Type}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Mqueue) Less(other any) bool { o, _ := other.(*Mqueue) if len(r.Access) != len(o.Access) { diff --git a/pkg/aa/network.go b/pkg/aa/network.go index 554b77ab4..edc781007 100644 --- a/pkg/aa/network.go +++ b/pkg/aa/network.go @@ -4,6 +4,10 @@ package aa +import ( + "fmt" +) + const tokNETWORK = "network" func init() { @@ -77,6 +81,19 @@ func newNetworkFromLog(log map[string]string) Rule { } } +func (r *Network) Validate() error { + if err := validateValues(r.Kind(), "domains", []string{r.Domain}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + if err := validateValues(r.Kind(), "type", []string{r.Type}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + if err := validateValues(r.Kind(), "protocol", []string{r.Protocol}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Network) Less(other any) bool { o, _ := other.(*Network) if r.Domain != o.Domain { diff --git a/pkg/aa/pivot_root.go b/pkg/aa/pivot_root.go index 3c421adf6..7c3f5ab8d 100644 --- a/pkg/aa/pivot_root.go +++ b/pkg/aa/pivot_root.go @@ -24,6 +24,10 @@ func newPivotRootFromLog(log map[string]string) Rule { } } +func (r *PivotRoot) Validate() error { + return nil +} + func (r *PivotRoot) Less(other any) bool { o, _ := other.(*PivotRoot) if r.OldRoot != o.OldRoot { diff --git a/pkg/aa/preamble.go b/pkg/aa/preamble.go index 539e719c4..8d28612b7 100644 --- a/pkg/aa/preamble.go +++ b/pkg/aa/preamble.go @@ -21,6 +21,10 @@ type Comment struct { RuleBase } +func (r *Comment) Validate() error { + return nil +} + func (r *Comment) Less(other any) bool { return false } @@ -51,6 +55,10 @@ type Abi struct { IsMagic bool } +func (r *Abi) Validate() error { + return nil +} + func (r *Abi) Less(other any) bool { o, _ := other.(*Abi) if r.Path != o.Path { @@ -82,6 +90,10 @@ type Alias struct { RewrittenPath string } +func (r *Alias) Validate() error { + return nil +} + func (r Alias) Less(other any) bool { o, _ := other.(*Alias) if r.Path != o.Path { @@ -114,6 +126,10 @@ type Include struct { IsMagic bool } +func (r *Include) Validate() error { + return nil +} + func (r *Include) Less(other any) bool { o, _ := other.(*Include) if r.Path == o.Path { @@ -149,6 +165,10 @@ type Variable struct { Define bool } +func (r *Variable) Validate() error { + return nil +} + func (r *Variable) Less(other any) bool { o, _ := other.(*Variable) if r.Name != o.Name { diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index fdcc912b0..ec646ea21 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "maps" "reflect" "slices" @@ -18,6 +19,17 @@ const ( tokPROFILE = "profile" ) +func init() { + requirements[tokPROFILE] = requirement{ + tokFLAGS: { + "enforce", "complain", "kill", "default_allow", "unconfined", + "prompt", "audit", "mediate_deleted", "attach_disconnected", + "attach_disconneced.path=", "chroot_relative", "debug", + "interruptible", "kill", "kill.signal=", + }, + } +} + // Profile represents a single AppArmor profile. type Profile struct { RuleBase @@ -33,6 +45,13 @@ type Header struct { Flags []string } +func (r *Profile) Validate() error { + if err := validateValues(r.Kind(), tokFLAGS, r.Flags); err != nil { + return fmt.Errorf("profile %s: %w", r.Name, err) + } + return r.Rules.Validate() +} + func (p *Profile) Less(other any) bool { o, _ := other.(*Profile) if p.Name != o.Name { diff --git a/pkg/aa/ptrace.go b/pkg/aa/ptrace.go index 1b920a26e..d73060edf 100644 --- a/pkg/aa/ptrace.go +++ b/pkg/aa/ptrace.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "slices" ) @@ -34,6 +35,13 @@ func newPtraceFromLog(log map[string]string) Rule { } } +func (r *Ptrace) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Ptrace) Less(other any) bool { o, _ := other.(*Ptrace) if len(r.Access) != len(o.Access) { diff --git a/pkg/aa/rlimit.go b/pkg/aa/rlimit.go index fc9ebf281..1f2c484f2 100644 --- a/pkg/aa/rlimit.go +++ b/pkg/aa/rlimit.go @@ -35,6 +35,13 @@ func newRlimitFromLog(log map[string]string) Rule { } } +func (r *Rlimit) Validate() error { + if err := validateValues(r.Kind(), "keys", []string{r.Key}); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Rlimit) Less(other any) bool { o, _ := other.(*Rlimit) if r.Key != o.Key { diff --git a/pkg/aa/rules.go b/pkg/aa/rules.go index e6ab8ec1f..0a971f5d3 100644 --- a/pkg/aa/rules.go +++ b/pkg/aa/rules.go @@ -28,6 +28,7 @@ const ( // Rule generic interface for all AppArmor rules type Rule interface { + Validate() error Less(other any) bool Equals(other any) bool String() string @@ -37,6 +38,15 @@ type Rule interface { type Rules []Rule +func (r Rules) Validate() error { + for _, rule := range r { + if err := rule.Validate(); err != nil { + return err + } + } + return nil +} + func (r Rules) String() string { return renderTemplate("rules", r) } @@ -82,6 +92,18 @@ func Must[T any](v T, err error) T { return v } +func validateValues(rule string, key string, values []string) error { + for _, v := range values { + if v == "" { + continue + } + if !slices.Contains(requirements[rule][key], v) { + return fmt.Errorf("invalid mode '%s'", v) + } + } + return nil +} + // Helper function to convert a string to a slice of rule values according to // the rule requirements as defined in the requirements map. func toValues(rule string, key string, input string) ([]string, error) { diff --git a/pkg/aa/signal.go b/pkg/aa/signal.go index 33265a6d0..ace95d79c 100644 --- a/pkg/aa/signal.go +++ b/pkg/aa/signal.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "slices" ) @@ -49,6 +50,16 @@ func newSignalFromLog(log map[string]string) Rule { } } +func (r *Signal) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + if err := validateValues(r.Kind(), "set", r.Set); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Signal) Less(other any) bool { o, _ := other.(*Signal) if len(r.Access) != len(o.Access) { diff --git a/pkg/aa/unix.go b/pkg/aa/unix.go index 88c37d198..eefe049f9 100644 --- a/pkg/aa/unix.go +++ b/pkg/aa/unix.go @@ -4,7 +4,10 @@ package aa -import "slices" +import ( + "fmt" + "slices" +) const tokUNIX = "unix" @@ -48,6 +51,13 @@ func newUnixFromLog(log map[string]string) Rule { } } +func (r *Unix) Validate() error { + if err := validateValues(r.Kind(), "access", r.Access); err != nil { + return fmt.Errorf("%s: %w", r, err) + } + return nil +} + func (r *Unix) Less(other any) bool { o, _ := other.(*Unix) if len(r.Access) != len(o.Access) { diff --git a/pkg/aa/userns.go b/pkg/aa/userns.go index 5e9437fab..e84093132 100644 --- a/pkg/aa/userns.go +++ b/pkg/aa/userns.go @@ -20,6 +20,10 @@ func newUsernsFromLog(log map[string]string) Rule { } } +func (r *Userns) Validate() error { + return nil +} + func (r *Userns) Less(other any) bool { o, _ := other.(*Userns) if r.Create != o.Create {