diff --git a/cmd/aa-log/main.go b/cmd/aa-log/main.go index ae9882b9..a7ef9a56 100644 --- a/cmd/aa-log/main.go +++ b/cmd/aa-log/main.go @@ -67,6 +67,8 @@ func aaLog(logger string, path string, profile string) error { if rules { profiles := aaLogs.ParseToProfiles() for _, profile := range profiles { + profile.Sort() + profile.MergeRules() fmt.Print(profile.String() + "\n") } } else { diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index c0257065..9168e1d0 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -1,11 +1,13 @@ // apparmor.d - Full set of apparmor profiles -// Copyright (C) 2023 Alexandre Pujol +// Copyright (C) 2021-2023 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only package aa import ( "bytes" + "reflect" + "sort" "strings" "golang.org/x/exp/slices" @@ -126,3 +128,44 @@ func (p *AppArmorProfile) AddRule(log map[string]string) { } } +func typeToValue(i reflect.Type) string { + return strings.ToLower(strings.TrimPrefix(i.String(), "*aa.")) +} + +// Sort the rules in the profile +func (p *AppArmorProfile) Sort() { + sort.Slice(p.Rules, func(i, j int) bool { + typeOfI := reflect.TypeOf(p.Rules[i]) + typeOfJ := reflect.TypeOf(p.Rules[j]) + if typeOfI != typeOfJ { + valueOfI := typeToValue(typeOfI) + valueOfJ := typeToValue(typeOfJ) + return ruleWeights[valueOfI] < ruleWeights[valueOfJ] + } + return p.Rules[i].Less(p.Rules[j]) + }) +} + +// MergeRules merge similar rules together +// Steps: +// - Remove identical rules +// - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw' +// +// Note: logs.regCleanLogs helps a lot to do a first cleaning +func (p *AppArmorProfile) MergeRules() { + for i := 0; i < len(p.Rules); i++ { + for j := i + 1; j < len(p.Rules); j++ { + typeOfI := reflect.TypeOf(p.Rules[i]) + typeOfJ := reflect.TypeOf(p.Rules[j]) + if typeOfI != typeOfJ { + continue + } + + // If rules are identical, merge them + if p.Rules[i].Equals(p.Rules[j]) { + p.Rules = append(p.Rules[:j], p.Rules[j+1:]...) + j-- + } + } + } +} diff --git a/pkg/aa/template.go b/pkg/aa/template.go index a47c80ff..48d88c8d 100644 --- a/pkg/aa/template.go +++ b/pkg/aa/template.go @@ -60,8 +60,64 @@ var ( "x": "rix", } + // The order the apparmor rules should be sorted + ruleAlphabet = []string{ + "include", + "rlimit", + "capability", + "network", + "mount", + "remount", + "umount", + "pivotroot", + "changeprofile", + "mqueue", + "signal", + "ptrace", + "unix", + "userns", + "iouring", + "dbus", + "file", + "include_local", + } + ruleWeights = map[string]int{} + + // The order the apparmor file rules should be sorted + fileAlphabet = []string{ + "@{exec_path}", // 1. entry point + "@{bin}", // 2.1 binaries + "@{lib}", // 2.2 libraries + "/opt", // 2.3 opt binaries & libraries + "/usr/share", // 3. shared data + "/etc", // 4. system configuration + "/", // 5.1 system data + "/var", // 5.2 system data read/write data + "/boot", // 5.3 boot files + "/home", // 6.1 user data + "@{HOME}", // 6.2 home files + "@{user_cache_dirs}", // 7.1 user caches + "@{user_config_dirs}", // 7.2 user config + "@{user_share_dirs}", // 7.3 user shared + "/tmp", // 8.1 Temporary data + "@{run}", // 8.2 Runtime data + "/dev/shm", // 8.3 Shared memory + "@{sys}", // 9. Sys files + "@{PROC}", // 10. Proc files + "/dev", // 11. Dev files + "deny", // 12. Deny rules + } + fileWeights = map[string]int{} ) +func init() { + for i, r := range fileAlphabet { + fileWeights[r] = i + } + for i, r := range ruleAlphabet { + ruleWeights[r] = i + } +} func join(i any) string { switch reflect.TypeOf(i).Kind() {