// apparmor.d - Full set of apparmor profiles // Copyright (C) 2023 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only package aa import ( "bytes" "strings" "golang.org/x/exp/slices" ) // AppArmorProfiles represents a full set of apparmor profiles type AppArmorProfiles map[string]*AppArmorProfile // ApparmorProfile represents a full apparmor profile. // Warning: close to the BNF grammar of apparmor profile but not exactly the same (yet): // - Some rules are not supported yet (subprofile, hat...) // - The structure is simplified as it only aims at writing profile, not parsing it. type AppArmorProfile struct { Preamble Profile } // Preamble section of a profile type Preamble struct { Abi []Abi Includes []Include Aliases []Alias Variables []Variable } // Profile section of a profile type Profile struct { Name string Attachments []string Attributes map[string]string Flags []string Rules Rules } // ApparmorRule generic interface type ApparmorRule interface { Less(other any) bool Equals(other any) bool } type Rules []ApparmorRule func NewAppArmorProfile() *AppArmorProfile { return &AppArmorProfile{} } // String returns the formatted representation of a profile as a string func (p *AppArmorProfile) String() string { var res bytes.Buffer err := tmplAppArmorProfile.Execute(&res, p) if err != nil { return err.Error() } return res.String() } // AddRule adds a new rule to the profile from a log map func (p *AppArmorProfile) AddRule(log map[string]string) { noNewPrivs := false fileInherit := false if log["operation"] == "file_inherit" { fileInherit = true } switch log["error"] { case "-1": noNewPrivs = true case "-2": if !slices.Contains(p.Flags, "mediate_deleted") { p.Flags = append(p.Flags, "mediate_deleted") } case "-13": if !slices.Contains(p.Flags, "attach_disconnected") { p.Flags = append(p.Flags, "attach_disconnected") } default: } switch log["class"] { case "cap": p.Capability = append(p.Capability, NewCapability(log, noNewPrivs, fileInherit)) case "file": p.File = append(p.File, NewFile(log, noNewPrivs, fileInherit)) case "net": if log["family"] == "unix" { p.Unix = append(p.Unix, NewUnix(log, noNewPrivs, fileInherit)) } else { p.Network = append(p.Network, NewNetwork(log, noNewPrivs, fileInherit)) } case "signal": p.Signal = append(p.Signal, NewSignal(log, noNewPrivs, fileInherit)) case "ptrace": p.Ptrace = append(p.Ptrace, NewPtrace(log, noNewPrivs, fileInherit)) case "unix": p.Unix = append(p.Unix, NewUnix(log, noNewPrivs, fileInherit)) case "mount": p.Mount = append(p.Mount, NewMount(log, noNewPrivs, fileInherit)) default: if strings.Contains(log["operation"], "dbus") { p.Dbus = append(p.Dbus, NewDbus(log, noNewPrivs, fileInherit)) } else if log["family"] == "unix" { p.Unix = append(p.Unix, NewUnix(log, noNewPrivs, fileInherit)) } } }