feat(aa): add the Kind struct to manage aa rules.

This commit is contained in:
Alexandre Pujol 2024-05-28 18:15:22 +01:00
parent 1333ec2025
commit 3b0944c615
No known key found for this signature in database
GPG key ID: C5469996F0DF68EC
23 changed files with 239 additions and 223 deletions

View file

@ -24,7 +24,7 @@ func newRule(rule []string) RuleBase {
idx := 0 idx := 0
for idx < len(rule) { for idx < len(rule) {
if rule[idx] == tokCOMMENT { if rule[idx] == COMMENT.Tok() {
comment = " " + strings.Join(rule[idx+1:], " ") comment = " " + strings.Join(rule[idx+1:], " ")
break break
} }
@ -85,15 +85,15 @@ func (r RuleBase) Equals(other any) bool {
} }
func (r RuleBase) String() string { func (r RuleBase) String() string {
return renderTemplate("comment", r) return renderTemplate(r.Kind(), r)
} }
func (r RuleBase) Constraint() constraint { func (r RuleBase) Constraint() constraint {
return anyKind return anyKind
} }
func (r RuleBase) Kind() string { func (r RuleBase) Kind() Kind {
return "base" return COMMENT
} }
type Qualifier struct { type Qualifier struct {

View file

@ -5,8 +5,7 @@
package aa package aa
const ( const (
tokHAT = "hat" HAT Kind = "hat"
tokCARET = "^"
) )
// Hat represents a single AppArmor hat. // Hat represents a single AppArmor hat.
@ -26,7 +25,7 @@ func (p *Hat) Less(other any) bool {
} }
func (p *Hat) Equals(other any) bool { func (p *Hat) Equals(other any) bool {
o, _ := other.(*Profile) o, _ := other.(*Hat)
return p.Name == o.Name return p.Name == o.Name
} }
@ -38,6 +37,6 @@ func (p *Hat) Constraint() constraint {
return blockKind return blockKind
} }
func (p *Hat) Kind() string { func (p *Hat) Kind() Kind {
return tokHAT return HAT
} }

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokCAPABILITY = "capability" const CAPABILITY Kind = "capability"
func init() { func init() {
requirements[tokCAPABILITY] = requirement{ requirements[CAPABILITY] = requirement{
"name": { "name": {
"audit_control", "audit_read", "audit_write", "block_suspend", "bpf", "audit_control", "audit_read", "audit_write", "block_suspend", "bpf",
"checkpoint_restore", "chown", "dac_override", "dac_read_search", "checkpoint_restore", "chown", "dac_override", "dac_read_search",
@ -36,7 +36,7 @@ func newCapabilityFromLog(log map[string]string) Rule {
return &Capability{ return &Capability{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Names: Must(toValues(tokCAPABILITY, "name", log["capname"])), Names: Must(toValues(CAPABILITY, "name", log["capname"])),
} }
} }
@ -70,6 +70,6 @@ func (r *Capability) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Capability) Kind() string { func (r *Capability) Kind() Kind {
return tokCAPABILITY return CAPABILITY
} }

View file

@ -6,10 +6,10 @@ package aa
import "fmt" import "fmt"
const tokCHANGEPROFILE = "change_profile" const CHANGEPROFILE Kind = "change_profile"
func init() { func init() {
requirements[tokCHANGEPROFILE] = requirement{ requirements[CHANGEPROFILE] = requirement{
"mode": []string{"safe", "unsafe"}, "mode": []string{"safe", "unsafe"},
} }
} }
@ -67,6 +67,6 @@ func (r *ChangeProfile) Constraint() constraint {
return blockKind return blockKind
} }
func (r *ChangeProfile) Kind() string { func (r *ChangeProfile) Kind() Kind {
return tokCHANGEPROFILE return CHANGEPROFILE
} }

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokDBUS = "dbus" const DBUS Kind = "dbus"
func init() { func init() {
requirements[tokDBUS] = requirement{ requirements[DBUS] = requirement{
"access": []string{ "access": []string{
"send", "receive", "bind", "eavesdrop", "r", "read", "send", "receive", "bind", "eavesdrop", "r", "read",
"w", "write", "rw", "w", "write", "rw",
@ -110,6 +110,6 @@ func (r *Dbus) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Dbus) Kind() string { func (r *Dbus) Kind() Kind {
return tokDBUS return DBUS
} }

View file

@ -11,14 +11,14 @@ import (
) )
const ( const (
tokLINK = "link" LINK Kind = "link"
tokFILE = "file" FILE Kind = "file"
tokOWNER = "owner" tokOWNER = "owner"
tokSUBSET = "subset" tokSUBSET = "subset"
) )
func init() { func init() {
requirements[tokFILE] = requirement{ requirements[FILE] = requirement{
"access": {"m", "r", "w", "l", "k"}, "access": {"m", "r", "w", "l", "k"},
"transition": { "transition": {
"ix", "ux", "Ux", "px", "Px", "cx", "Cx", "pix", "Pix", "cix", "ix", "ux", "Ux", "px", "Px", "cx", "Cx", "pix", "Pix", "cix",
@ -40,15 +40,15 @@ func isOwner(log map[string]string) bool {
// cmpFileAccess compares two access strings for file rules. // cmpFileAccess compares two access strings for file rules.
// It is aimed to be used in slices.SortFunc. // It is aimed to be used in slices.SortFunc.
func cmpFileAccess(i, j string) int { func cmpFileAccess(i, j string) int {
if slices.Contains(requirements[tokFILE]["access"], i) && if slices.Contains(requirements[FILE]["access"], i) &&
slices.Contains(requirements[tokFILE]["access"], j) { slices.Contains(requirements[FILE]["access"], j) {
return requirementsWeights[tokFILE]["access"][i] - requirementsWeights[tokFILE]["access"][j] return requirementsWeights[FILE]["access"][i] - requirementsWeights[FILE]["access"][j]
} }
if slices.Contains(requirements[tokFILE]["transition"], i) && if slices.Contains(requirements[FILE]["transition"], i) &&
slices.Contains(requirements[tokFILE]["transition"], j) { slices.Contains(requirements[FILE]["transition"], j) {
return requirementsWeights[tokFILE]["transition"][i] - requirementsWeights[tokFILE]["transition"][j] return requirementsWeights[FILE]["transition"][i] - requirementsWeights[FILE]["transition"][j]
} }
if slices.Contains(requirements[tokFILE]["access"], i) { if slices.Contains(requirements[FILE]["access"], i) {
return -1 return -1
} }
return 1 return 1
@ -121,8 +121,8 @@ func (r *File) Constraint() constraint {
return blockKind return blockKind
} }
func (r *File) Kind() string { func (r *File) Kind() Kind {
return tokFILE return FILE
} }
type Link struct { type Link struct {
@ -179,6 +179,6 @@ func (r *Link) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Link) Kind() string { func (r *Link) Kind() Kind {
return tokLINK return LINK
} }

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokIOURING = "io_uring" const IOURING Kind = "io_uring"
func init() { func init() {
requirements[tokIOURING] = requirement{ requirements[IOURING] = requirement{
"access": []string{"sqpoll", "override_creds"}, "access": []string{"sqpoll", "override_creds"},
} }
} }
@ -28,7 +28,7 @@ func newIOUringFromLog(log map[string]string) Rule {
return &IOUring{ return &IOUring{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Access: Must(toAccess(tokIOURING, log["requested"])), Access: Must(toAccess(IOURING, log["requested"])),
Label: log["label"], Label: log["label"],
} }
} }
@ -64,6 +64,6 @@ func (r *IOUring) Constraint() constraint {
return blockKind return blockKind
} }
func (r *IOUring) Kind() string { func (r *IOUring) Kind() Kind {
return tokIOURING return IOURING
} }

View file

@ -10,13 +10,13 @@ import (
) )
const ( const (
tokMOUNT = "mount" MOUNT Kind = "mount"
tokREMOUNT = "remount" REMOUNT Kind = "remount"
tokUMOUNT = "umount" UMOUNT Kind = "umount"
) )
func init() { func init() {
requirements[tokMOUNT] = requirement{ requirements[MOUNT] = requirement{
"flags": { "flags": {
"acl", "async", "atime", "ro", "rw", "bind", "rbind", "dev", "acl", "async", "atime", "ro", "rw", "bind", "rbind", "dev",
"diratime", "dirsync", "exec", "iversion", "loud", "mand", "move", "diratime", "dirsync", "exec", "iversion", "loud", "mand", "move",
@ -38,14 +38,14 @@ func newMountConditionsFromLog(log map[string]string) MountConditions {
if _, present := log["flags"]; present { if _, present := log["flags"]; present {
return MountConditions{ return MountConditions{
FsType: log["fstype"], FsType: log["fstype"],
Options: Must(toValues(tokMOUNT, "flags", log["flags"])), Options: Must(toValues(MOUNT, "flags", log["flags"])),
} }
} }
return MountConditions{FsType: log["fstype"]} return MountConditions{FsType: log["fstype"]}
} }
func (m MountConditions) Validate() error { func (m MountConditions) Validate() error {
return validateValues(tokMOUNT, "flags", m.Options) return validateValues(MOUNT, "flags", m.Options)
} }
func (m MountConditions) Less(other MountConditions) bool { func (m MountConditions) Less(other MountConditions) bool {
@ -113,8 +113,8 @@ func (r *Mount) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Mount) Kind() string { func (r *Mount) Kind() Kind {
return tokMOUNT return MOUNT
} }
type Umount struct { type Umount struct {
@ -166,8 +166,8 @@ func (r *Umount) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Umount) Kind() string { func (r *Umount) Kind() Kind {
return tokUMOUNT return UMOUNT
} }
type Remount struct { type Remount struct {
@ -219,6 +219,6 @@ func (r *Remount) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Remount) Kind() string { func (r *Remount) Kind() Kind {
return tokREMOUNT return REMOUNT
} }

View file

@ -10,10 +10,10 @@ import (
"strings" "strings"
) )
const tokMQUEUE = "mqueue" const MQUEUE Kind = "mqueue"
func init() { func init() {
requirements[tokMQUEUE] = requirement{ requirements[MQUEUE] = requirement{
"access": []string{ "access": []string{
"r", "w", "rw", "read", "write", "create", "open", "r", "w", "rw", "read", "write", "create", "open",
"delete", "getattr", "setattr", "delete", "getattr", "setattr",
@ -41,7 +41,7 @@ func newMqueueFromLog(log map[string]string) Rule {
return &Mqueue{ return &Mqueue{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Access: Must(toAccess(tokMQUEUE, log["requested"])), Access: Must(toAccess(MQUEUE, log["requested"])),
Type: mqueueType, Type: mqueueType,
Label: log["label"], Label: log["label"],
Name: log["name"], Name: log["name"],
@ -86,6 +86,6 @@ func (r *Mqueue) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Mqueue) Kind() string { func (r *Mqueue) Kind() Kind {
return tokMQUEUE return MQUEUE
} }

View file

@ -8,10 +8,10 @@ import (
"fmt" "fmt"
) )
const tokNETWORK = "network" const NETWORK Kind = "network"
func init() { func init() {
requirements[tokNETWORK] = requirement{ requirements[NETWORK] = requirement{
"access": []string{ "access": []string{
"create", "bind", "listen", "accept", "connect", "shutdown", "create", "bind", "listen", "accept", "connect", "shutdown",
"getattr", "setattr", "getopt", "setopt", "send", "receive", "getattr", "setattr", "getopt", "setopt", "send", "receive",
@ -126,6 +126,6 @@ func (r *Network) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Network) Kind() string { func (r *Network) Kind() Kind {
return tokNETWORK return NETWORK
} }

View file

@ -26,12 +26,16 @@ const (
var ( var (
newRuleMap = map[string]func([]string) (Rule, error){ newRuleMap = map[string]func([]string) (Rule, error){
tokCOMMENT: newComment, COMMENT.Tok(): newComment,
tokABI: newAbi, ABI.Tok(): newAbi,
tokALIAS: newAlias, ALIAS.Tok(): newAlias,
tokINCLUDE: newInclude, INCLUDE.Tok(): newInclude,
} }
tok = map[Kind]string{
COMMENT: "#",
VARIABLE: "@{",
}
openBlocks = []rune{tokOPENPAREN, tokOPENBRACE, tokOPENBRACKET} openBlocks = []rune{tokOPENPAREN, tokOPENBRACE, tokOPENBRACKET}
closeBlocks = []rune{tokCLOSEPAREN, tokCLOSEBRACE, tokCLOSEBRACKET} closeBlocks = []rune{tokCLOSEPAREN, tokCLOSEBRACE, tokCLOSEBRACKET}
) )
@ -53,7 +57,7 @@ func tokenize(str string) []string {
blockStack := []rune{} blockStack := []rune{}
tokens := make([]string, 0, len(str)/2) tokens := make([]string, 0, len(str)/2)
if len(str) > 2 && str[0:2] == tokVARIABLE { if len(str) > 2 && str[0:2] == VARIABLE.Tok() {
isVariable = true isVariable = true
} }
for _, r := range str { for _, r := range str {
@ -122,7 +126,7 @@ func tokenToSlice(token string) []string {
func tokensStripComment(tokens []string) []string { func tokensStripComment(tokens []string) []string {
res := []string{} res := []string{}
for _, v := range tokens { for _, v := range tokens {
if v == tokCOMMENT { if v == COMMENT.Tok() {
break break
} }
res = append(res, v) res = append(res, v)
@ -147,7 +151,7 @@ func newRules(rules [][]string) (Rules, error) {
return nil, err return nil, err
} }
res = append(res, r) res = append(res, r)
} else if strings.HasPrefix(rule[0], tokVARIABLE) { } else if strings.HasPrefix(rule[0], VARIABLE.Tok()) {
r, err = newVariable(rule) r, err = newVariable(rule)
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,7 +171,7 @@ func (f *AppArmorProfileFile) parsePreamble(input []string) error {
tokenizedRules := [][]string{} tokenizedRules := [][]string{}
for _, line := range input { for _, line := range input {
if strings.HasPrefix(line, tokCOMMENT) { if strings.HasPrefix(line, COMMENT.Tok()) {
r, err = newComment(strings.Split(line, " ")) r, err = newComment(strings.Split(line, " "))
if err != nil { if err != nil {
return err return err
@ -215,7 +219,7 @@ done:
switch { switch {
case tmp == "": case tmp == "":
continue continue
case strings.HasPrefix(tmp, tokPROFILE): case strings.HasPrefix(tmp, PROFILE.Tok()):
rawHeader = tmp rawHeader = tmp
break done break done
default: default:

View file

@ -4,7 +4,7 @@
package aa package aa
const tokPIVOTROOT = "pivot_root" const PIVOTROOT = "pivot_root"
type PivotRoot struct { type PivotRoot struct {
RuleBase RuleBase
@ -57,6 +57,6 @@ func (r *PivotRoot) Constraint() constraint {
return blockKind return blockKind
} }
func (r *PivotRoot) Kind() string { func (r *PivotRoot) Kind() Kind {
return tokPIVOTROOT return PIVOTROOT
} }

View file

@ -11,12 +11,13 @@ import (
) )
const ( const (
tokABI = "abi" ABI Kind = "abi"
tokALIAS = "alias" ALIAS Kind = "alias"
tokINCLUDE = "include" INCLUDE Kind = "include"
VARIABLE Kind = "variable"
COMMENT Kind = "comment"
tokIFEXISTS = "if exists" tokIFEXISTS = "if exists"
tokVARIABLE = "@{"
tokCOMMENT = "#"
) )
type Comment struct { type Comment struct {
@ -42,7 +43,7 @@ func (r *Comment) Equals(other any) bool {
} }
func (r *Comment) String() string { func (r *Comment) String() string {
return renderTemplate("comment", r) return renderTemplate(r.Kind(), r)
} }
func (r *Comment) IsPreamble() bool { func (r *Comment) IsPreamble() bool {
@ -53,8 +54,8 @@ func (r *Comment) Constraint() constraint {
return anyKind return anyKind
} }
func (r *Comment) Kind() string { func (r *Comment) Kind() Kind {
return tokCOMMENT return COMMENT
} }
type Abi struct { type Abi struct {
@ -65,7 +66,7 @@ type Abi struct {
func newAbi(rule []string) (Rule, error) { func newAbi(rule []string) (Rule, error) {
var magic bool var magic bool
if len(rule) > 0 && rule[0] == tokABI { if len(rule) > 0 && rule[0] == ABI.Tok() {
rule = rule[1:] rule = rule[1:]
} }
if len(rule) != 1 { if len(rule) != 1 {
@ -113,8 +114,8 @@ func (r *Abi) Constraint() constraint {
return preambleKind return preambleKind
} }
func (r *Abi) Kind() string { func (r *Abi) Kind() Kind {
return tokABI return ABI
} }
type Alias struct { type Alias struct {
@ -124,7 +125,7 @@ type Alias struct {
} }
func newAlias(rule []string) (Rule, error) { func newAlias(rule []string) (Rule, error) {
if len(rule) > 0 && rule[0] == tokALIAS { if len(rule) > 0 && rule[0] == ALIAS.Tok() {
rule = rule[1:] rule = rule[1:]
} }
if len(rule) != 3 { if len(rule) != 3 {
@ -165,8 +166,8 @@ func (r *Alias) Constraint() constraint {
return preambleKind return preambleKind
} }
func (r *Alias) Kind() string { func (r *Alias) Kind() Kind {
return tokALIAS return ALIAS
} }
type Include struct { type Include struct {
@ -180,7 +181,7 @@ func newInclude(rule []string) (Rule, error) {
var magic bool var magic bool
var ifexists bool var ifexists bool
if len(rule) > 0 && rule[0] == tokINCLUDE { if len(rule) > 0 && rule[0] == INCLUDE.Tok() {
rule = rule[1:] rule = rule[1:]
} }
@ -239,8 +240,8 @@ func (r *Include) Constraint() constraint {
return anyKind return anyKind
} }
func (r *Include) Kind() string { func (r *Include) Kind() Kind {
return tokINCLUDE return INCLUDE
} }
type Variable struct { type Variable struct {
@ -257,7 +258,7 @@ func newVariable(rule []string) (Rule, error) {
return nil, fmt.Errorf("invalid variable format: %v", rule) return nil, fmt.Errorf("invalid variable format: %v", rule)
} }
name := strings.Trim(rule[0], tokVARIABLE+"}") name := strings.Trim(rule[0], VARIABLE.Tok()+"}")
switch rule[1] { switch rule[1] {
case tokEQUAL: case tokEQUAL:
define = true define = true
@ -297,13 +298,13 @@ func (r *Variable) Equals(other any) bool {
} }
func (r *Variable) String() string { func (r *Variable) String() string {
return renderTemplate("variable", r) return renderTemplate(r.Kind(), r)
} }
func (r *Variable) Constraint() constraint { func (r *Variable) Constraint() constraint {
return preambleKind return preambleKind
} }
func (r *Variable) Kind() string { func (r *Variable) Kind() Kind {
return tokVARIABLE return VARIABLE
} }

View file

@ -14,13 +14,14 @@ import (
) )
const ( const (
PROFILE Kind = "profile"
tokATTRIBUTES = "xattrs" tokATTRIBUTES = "xattrs"
tokFLAGS = "flags" tokFLAGS = "flags"
tokPROFILE = "profile"
) )
func init() { func init() {
requirements[tokPROFILE] = requirement{ requirements[PROFILE] = requirement{
tokFLAGS: { tokFLAGS: {
"enforce", "complain", "kill", "default_allow", "unconfined", "enforce", "complain", "kill", "default_allow", "unconfined",
"prompt", "audit", "mediate_deleted", "attach_disconnected", "prompt", "audit", "mediate_deleted", "attach_disconnected",
@ -52,7 +53,7 @@ func newHeader(rule []string) (Header, error) {
if rule[len(rule)-1] == "{" { if rule[len(rule)-1] == "{" {
rule = rule[:len(rule)-1] rule = rule[:len(rule)-1]
} }
if rule[0] == tokPROFILE { if rule[0] == PROFILE.Tok() {
rule = rule[1:] rule = rule[1:]
} }
@ -120,8 +121,8 @@ func (p *Profile) Constraint() constraint {
return blockKind return blockKind
} }
func (p *Profile) Kind() string { func (p *Profile) Kind() Kind {
return tokPROFILE return PROFILE
} }
// Merge merge similar rules together. // Merge merge similar rules together.

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokPTRACE = "ptrace" const PTRACE Kind = "ptrace"
func init() { func init() {
requirements[tokPTRACE] = requirement{ requirements[PTRACE] = requirement{
"access": []string{ "access": []string{
"r", "w", "rw", "read", "readby", "trace", "tracedby", "r", "w", "rw", "read", "readby", "trace", "tracedby",
}, },
@ -30,7 +30,7 @@ func newPtraceFromLog(log map[string]string) Rule {
return &Ptrace{ return &Ptrace{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Access: Must(toAccess(tokPTRACE, log["requested_mask"])), Access: Must(toAccess(PTRACE, log["requested_mask"])),
Peer: log["peer"], Peer: log["peer"],
} }
} }
@ -67,6 +67,6 @@ func (r *Ptrace) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Ptrace) Kind() string { func (r *Ptrace) Kind() Kind {
return tokPTRACE return PTRACE
} }

View file

@ -59,7 +59,7 @@ func (f *AppArmorProfileFile) Resolve() error {
} }
func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) { func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) {
if !strings.Contains(input, tokVARIABLE) { if !strings.Contains(input, VARIABLE.Tok()) {
return []string{input}, nil return []string{input}, nil
} }
@ -76,7 +76,7 @@ func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) {
if vrbl.Name == varname { if vrbl.Name == varname {
found = true found = true
for _, v := range vrbl.Values { for _, v := range vrbl.Values {
if strings.Contains(v, tokVARIABLE+varname+"}") { if strings.Contains(v, VARIABLE.Tok()+varname+"}") {
return nil, fmt.Errorf("recursive variable found in: %s", varname) return nil, fmt.Errorf("recursive variable found in: %s", varname)
} }
newValues := strings.ReplaceAll(input, variable, v) newValues := strings.ReplaceAll(input, variable, v)
@ -152,7 +152,7 @@ func (f *AppArmorProfileFile) resolveInclude(include *Include) error {
} }
// Remove all includes in iFile // Remove all includes in iFile
iFile.Preamble = iFile.Preamble.DeleteKind(tokINCLUDE) iFile.Preamble = iFile.Preamble.DeleteKind(INCLUDE)
// Cache the included file // Cache the included file
includeCache[include] = iFile includeCache[include] = iFile

View file

@ -7,12 +7,11 @@ package aa
import "fmt" import "fmt"
const ( const (
tokRLIMIT = "rlimit" RLIMIT Kind = "rlimit"
tokSET = "set"
) )
func init() { func init() {
requirements[tokRLIMIT] = requirement{ requirements[RLIMIT] = requirement{
"keys": { "keys": {
"cpu", "fsize", "data", "stack", "core", "rss", "nofile", "ofile", "cpu", "fsize", "data", "stack", "core", "rss", "nofile", "ofile",
"as", "nproc", "memlock", "locks", "sigpending", "msgqueue", "nice", "as", "nproc", "memlock", "locks", "sigpending", "msgqueue", "nice",
@ -68,6 +67,6 @@ func (r *Rlimit) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Rlimit) Kind() string { func (r *Rlimit) Kind() Kind {
return tokRLIMIT return RLIMIT
} }

View file

@ -26,6 +26,20 @@ const (
blockKind // The rule can only be found in a profile blockKind // The rule can only be found in a profile
) )
// Kind represents an AppArmor rule kind.
type Kind string
func (k Kind) String() string {
return string(k)
}
func (k Kind) Tok() string {
if t, ok := tok[k]; ok {
return t
}
return string(k)
}
// Rule generic interface for all AppArmor rules // Rule generic interface for all AppArmor rules
type Rule interface { type Rule interface {
Validate() error Validate() error
@ -33,7 +47,7 @@ type Rule interface {
Equals(other any) bool Equals(other any) bool
String() string String() string
Constraint() constraint Constraint() constraint
Kind() string Kind() Kind
} }
type Rules []Rule type Rules []Rule
@ -77,7 +91,7 @@ func (r Rules) Delete(i int) Rules {
return append(r[:i], r[i+1:]...) return append(r[:i], r[i+1:]...)
} }
func (r Rules) DeleteKind(kind string) Rules { func (r Rules) DeleteKind(kind Kind) Rules {
res := make(Rules, 0) res := make(Rules, 0)
for _, rule := range r { for _, rule := range r {
if rule.Kind() != kind { if rule.Kind() != kind {
@ -87,7 +101,7 @@ func (r Rules) DeleteKind(kind string) Rules {
return res return res
} }
func (r Rules) Filter(filter string) Rules { func (r Rules) Filter(filter Kind) Rules {
res := make(Rules, 0) res := make(Rules, 0)
for _, rule := range r { for _, rule := range r {
if rule.Kind() != filter { if rule.Kind() != filter {
@ -128,12 +142,12 @@ func Must[T any](v T, err error) T {
return v return v
} }
func validateValues(rule string, key string, values []string) error { func validateValues(kind Kind, key string, values []string) error {
for _, v := range values { for _, v := range values {
if v == "" { if v == "" {
continue continue
} }
if !slices.Contains(requirements[rule][key], v) { if !slices.Contains(requirements[kind][key], v) {
return fmt.Errorf("invalid mode '%s'", v) return fmt.Errorf("invalid mode '%s'", v)
} }
} }
@ -142,10 +156,10 @@ func validateValues(rule string, key string, values []string) error {
// Helper function to convert a string to a slice of rule values according to // Helper function to convert a string to a slice of rule values according to
// the rule requirements as defined in the requirements map. // the rule requirements as defined in the requirements map.
func toValues(rule string, key string, input string) ([]string, error) { func toValues(kind Kind, key string, input string) ([]string, error) {
req, ok := requirements[rule][key] req, ok := requirements[kind][key]
if !ok { if !ok {
return nil, fmt.Errorf("unrecognized requirement '%s' for rule %s", key, rule) return nil, fmt.Errorf("unrecognized requirement '%s' for rule %s", key, kind)
} }
res := tokenToSlice(input) res := tokenToSlice(input)
@ -156,22 +170,22 @@ func toValues(rule string, key string, input string) ([]string, error) {
} }
} }
slices.SortFunc(res, func(i, j string) int { slices.SortFunc(res, func(i, j string) int {
return requirementsWeights[rule][key][i] - requirementsWeights[rule][key][j] return requirementsWeights[kind][key][i] - requirementsWeights[kind][key][j]
}) })
return slices.Compact(res), nil return slices.Compact(res), nil
} }
// Helper function to convert an access string to a slice of access according to // Helper function to convert an access string to a slice of access according to
// the rule requirements as defined in the requirements map. // the rule requirements as defined in the requirements map.
func toAccess(rule string, input string) ([]string, error) { func toAccess(kind Kind, input string) ([]string, error) {
var res []string var res []string
switch rule { switch kind {
case tokFILE: case FILE:
raw := strings.Split(input, "") raw := strings.Split(input, "")
trans := []string{} trans := []string{}
for _, access := range raw { for _, access := range raw {
if slices.Contains(requirements[tokFILE]["access"], access) { if slices.Contains(requirements[FILE]["access"], access) {
res = append(res, access) res = append(res, access)
} else { } else {
trans = append(trans, access) trans = append(trans, access)
@ -180,17 +194,17 @@ func toAccess(rule string, input string) ([]string, error) {
transition := strings.Join(trans, "") transition := strings.Join(trans, "")
if len(transition) > 0 { if len(transition) > 0 {
if slices.Contains(requirements[tokFILE]["transition"], transition) { if slices.Contains(requirements[FILE]["transition"], transition) {
res = append(res, transition) res = append(res, transition)
} else { } else {
return nil, fmt.Errorf("unrecognized transition: %s", transition) return nil, fmt.Errorf("unrecognized transition: %s", transition)
} }
} }
case tokFILE + "-log": case FILE + "-log":
raw := strings.Split(input, "") raw := strings.Split(input, "")
for _, access := range raw { for _, access := range raw {
if slices.Contains(requirements[tokFILE]["access"], access) { if slices.Contains(requirements[FILE]["access"], access) {
res = append(res, access) res = append(res, access)
} else if maskToAccess[access] != "" { } else if maskToAccess[access] != "" {
res = append(res, maskToAccess[access]) res = append(res, maskToAccess[access])
@ -200,7 +214,7 @@ func toAccess(rule string, input string) ([]string, error) {
} }
default: default:
return toValues(rule, "access", input) return toValues(kind, "access", input)
} }
slices.SortFunc(res, cmpFileAccess) slices.SortFunc(res, cmpFileAccess)

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokSIGNAL = "signal" const SIGNAL Kind = "signal"
func init() { func init() {
requirements[tokSIGNAL] = requirement{ requirements[SIGNAL] = requirement{
"access": { "access": {
"r", "w", "rw", "read", "write", "send", "receive", "r", "w", "rw", "read", "write", "send", "receive",
}, },
@ -44,7 +44,7 @@ func newSignalFromLog(log map[string]string) Rule {
return &Signal{ return &Signal{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Access: Must(toAccess(tokSIGNAL, log["requested_mask"])), Access: Must(toAccess(SIGNAL, log["requested_mask"])),
Set: []string{log["signal"]}, Set: []string{log["signal"]},
Peer: log["peer"], Peer: log["peer"],
} }
@ -88,6 +88,6 @@ func (r *Signal) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Signal) Kind() string { func (r *Signal) Kind() Kind {
return tokSIGNAL return SIGNAL
} }

View file

@ -25,7 +25,7 @@ var (
// The functions available in the template // The functions available in the template
tmplFunctionMap = template.FuncMap{ tmplFunctionMap = template.FuncMap{
"typeof": typeOf, "kindof": kindOf,
"join": join, "join": join,
"cjoin": cjoin, "cjoin": cjoin,
"indent": indent, "indent": indent,
@ -34,24 +34,25 @@ var (
} }
// The apparmor templates // The apparmor templates
tmpl = generateTemplates([]string{ tmpl = generateTemplates([]Kind{
// Global templates // Global templates
"apparmor", "apparmor",
tokPROFILE, PROFILE,
HAT,
"rules", "rules",
// Preamble templates // Preamble templates
tokABI, ABI,
tokALIAS, ALIAS,
tokINCLUDE, INCLUDE,
"variable", VARIABLE,
"comment", COMMENT,
// Rules templates // Rules templates
tokALL, tokRLIMIT, tokUSERNS, tokCAPABILITY, tokNETWORK, ALL, RLIMIT, USERNS, CAPABILITY, NETWORK,
tokMOUNT, tokREMOUNT, tokUMOUNT, tokPIVOTROOT, tokCHANGEPROFILE, MOUNT, REMOUNT, UMOUNT, PIVOTROOT, CHANGEPROFILE,
tokMQUEUE, tokIOURING, tokUNIX, tokPTRACE, tokSIGNAL, tokDBUS, MQUEUE, IOURING, UNIX, PTRACE, SIGNAL, DBUS,
tokFILE, tokLINK, FILE, LINK,
}) })
// convert apparmor requested mask to apparmor access mode // convert apparmor requested mask to apparmor access mode
@ -64,27 +65,28 @@ var (
} }
// The order the apparmor rules should be sorted // The order the apparmor rules should be sorted
ruleAlphabet = []string{ ruleAlphabet = []Kind{
"include", INCLUDE,
"all", ALL,
"rlimit", RLIMIT,
"userns", USERNS,
"capability", CAPABILITY,
"network", NETWORK,
"mount", MOUNT,
"remount", REMOUNT,
"umount", UMOUNT,
"pivotroot", PIVOTROOT,
"changeprofile", CHANGEPROFILE,
"mqueue", MQUEUE,
"iouring", IOURING,
"signal", SIGNAL,
"ptrace", PTRACE,
"unix", UNIX,
"dbus", DBUS,
"file", FILE,
"link", LINK,
"profile", PROFILE,
HAT,
"include_if_exists", "include_if_exists",
} }
ruleWeights = generateWeights(ruleAlphabet) ruleWeights = generateWeights(ruleAlphabet)
@ -117,16 +119,16 @@ var (
fileWeights = generateWeights(fileAlphabet) fileWeights = generateWeights(fileAlphabet)
// The order the rule values (access, type, domains, etc) should be sorted // The order the rule values (access, type, domains, etc) should be sorted
requirements = map[string]requirement{} requirements = map[Kind]requirement{}
requirementsWeights map[string]map[string]map[string]int requirementsWeights map[Kind]map[string]map[string]int
) )
func init() { func init() {
requirementsWeights = generateRequirementsWeights(requirements) requirementsWeights = generateRequirementsWeights(requirements)
} }
func generateTemplates(names []string) map[string]*template.Template { func generateTemplates(names []Kind) map[Kind]*template.Template {
res := make(map[string]*template.Template, len(names)) res := make(map[Kind]*template.Template, len(names))
base := template.New("").Funcs(tmplFunctionMap) base := template.New("").Funcs(tmplFunctionMap)
base = template.Must(base.ParseFS(tmplFiles, base = template.Must(base.ParseFS(tmplFiles,
"templates/*.j2", "templates/rule/*.j2", "templates/*.j2", "templates/rule/*.j2",
@ -141,11 +143,11 @@ func generateTemplates(names []string) map[string]*template.Template {
return res return res
} }
func renderTemplate(name string, data any) string { func renderTemplate(name Kind, data any) string {
var res strings.Builder var res strings.Builder
template, ok := tmpl[name] template, ok := tmpl[name]
if !ok { if !ok {
panic("template '" + name + "' not found") panic("template '" + name.String() + "' not found")
} }
err := template.Execute(&res, data) err := template.Execute(&res, data)
if err != nil { if err != nil {
@ -154,16 +156,16 @@ func renderTemplate(name string, data any) string {
return res.String() return res.String()
} }
func generateWeights(alphabet []string) map[string]int { func generateWeights[T Kind | string](alphabet []T) map[T]int {
res := make(map[string]int, len(alphabet)) res := make(map[T]int, len(alphabet))
for i, r := range alphabet { for i, r := range alphabet {
res[r] = i res[r] = i
} }
return res return res
} }
func generateRequirementsWeights(requirements map[string]requirement) map[string]map[string]map[string]int { func generateRequirementsWeights(requirements map[Kind]requirement) map[Kind]map[string]map[string]int {
res := make(map[string]map[string]map[string]int, len(requirements)) res := make(map[Kind]map[string]map[string]int, len(requirements))
for rule, req := range requirements { for rule, req := range requirements {
res[rule] = make(map[string]map[string]int, len(req)) res[rule] = make(map[string]map[string]int, len(req))
for key, values := range req { for key, values := range req {
@ -207,15 +209,11 @@ func cjoin(i any) string {
} }
} }
func typeOf(i any) string { func kindOf(i any) string {
if i == nil { if i == nil {
return "" return ""
} }
return strings.TrimPrefix(reflect.TypeOf(i).String(), "*aa.") return i.(Rule).Kind().String()
}
func typeToValue(i reflect.Type) string {
return strings.ToLower(strings.TrimPrefix(i.String(), "*aa."))
} }
func setindent(i string) string { func setindent(i string) string {

View file

@ -4,118 +4,118 @@
{{- define "rules" -}} {{- define "rules" -}}
{{- $oldtype := "" -}} {{- $oldkind := "" -}}
{{- range . -}} {{- range . -}}
{{- $type := typeof . -}} {{- $kind := kindof . -}}
{{- if eq $type "" -}} {{- if eq $kind "" -}}
{{- "\n" -}} {{- "\n" -}}
{{- continue -}} {{- continue -}}
{{- end -}} {{- end -}}
{{- if eq $type "Comment" -}} {{- if eq $kind "comment" -}}
{{- template "comment" . -}} {{- template "comment" . -}}
{{- "\n" -}} {{- "\n" -}}
{{- continue -}} {{- continue -}}
{{- end -}} {{- end -}}
{{- if and (ne $type $oldtype) (ne $oldtype "") -}} {{- if and (ne $kind $oldkind) (ne $oldkind "") -}}
{{- "\n" -}} {{- "\n" -}}
{{- end -}} {{- end -}}
{{- indent "" -}} {{- indent "" -}}
{{- if eq $type "Abi" -}} {{- if eq $kind "abi" -}}
{{- template "abi" . -}} {{- template "abi" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Alias" -}} {{- if eq $kind "alias" -}}
{{- template "alias" . -}} {{- template "alias" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Include" -}} {{- if eq $kind "include" -}}
{{- template "include" . -}} {{- template "include" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Variable" -}} {{- if eq $kind "variable" -}}
{{- template "variable" . -}} {{- template "variable" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "All" -}} {{- if eq $kind "all" -}}
{{- template "all" . -}} {{- template "all" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Rlimit" -}} {{- if eq $kind "rlimit" -}}
{{- template "rlimit" . -}} {{- template "rlimit" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Userns" -}} {{- if eq $kind "userns" -}}
{{- template "userns" . -}} {{- template "userns" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Capability" -}} {{- if eq $kind "capability" -}}
{{- template "capability" . -}} {{- template "capability" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Network" -}} {{- if eq $kind "network" -}}
{{- template "network" . -}} {{- template "network" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Mount" -}} {{- if eq $kind "mount" -}}
{{- template "mount" . -}} {{- template "mount" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Remount" -}} {{- if eq $kind "remount" -}}
{{- template "remount" . -}} {{- template "remount" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Umount" -}} {{- if eq $kind "umount" -}}
{{- template "umount" . -}} {{- template "umount" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "PivotRoot" -}} {{- if eq $kind "pivot_root" -}}
{{- template "pivot_root" . -}} {{- template "pivot_root" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "ChangeProfile" -}} {{- if eq $kind "change_profile" -}}
{{- template "change_profile" . -}} {{- template "change_profile" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Mqueue" -}} {{- if eq $kind "mqueue" -}}
{{- template "mqueue" . -}} {{- template "mqueue" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "IOUring" -}} {{- if eq $kind "io_uring" -}}
{{- template "io_uring" . -}} {{- template "io_uring" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Unix" -}} {{- if eq $kind "unix" -}}
{{- template "unix" . -}} {{- template "unix" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Ptrace" -}} {{- if eq $kind "ptrace" -}}
{{- template "ptrace" . -}} {{- template "ptrace" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Signal" -}} {{- if eq $kind "signal" -}}
{{- template "signal" . -}} {{- template "signal" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Dbus" -}} {{- if eq $kind "dbus" -}}
{{- template "dbus" . -}} {{- template "dbus" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "File" -}} {{- if eq $kind "file" -}}
{{- template "file" . -}} {{- template "file" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Link" -}} {{- if eq $kind "link" -}}
{{- template "link" . -}} {{- template "link" . -}}
{{- end -}} {{- end -}}
{{- if eq $type "Profile" -}} {{- if eq $kind "profile" -}}
{{- template "profile" . -}} {{- template "profile" . -}}
{{- end -}} {{- end -}}
{{- "\n" -}} {{- "\n" -}}
{{- $oldtype = $type -}} {{- $oldkind = $kind -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}

View file

@ -9,10 +9,10 @@ import (
"slices" "slices"
) )
const tokUNIX = "unix" const UNIX Kind = "unix"
func init() { func init() {
requirements[tokUNIX] = requirement{ requirements[UNIX] = requirement{
"access": []string{ "access": []string{
"create", "bind", "listen", "accept", "connect", "shutdown", "create", "bind", "listen", "accept", "connect", "shutdown",
"getattr", "setattr", "getopt", "setopt", "send", "receive", "getattr", "setattr", "getopt", "setopt", "send", "receive",
@ -39,7 +39,7 @@ func newUnixFromLog(log map[string]string) Rule {
return &Unix{ return &Unix{
RuleBase: newRuleFromLog(log), RuleBase: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log), Qualifier: newQualifierFromLog(log),
Access: Must(toAccess(tokUNIX, log["requested_mask"])), Access: Must(toAccess(UNIX, log["requested_mask"])),
Type: log["sock_type"], Type: log["sock_type"],
Protocol: log["protocol"], Protocol: log["protocol"],
Address: log["addr"], Address: log["addr"],
@ -107,6 +107,6 @@ func (r *Unix) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Unix) Kind() string { func (r *Unix) Kind() Kind {
return tokUNIX return UNIX
} }

View file

@ -4,7 +4,7 @@
package aa package aa
const tokUSERNS = "userns" const USERNS Kind = "userns"
type Userns struct { type Userns struct {
RuleBase RuleBase
@ -45,6 +45,6 @@ func (r *Userns) Constraint() constraint {
return blockKind return blockKind
} }
func (r *Userns) Kind() string { func (r *Userns) Kind() Kind {
return tokUSERNS return USERNS
} }