mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2025-01-30 06:45:10 +01:00
feat(aa-log): add support dbus session log using journactl.
This commit is contained in:
parent
a2fa2421cb
commit
c0356e92e5
3 changed files with 212 additions and 11 deletions
|
@ -13,9 +13,30 @@ profile aa-log @{exec_path} {
|
||||||
|
|
||||||
@{exec_path} mr,
|
@{exec_path} mr,
|
||||||
|
|
||||||
|
/{usr/,}bin/journalctl rCx -> journalctl,
|
||||||
|
|
||||||
/var/log/audit/* r,
|
/var/log/audit/* r,
|
||||||
|
|
||||||
@{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r,
|
@{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r,
|
||||||
|
|
||||||
|
profile journalctl {
|
||||||
|
include <abstractions/base>
|
||||||
|
include <abstractions/consoles>
|
||||||
|
|
||||||
|
/{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 <local/aa-log>
|
include if exists <local/aa-log>
|
||||||
}
|
}
|
|
@ -1,16 +1,19 @@
|
||||||
// aa-log - Review AppArmor generated messages
|
// aa-log - Review AppArmor generated messages
|
||||||
// Copyright (C) 2021 Alexandre Pujol <alexandre@pujol.io>
|
// Copyright (C) 2021-2022 Alexandre Pujol <alexandre@pujol.io>
|
||||||
// SPDX-License-Identifier: GPL-2.0-only
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -18,8 +21,9 @@ import (
|
||||||
|
|
||||||
// Command line options
|
// Command line options
|
||||||
var (
|
var (
|
||||||
help bool
|
dbus bool
|
||||||
path string
|
help bool
|
||||||
|
path string
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogFile is the default path to the file to query
|
// 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
|
// AppArmorLogs describes all apparmor log entries
|
||||||
type AppArmorLogs []AppArmorLog
|
type AppArmorLogs []AppArmorLog
|
||||||
|
|
||||||
|
// SystemdLog is a simplified systemd json log representation.
|
||||||
|
type SystemdLog struct {
|
||||||
|
Message string `json:"MESSAGE"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
quoted bool
|
quoted bool
|
||||||
isHexa = regexp.MustCompile("^[0-9A-Fa-f]+$")
|
isHexa = regexp.MustCompile("^[0-9A-Fa-f]+$")
|
||||||
|
@ -84,6 +93,40 @@ func removeDuplicateLog(logs []string) []string {
|
||||||
return list
|
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
|
// NewApparmorLogs return a new ApparmorLogs list of map from a log file
|
||||||
func NewApparmorLogs(file io.Reader, profile string) AppArmorLogs {
|
func NewApparmorLogs(file io.Reader, profile string) AppArmorLogs {
|
||||||
log := ""
|
log := ""
|
||||||
|
@ -198,7 +241,7 @@ func (aaLogs AppArmorLogs) String() string {
|
||||||
return res
|
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))
|
file, err := os.Open(filepath.Clean(path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -210,21 +253,31 @@ func aaLog(path string, profile string) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
aaLogs := NewApparmorLogs(file, profile)
|
if dbus {
|
||||||
fmt.Print(aaLogs.String())
|
file, err := getJournalctlDbusSessionLogs(file, path != LogFile)
|
||||||
return err
|
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() {
|
func init() {
|
||||||
flag.BoolVar(&help, "h", false, "Show this help message and exit.")
|
flag.BoolVar(&help, "h", false, "Show this help message and exit.")
|
||||||
flag.StringVar(&path, "f", LogFile,
|
flag.StringVar(&path, "f", LogFile,
|
||||||
"Set a log`file` or a suffix to the default log file.")
|
"Set a log`file` or a suffix to the default log file.")
|
||||||
|
flag.BoolVar(&dbus, "d", false, "Show dbus session event.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if help {
|
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.
|
Review AppArmor generated messages in a colorful way.
|
||||||
It can be given an optional profile name to filter the output with.
|
It can be given an optional profile name to filter the output with.
|
||||||
|
@ -244,7 +297,7 @@ func main() {
|
||||||
logfile = path
|
logfile = path
|
||||||
}
|
}
|
||||||
|
|
||||||
err := aaLog(logfile, profile)
|
err := aaLog(logfile, profile, dbus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -153,6 +187,25 @@ func TestNewApparmorLogs(t *testing.T) {
|
||||||
path: "../../tests/audit.log",
|
path: "../../tests/audit.log",
|
||||||
want: refMan,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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) {
|
func TestAppArmorLogs_String(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -180,6 +278,25 @@ func TestAppArmorLogs_String(t *testing.T) {
|
||||||
aaLogs: refMan,
|
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",
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -195,24 +312,34 @@ func Test_app(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
profile string
|
profile string
|
||||||
|
dbus bool
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "OK",
|
name: "Test audit.log",
|
||||||
path: "../../tests/audit.log",
|
path: "../../tests/audit.log",
|
||||||
profile: "",
|
profile: "",
|
||||||
|
dbus: false,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Dbus Session",
|
||||||
|
path: "../../tests/systemd.log",
|
||||||
|
profile: "",
|
||||||
|
dbus: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "No logfile",
|
name: "No logfile",
|
||||||
path: "../../tests/log",
|
path: "../../tests/log",
|
||||||
profile: "",
|
profile: "",
|
||||||
|
dbus: false,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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)
|
t.Errorf("aaLog() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue