From 538da05696b353dab1a1c1fe22082e905fff69fd Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Sat, 6 May 2023 12:18:20 +0100 Subject: [PATCH] feat(aa-log): add -a option to anonymize the logs. --- cmd/aa-log/main.go | 19 +++++++---- cmd/aa-log/main_test.go | 7 +++- pkg/logs/logs.go | 27 ++++++++++++++++ pkg/logs/logs_test.go | 71 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 7 deletions(-) diff --git a/cmd/aa-log/main.go b/cmd/aa-log/main.go index 8cd66f52..41f114a1 100644 --- a/cmd/aa-log/main.go +++ b/cmd/aa-log/main.go @@ -11,7 +11,7 @@ import ( "os" "github.com/roddhjav/apparmor.d/pkg/logs" - "github.com/roddhjav/apparmor.d/pkg/util" + "golang.org/x/exp/slices" ) const usage = `aa-log [-h] [--systemd] [--file file] [profile] @@ -28,17 +28,19 @@ Options: -h, --help Show this help message and exit. -f, --file FILE Set a logfile or a suffix to the default log file. -s, --systemd Parse systemd logs from journalctl. + -a, --anonymize Anonymize the logs. ` // Command line options var ( - help bool - path string - systemd bool + help bool + anonymize bool + path string + systemd bool ) -func aaLog(logger string, path string, profile string) error { +func aaLog(logger string, path string, profile string, anonymize bool) error { var err error var file io.Reader @@ -54,6 +56,9 @@ func aaLog(logger string, path string, profile string) error { return err } aaLogs := logs.NewApparmorLogs(file, profile) + if anonymize { + aaLogs.Anonymize() + } fmt.Print(aaLogs.String()) return nil } @@ -65,6 +70,8 @@ func init() { flag.StringVar(&path, "file", "", "Set a logfile or a suffix to the default log file.") flag.BoolVar(&systemd, "s", false, "Parse systemd logs from journalctl.") flag.BoolVar(&systemd, "systemd", false, "Parse systemd logs from journalctl.") + flag.BoolVar(&anonymize, "a", false, "Anonymize the logs.") + flag.BoolVar(&anonymize, "anonymize", false, "Anonymize the logs.") } func main() { @@ -86,7 +93,7 @@ func main() { } logfile := logs.GetLogFile(path) - err := aaLog(logger, logfile, profile) + err := aaLog(logger, logfile, profile, anonymize) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/aa-log/main_test.go b/cmd/aa-log/main_test.go index e07da7d7..513d3f1e 100644 --- a/cmd/aa-log/main_test.go +++ b/cmd/aa-log/main_test.go @@ -14,6 +14,7 @@ func Test_app(t *testing.T) { logger string path string profile string + anon bool wantErr bool }{ { @@ -21,6 +22,7 @@ func Test_app(t *testing.T) { logger: "auditd", path: "../../tests/audit.log", profile: "", + anon: true, wantErr: false, }, { @@ -28,6 +30,7 @@ func Test_app(t *testing.T) { logger: "systemd", path: "../../tests/systemd.log", profile: "", + anon: false, wantErr: false, }, { @@ -35,6 +38,7 @@ func Test_app(t *testing.T) { logger: "auditd", path: "../../tests/log", profile: "", + anon: false, wantErr: true, }, { @@ -42,12 +46,13 @@ func Test_app(t *testing.T) { logger: "raw", path: "../../tests/audit.log", profile: "", + anon: false, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := aaLog(tt.logger, tt.path, tt.profile); (err != nil) != tt.wantErr { + if err := aaLog(tt.logger, tt.path, tt.profile, tt.anon); (err != nil) != tt.wantErr { t.Errorf("aaLog() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index 6cede1d5..048924c9 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -8,6 +8,7 @@ import ( "bufio" "fmt" "io" + "os/user" "regexp" "strings" @@ -28,6 +29,9 @@ const ( boldYellow = "\033[1;33m" ) +// Anonymized username +const Username = "AAD" + var ( quoted bool isAppArmorLogTemplate = regexp.MustCompile(`apparmor=("DENIED"|"ALLOWED"|"AUDIT")`) @@ -116,6 +120,29 @@ func NewApparmorLogs(file io.Reader, profile string) AppArmorLogs { return aaLogs } +// Anonymize the logs before reporting +func (aaLogs AppArmorLogs) Anonymize() { + user, _ := user.Current() + keys := []string{"name", "comm"} + regAnonymizeLogs := []struct { + regex *regexp.Regexp + repl string + }{ + {regexp.MustCompile(user.Username), Username}, + {regexp.MustCompile(`/home/[^/]+`), `/home/` + Username}, + {regexp.MustCompile(`[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*`), `b08dfa60-83e7-567a-1921-a715000001fb`}, + } + for _, log := range aaLogs { + for _, key := range keys { + if _, ok := log[key]; ok { + for _, aa := range regAnonymizeLogs { + log[key] = aa.regex.ReplaceAllLiteralString(log[key], aa.repl) + } + } + } + } +} + // String returns a formatted AppArmor logs string func (aaLogs AppArmorLogs) String() string { // Apparmor log states diff --git a/pkg/logs/logs_test.go b/pkg/logs/logs_test.go index 69608d2b..c96167dc 100644 --- a/pkg/logs/logs_test.go +++ b/pkg/logs/logs_test.go @@ -261,3 +261,74 @@ func TestAppArmorLogs_String(t *testing.T) { }) } } + +func TestAppArmorLogs_Anonymize(t *testing.T) { + tests := []struct { + name string + aaLogs AppArmorLogs + want AppArmorLogs + }{ + { + name: "Anonymize Username", + aaLogs: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "foo", + "operation": "file_perm", + "name": "/home/foo/.bash_history", + "comm": "bash", + "requested_mask": "rw", + "denied_mask": "rw", + "parent": "16001", + }, + }, + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "foo", + "operation": "file_perm", + "name": "/home/AAD/.bash_history", + "comm": "bash", + "requested_mask": "rw", + "denied_mask": "rw", + "parent": "16001", + }, + }, + }, + { + name: "Anonymize UUID", + aaLogs: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "drkonqi", + "operation": "file_perm", + "name": "/sys/devices/pci0000:00/0000:00:02.0/drm/card1/metrics/399d3001-97d6-4240-b065-4fb843138e17/id", + "comm": "bash", + "requested_mask": "r", + "denied_mask": "r", + "parent": "16001", + }, + }, + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "drkonqi", + "operation": "file_perm", + "name": "/sys/devices/pci0000:00/0000:00:02.0/drm/card1/metrics/b08dfa60-83e7-567a-1921-a715000001fb/id", + "comm": "bash", + "requested_mask": "r", + "denied_mask": "r", + "parent": "16001", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.aaLogs.Anonymize() + if !reflect.DeepEqual(tt.aaLogs, tt.want) { + t.Errorf("Anonymize() = %v, want %v", tt.aaLogs, tt.want) + } + }) + } +}