feat(aa-log): add -a option to anonymize the logs.

This commit is contained in:
Alexandre Pujol 2023-05-06 12:18:20 +01:00
parent 26bd9350f2
commit 538da05696
Failed to generate hash of commit
4 changed files with 117 additions and 7 deletions

View file

@ -11,7 +11,7 @@ import (
"os" "os"
"github.com/roddhjav/apparmor.d/pkg/logs" "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] const usage = `aa-log [-h] [--systemd] [--file file] [profile]
@ -28,17 +28,19 @@ Options:
-h, --help Show this help message and exit. -h, --help Show this help message and exit.
-f, --file FILE Set a logfile or a suffix to the default log file. -f, --file FILE Set a logfile or a suffix to the default log file.
-s, --systemd Parse systemd logs from journalctl. -s, --systemd Parse systemd logs from journalctl.
-a, --anonymize Anonymize the logs.
` `
// Command line options // Command line options
var ( var (
help bool help bool
path string anonymize bool
systemd 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 err error
var file io.Reader var file io.Reader
@ -54,6 +56,9 @@ func aaLog(logger string, path string, profile string) error {
return err return err
} }
aaLogs := logs.NewApparmorLogs(file, profile) aaLogs := logs.NewApparmorLogs(file, profile)
if anonymize {
aaLogs.Anonymize()
}
fmt.Print(aaLogs.String()) fmt.Print(aaLogs.String())
return nil return nil
} }
@ -65,6 +70,8 @@ func init() {
flag.StringVar(&path, "file", "", "Set a logfile or a suffix to the default log file.") 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, "s", false, "Parse systemd logs from journalctl.")
flag.BoolVar(&systemd, "systemd", 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() { func main() {
@ -86,7 +93,7 @@ func main() {
} }
logfile := logs.GetLogFile(path) logfile := logs.GetLogFile(path)
err := aaLog(logger, logfile, profile) err := aaLog(logger, logfile, profile, anonymize)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View file

@ -14,6 +14,7 @@ func Test_app(t *testing.T) {
logger string logger string
path string path string
profile string profile string
anon bool
wantErr bool wantErr bool
}{ }{
{ {
@ -21,6 +22,7 @@ func Test_app(t *testing.T) {
logger: "auditd", logger: "auditd",
path: "../../tests/audit.log", path: "../../tests/audit.log",
profile: "", profile: "",
anon: true,
wantErr: false, wantErr: false,
}, },
{ {
@ -28,6 +30,7 @@ func Test_app(t *testing.T) {
logger: "systemd", logger: "systemd",
path: "../../tests/systemd.log", path: "../../tests/systemd.log",
profile: "", profile: "",
anon: false,
wantErr: false, wantErr: false,
}, },
{ {
@ -35,6 +38,7 @@ func Test_app(t *testing.T) {
logger: "auditd", logger: "auditd",
path: "../../tests/log", path: "../../tests/log",
profile: "", profile: "",
anon: false,
wantErr: true, wantErr: true,
}, },
{ {
@ -42,12 +46,13 @@ func Test_app(t *testing.T) {
logger: "raw", logger: "raw",
path: "../../tests/audit.log", path: "../../tests/audit.log",
profile: "", profile: "",
anon: 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.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) t.Errorf("aaLog() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })

View file

@ -8,6 +8,7 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"os/user"
"regexp" "regexp"
"strings" "strings"
@ -28,6 +29,9 @@ const (
boldYellow = "\033[1;33m" boldYellow = "\033[1;33m"
) )
// Anonymized username
const Username = "AAD"
var ( var (
quoted bool quoted bool
isAppArmorLogTemplate = regexp.MustCompile(`apparmor=("DENIED"|"ALLOWED"|"AUDIT")`) isAppArmorLogTemplate = regexp.MustCompile(`apparmor=("DENIED"|"ALLOWED"|"AUDIT")`)
@ -116,6 +120,29 @@ func NewApparmorLogs(file io.Reader, profile string) AppArmorLogs {
return aaLogs 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 // String returns a formatted AppArmor logs string
func (aaLogs AppArmorLogs) String() string { func (aaLogs AppArmorLogs) String() string {
// Apparmor log states // Apparmor log states

View file

@ -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)
}
})
}
}