From c0356e92e5106085edd1feec334b38eb6234f133 Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Fri, 19 Aug 2022 19:05:46 +0100 Subject: [PATCH] feat(aa-log): add support dbus session log using journactl. --- apparmor.d/profiles-a-f/aa-log | 21 ++++++ cmd/aa-log/main.go | 71 +++++++++++++++--- cmd/aa-log/main_test.go | 131 ++++++++++++++++++++++++++++++++- 3 files changed, 212 insertions(+), 11 deletions(-) diff --git a/apparmor.d/profiles-a-f/aa-log b/apparmor.d/profiles-a-f/aa-log index ef93fd235..103c4ed96 100644 --- a/apparmor.d/profiles-a-f/aa-log +++ b/apparmor.d/profiles-a-f/aa-log @@ -13,9 +13,30 @@ profile aa-log @{exec_path} { @{exec_path} mr, + /{usr/,}bin/journalctl rCx -> journalctl, + /var/log/audit/* r, @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r, + profile journalctl { + include + include + + /{usr/,}bin/journalctl mr, + + /var/lib/dbus/machine-id r, + /etc/machine-id r, + + /{run,var}/log/journal/ r, + /{run,var}/log/journal/[0-9a-f]*/ r, + /{run,var}/log/journal/[0-9a-f]*/user-@{uid}*.journal* r, + /{run,var}/log/journal/[0-9a-f]*/user-@{uid}.journal r, + + @{PROC}/sys/kernel/random/boot_id r, + @{PROC}/sys/kernel/cap_last_cap r, + + } + include if exists } \ No newline at end of file diff --git a/cmd/aa-log/main.go b/cmd/aa-log/main.go index aa47d9c00..9059b298c 100644 --- a/cmd/aa-log/main.go +++ b/cmd/aa-log/main.go @@ -1,16 +1,19 @@ // aa-log - Review AppArmor generated messages -// Copyright (C) 2021 Alexandre Pujol +// Copyright (C) 2021-2022 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only package main import ( "bufio" + "bytes" "encoding/hex" + "encoding/json" "flag" "fmt" "io" "os" + "os/exec" "path/filepath" "regexp" "strings" @@ -18,8 +21,9 @@ import ( // Command line options var ( - help bool - path string + dbus bool + help bool + path string ) // LogFile is the default path to the file to query @@ -45,6 +49,11 @@ type AppArmorLog map[string]string // AppArmorLogs describes all apparmor log entries type AppArmorLogs []AppArmorLog +// SystemdLog is a simplified systemd json log representation. +type SystemdLog struct { + Message string `json:"MESSAGE"` +} + var ( quoted bool isHexa = regexp.MustCompile("^[0-9A-Fa-f]+$") @@ -84,6 +93,40 @@ func removeDuplicateLog(logs []string) []string { return list } +// getJournalctlDbusSessionLogs return a reader with the logs entries +func getJournalctlDbusSessionLogs(file io.Reader, useFile bool) (io.Reader, error) { + var logs []SystemdLog + var stdout bytes.Buffer + var value string + + if useFile { + content, err := io.ReadAll(file) + if err != nil { + return nil, err + } + value = string(content) + } else { + cmd := exec.Command("journalctl", "--user", "-b", "-u", "dbus.service", "-o", "json") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return nil, err + } + value = stdout.String() + } + + value = strings.Replace(value, "\n", ",\n", -1) + value = strings.TrimSuffix(value, ",\n") + value = `[` + value + `]` + if err := json.Unmarshal([]byte(value), &logs); err != nil { + return nil, err + } + res := "" + for _, log := range logs { + res += log.Message + "\n" + } + return strings.NewReader(res), nil +} + // NewApparmorLogs return a new ApparmorLogs list of map from a log file func NewApparmorLogs(file io.Reader, profile string) AppArmorLogs { log := "" @@ -198,7 +241,7 @@ func (aaLogs AppArmorLogs) String() string { return res } -func aaLog(path string, profile string) error { +func aaLog(path string, profile string, dbus bool) error { file, err := os.Open(filepath.Clean(path)) if err != nil { return err @@ -210,21 +253,31 @@ func aaLog(path string, profile string) error { } }() - aaLogs := NewApparmorLogs(file, profile) - fmt.Print(aaLogs.String()) - return err + if dbus { + file, err := getJournalctlDbusSessionLogs(file, path != LogFile) + if err != nil { + return err + } + aaLogs := NewApparmorLogs(file, profile) + fmt.Print(aaLogs.String()) + } else { + aaLogs := NewApparmorLogs(file, profile) + fmt.Print(aaLogs.String()) + } + return nil } func init() { flag.BoolVar(&help, "h", false, "Show this help message and exit.") flag.StringVar(&path, "f", LogFile, "Set a log`file` or a suffix to the default log file.") + flag.BoolVar(&dbus, "d", false, "Show dbus session event.") } func main() { flag.Parse() if help { - fmt.Printf(`aa-log [-h] [-f file] [profile] + fmt.Printf(`aa-log [-h] [-d] [-f file] [profile] Review AppArmor generated messages in a colorful way. It can be given an optional profile name to filter the output with. @@ -244,7 +297,7 @@ func main() { logfile = path } - err := aaLog(logfile, profile) + err := aaLog(logfile, profile, dbus) 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 2ddca15d2..7e6dd0aa2 100644 --- a/cmd/aa-log/main_test.go +++ b/cmd/aa-log/main_test.go @@ -93,6 +93,40 @@ func TestAppArmorEvents(t *testing.T) { }, }, }, + { + name: "dbus system", + event: `type=USER_AVC msg=audit(1111111111.111:1111): pid=1780 uid=102 auid=4294967295 ses=4294967295 subj=? msg='apparmor="ALLOWED" operation="dbus_method_call" bus="system" path="/org/freedesktop/PolicyKit1/Authority" interface="org.freedesktop.PolicyKit1.Authority" member="CheckAuthorization" mask="send" name="org.freedesktop.PolicyKit1" pid=1794 label="snapd" peer_pid=1790 peer_label="polkitd" exe="/usr/bin/dbus-daemon" sauid=102 hostname=? addr=? terminal=?'UID="messagebus" AUID="unset" SAUID="messagebus"`, + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "", + "label": "snapd", + "operation": "dbus_method_call", + "name": "org.freedesktop.PolicyKit1", + "mask": "send", + "bus": "system", + "path": "/org/freedesktop/PolicyKit1/Authority", + "interface": "org.freedesktop.PolicyKit1.Authority", + "member": "CheckAuthorization", + "peer_label": "polkitd", + }, + }, + }, + { + name: "dbus session", + event: `apparmor="ALLOWED" operation="dbus_bind" bus="session" name="org.freedesktop.portal.Documents" mask="bind" pid=2174 label="xdg-document-portal"`, + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "", + "label": "xdg-document-portal", + "operation": "dbus_bind", + "name": "org.freedesktop.portal.Documents", + "mask": "bind", + "bus": "session", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -153,6 +187,25 @@ func TestNewApparmorLogs(t *testing.T) { path: "../../tests/audit.log", want: refMan, }, + { + name: "power-profiles-daemon", + path: "../../tests/audit.log", + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "", + "label": "power-profiles-daemon", + "operation": "dbus_method_call", + "name": "org.freedesktop.DBus", + "mask": "send", + "bus": "system", + "path": "/org/freedesktop/DBus", + "interface": "org.freedesktop.DBus", + "member": "AddMatch", + "peer_label": "dbus-daemon", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -164,6 +217,51 @@ func TestNewApparmorLogs(t *testing.T) { } } +func Test_getJournalctlDbusSessionLogs(t *testing.T) { + tests := []struct { + name string + path string + useFile bool + want AppArmorLogs + }{ + { + name: "gsd-xsettings", + useFile: true, + path: "../../tests/systemd.log", + want: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "", + "label": "gsd-xsettings", + "operation": "dbus_method_call", + "name": ":1.88", + "mask": "receive", + "bus": "session", + "path": "/org/gtk/Settings", + "interface": "org.freedesktop.DBus.Properties", + "member": "GetAll", + "peer_label": "gnome-extension-ding", + }, + }, + }, + { + name: "journalctl", + useFile: false, + path: "", + want: AppArmorLogs{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + file, _ := os.Open(tt.path) + reader, _ := getJournalctlDbusSessionLogs(file, tt.useFile) + if got := NewApparmorLogs(reader, tt.name); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewApparmorLogs() = %v, want %v", got, tt.want) + } + }) + } +} + func TestAppArmorLogs_String(t *testing.T) { tests := []struct { name string @@ -180,6 +278,25 @@ func TestAppArmorLogs_String(t *testing.T) { aaLogs: refMan, want: "\033[1;32mALLOWED\033[0m \033[34mman\033[0m \033[33mexec\033[0m \033[35m/usr/bin/preconv\033[0m info=\"no new privs\" comm=man requested_mask=\033[1;31mx\033[0m denied_mask=\033[1;31mx\033[0m error=-1\n", }, + { + name: "power-profiles-daemon", + aaLogs: AppArmorLogs{ + { + "apparmor": "ALLOWED", + "profile": "", + "label": "power-profiles-daemon", + "operation": "dbus_method_call", + "name": "org.freedesktop.DBus", + "mask": "send", + "bus": "system", + "path": "/org/freedesktop/DBus", + "interface": "org.freedesktop.DBus", + "member": "AddMatch", + "peer_label": "dbus-daemon", + }, + }, + want: "\033[1;32mALLOWED\033[0m \033[34mpower-profiles-daemon\033[0m \033[33mdbus_method_call\033[0m \033[35morg.freedesktop.DBus\033[0m \033[1;31msend\033[0m \033[36mbus=system\033[0m path=\033[37m/org/freedesktop/DBus\033[0m interface=\033[37morg.freedesktop.DBus\033[0m member=\033[32mAddMatch\033[0m peer_label=dbus-daemon\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -195,24 +312,34 @@ func Test_app(t *testing.T) { name string path string profile string + dbus bool wantErr bool }{ { - name: "OK", + name: "Test audit.log", path: "../../tests/audit.log", profile: "", + dbus: false, + wantErr: false, + }, + { + name: "Test Dbus Session", + path: "../../tests/systemd.log", + profile: "", + dbus: true, wantErr: false, }, { name: "No logfile", path: "../../tests/log", profile: "", + dbus: false, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := aaLog(tt.path, tt.profile); (err != nil) != tt.wantErr { + if err := aaLog(tt.path, tt.profile, tt.dbus); (err != nil) != tt.wantErr { t.Errorf("aaLog() error = %v, wantErr %v", err, tt.wantErr) } })