test(aa-log): add unit tests for profile rules.

This commit is contained in:
Alexandre Pujol 2023-09-25 00:22:41 +01:00
parent 43981517b2
commit a5b6373b02
No known key found for this signature in database
GPG Key ID: C5469996F0DF68EC
3 changed files with 647 additions and 1 deletions

268
pkg/aa/data_test.go Normal file
View File

@ -0,0 +1,268 @@
// apparmor.d - Full set of apparmor profiles
// Copyright (C) 2021-2023 Alexandre Pujol <alexandre@pujol.io>
// SPDX-License-Identifier: GPL-2.0-only
package aa
var (
// Capability
capability1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "cap",
"operation": "capable",
"capname": "net_admin",
"capability": "12",
"profile": "pkexec",
"comm": "pkexec",
}
capability1 = &Capability{
Qualifier: Qualifier{},
Name: "net_admin",
}
capability2 = &Capability{
Qualifier: Qualifier{},
Name: "sys_ptrace",
}
// Network
network1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "net",
"operation": "create",
"family": "netlink",
"profile": "sddm-greeter",
"sock_type": "raw",
"protocol": "15",
"requested_mask": "create",
"denied_mask": "create",
"comm": "sddm-greeter",
}
network1 = &Network{
Qualifier: Qualifier{},
Domain: "netlink",
Type: "raw",
Protocol: "15",
AddressExpr: AddressExpr{},
}
network2 = &Network{
Qualifier: Qualifier{},
Domain: "inet",
Type: "dgram",
Protocol: "",
AddressExpr: AddressExpr{},
}
// Mount
mount1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "mount",
"operation": "mount",
"info": "failed perms check",
"error": "-13",
"profile": "dockerd",
"name": "/var/lib/docker/overlay2/opaque-bug-check1209538631/merged/",
"comm": "dockerd",
"fstype": "overlay",
"srcname": "overlay",
}
mount2Log = map[string]string{
"apparmor": "ALLOWED",
"class": "mount",
"operation": "mount",
"info": "failed perms check",
"error": "-13",
"profile": "dockerd",
"name": "/var/lib/docker/overlay2/metacopy-check906831159/merged/",
"comm": "dockerd",
"fstype": "overlay",
"srcname": "overlay",
}
mount1 = &Mount{
Qualifier: Qualifier{},
MountConditions: MountConditions{
Fs: "",
Op: "",
FsType: "overlay",
Options: []string{},
},
Source: "overlay",
MountPoint: "/var/lib/docker/overlay2/opaque-bug-check1209538631/merged/",
}
mount2 = &Mount{
Qualifier: Qualifier{},
MountConditions: MountConditions{
Fs: "",
Op: "",
FsType: "overlay",
Options: []string{},
},
Source: "overlay",
MountPoint: "/var/lib/docker/overlay2/metacopy-check906831159/merged/",
}
// Signal
signal1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "signal",
"profile": "firefox",
"operation": "signal",
"comm": "49504320492F4F20506172656E74",
"requested_mask": "receive",
"denied_mask": "receive",
"signal": "kill",
"peer": "firefox//&firejail-default",
}
signal1 = &Signal{
Qualifier: Qualifier{},
Access: "receive",
Set: "kill",
Peer: "firefox//&firejail-default",
}
signal2 = &Signal{
Qualifier: Qualifier{},
Access: "receive",
Set: "up",
Peer: "firefox//&firejail-default",
}
// Ptrace
ptrace1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "ptrace",
"profile": "xdg-document-portal",
"operation": "ptrace",
"comm": "pool-/usr/lib/x",
"requested_mask": "read",
"denied_mask": "read",
"peer": "nautilus",
}
ptrace2Log = map[string]string{
"apparmor": "DENIED",
"class": "ptrace",
"operation": "ptrace",
"comm": "systemd-journal",
"requested_mask": "readby",
"denied_mask": "readby",
"peer": "systemd-journald",
}
ptrace1 = &Ptrace{
Qualifier: Qualifier{},
Access: "read",
Peer: "nautilus",
}
ptrace2 = &Ptrace{
Qualifier: Qualifier{},
Access: "readby",
Peer: "systemd-journald",
}
// Unix
unix1Log = map[string]string{
"apparmor": "ALLOWED",
"class": "net",
"family": "unix",
"operation": "file_perm",
"profile": "gsettings",
"comm": "dbus-daemon",
"requested_mask": "send receive",
"addr": "none",
"peer_addr": "@/tmp/dbus-AaKMpxzC4k",
"peer": "dbus-daemon",
"denied_mask": "send receive",
"sock_type": "stream",
"protocol": "0",
}
unix1 = &Unix{
Access: "send receive",
Type: "stream",
Protocol: "0",
Address: "none",
Peer: "dbus-daemon",
PeerAddr: "@/tmp/dbus-AaKMpxzC4k",
}
unix2 = &Unix{
Qualifier: Qualifier{FileInherit: true},
Access: "receive",
Type: "stream",
}
// Dbus
dbus1Log = map[string]string{
"apparmor": "ALLOWED",
"operation": "dbus_method_call",
"bus": "session",
"path": "/org/gtk/vfs/metadata",
"interface": "org.gtk.vfs.Metadata",
"member": "Remove",
"name": ":1.15",
"mask": "receive",
"label": "gvfsd-metadata",
"peer_pid": "3888",
"peer_label": "tracker-extract",
}
dbus2Log = map[string]string{
"apparmor": "ALLOWED",
"operation": "dbus_bind",
"bus": "session",
"name": "org.gnome.evolution.dataserver.Sources5",
"mask": "bind",
"pid": "3442",
"label": "evolution-source-registry",
}
dbus1 = &Dbus{
Access: "receive",
Bus: "session",
Name: ":1.15",
Path: "/org/gtk/vfs/metadata",
Interface: "org.gtk.vfs.Metadata",
Member: "Remove",
Label: "tracker-extract",
}
dbus2 = &Dbus{
Access: "bind",
Bus: "session",
Name: "org.gnome.evolution.dataserver.Sources5",
}
// File
file1Log = map[string]string{
"apparmor": "ALLOWED",
"operation": "open",
"class": "file",
"profile": "cupsd",
"name": "/usr/share/poppler/cMap/Identity-H",
"comm": "gs",
"requested_mask": "r",
"denied_mask": "r",
"fsuid": "209",
"FSUID": "cups",
"ouid": "0",
"OUID": "root",
}
file2Log = map[string]string{
"apparmor": "ALLOWED",
"operation": "open",
"class": "file",
"profile": "gsd-print-notifications",
"name": "/proc/4163/cgroup",
"comm": "gsd-print-notif",
"requested_mask": "r",
"denied_mask": "r",
"fsuid": "1000",
"FSUID": "user",
"ouid": "1000",
"OUID": "user",
"error": "-1",
}
file1 = &File{
Path: "/usr/share/poppler/cMap/Identity-H",
Access: "r",
Target: "",
}
file2 = &File{
Qualifier: Qualifier{Owner: true, NoNewPrivs: true},
Path: "/proc/4163/cgroup",
Access: "r",
Target: "",
}
)

View File

@ -25,7 +25,160 @@ func TestAppArmorProfile_String(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.p.String(); got != tt.want {
t.Errorf("AppArmorProfile.String() = %v, want %v", got, tt.want)
t.Errorf("AppArmorProfile.String() = |%v|, want |%v|", got, tt.want)
}
})
}
}
func TestAppArmorProfile_AddRule(t *testing.T) {
tests := []struct {
name string
log map[string]string
want *AppArmorProfile
}{
{
name: "capability",
log: capability1Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{capability1},
},
},
},
{
name: "network",
log: network1Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{network1},
},
},
},
{
name: "mount",
log: mount2Log,
want: &AppArmorProfile{
Profile: Profile{
Flags: []string{"attach_disconnected"},
Rules: []ApparmorRule{mount2},
},
},
},
{
name: "signal",
log: signal1Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{signal1},
},
},
},
{
name: "ptrace",
log: ptrace2Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{ptrace2},
},
},
},
{
name: "unix",
log: unix1Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{unix1},
},
},
},
{
name: "dbus",
log: dbus2Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{dbus2},
},
},
},
{
name: "file",
log: file2Log,
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{file2},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewAppArmorProfile()
got.AddRule(tt.log)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AppArmorProfile.AddRule() = %v, want %v", got, tt.want)
}
})
}
}
func TestAppArmorProfile_Sort(t *testing.T) {
tests := []struct {
name string
origin *AppArmorProfile
want *AppArmorProfile
}{
{
name: "all",
origin: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{file2, network1, dbus2, signal1, ptrace1, capability2, file1, dbus1, unix2, signal2, mount2},
},
},
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{capability2, network1, mount2, signal1, signal2, ptrace1, unix2, dbus2, dbus1, file2, file1},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.origin
got.Sort()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AppArmorProfile.Sort() = %v, want %v", got, tt.want)
}
})
}
}
func TestAppArmorProfile_MergeRules(t *testing.T) {
tests := []struct {
name string
origin *AppArmorProfile
want *AppArmorProfile
}{
{
name: "all",
origin: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{capability1, capability1, network1, network1, file1, file1},
},
},
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{capability1, network1, file1},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.origin
got.MergeRules()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AppArmorProfile.MergeRules() = %v, want %v", got, tt.want)
}
})
}

225
pkg/aa/rules_test.go Normal file
View File

@ -0,0 +1,225 @@
// apparmor.d - Full set of apparmor profiles
// Copyright (C) 2021-2023 Alexandre Pujol <alexandre@pujol.io>
// SPDX-License-Identifier: GPL-2.0-only
package aa
import (
"reflect"
"testing"
)
func TestRule_FromLog(t *testing.T) {
tests := []struct {
name string
fromLog func(map[string]string, bool, bool) ApparmorRule
log map[string]string
want ApparmorRule
}{
{
name: "capbability",
fromLog: CapabilityFromLog,
log: capability1Log,
want: capability1,
},
{
name: "network",
fromLog: NetworkFromLog,
log: network1Log,
want: network1,
},
{
name: "mount",
fromLog: MountFromLog,
log: mount1Log,
want: mount1,
},
{
name: "signal",
fromLog: SignalFromLog,
log: signal1Log,
want: signal1,
},
{
name: "ptrace/xdg-document-portal",
fromLog: PtraceFromLog,
log: ptrace1Log,
want: ptrace1,
},
{
name: "ptrace/snap-update-ns.firefox",
fromLog: PtraceFromLog,
log: ptrace2Log,
want: ptrace2,
},
{
name: "unix",
fromLog: UnixFromLog,
log: unix1Log,
want: unix1,
},
{
name: "dbus",
fromLog: DbusFromLog,
log: dbus1Log,
want: dbus1,
},
{
name: "file",
fromLog: FileFromLog,
log: file1Log,
want: file1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.fromLog(tt.log, false, false); !reflect.DeepEqual(got, tt.want) {
t.Errorf("RuleFromLog() = %v, want %v", got, tt.want)
}
})
}
}
func TestRule_Less(t *testing.T) {
tests := []struct {
name string
rule ApparmorRule
other ApparmorRule
want bool
}{
{
name: "capability",
rule: capability1,
other: capability2,
want: true,
},
{
name: "network",
rule: network1,
other: network2,
want: false,
},
{
name: "mount",
rule: mount1,
other: mount2,
want: false,
},
{
name: "signal",
rule: signal1,
other: signal2,
want: true,
},
{
name: "ptrace/less",
rule: ptrace1,
other: ptrace2,
want: true,
},
{
name: "ptrace/more",
rule: ptrace2,
other: ptrace1,
want: false,
},
{
name: "unix",
rule: unix1,
other: unix1,
want: false,
},
{
name: "dbus",
rule: dbus1,
other: dbus1,
want: false,
},
{
name: "file",
rule: file1,
other: file2,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.rule
if got := r.Less(tt.other); got != tt.want {
t.Errorf("Rule.Less() = %v, want %v", got, tt.want)
}
})
}
}
func TestRule_Equals(t *testing.T) {
tests := []struct {
name string
rule ApparmorRule
other ApparmorRule
want bool
}{
{
name: "capability/equal",
rule: capability1,
other: capability1,
want: true,
},
{
name: "network/equal",
rule: network1,
other: network1,
want: true,
},
{
name: "mount",
rule: mount1,
other: mount1,
want: true,
},
{
name: "signal1/equal",
rule: signal1,
other: signal1,
want: true,
},
{
name: "ptrace/equal",
rule: ptrace1,
other: ptrace1,
want: true,
},
{
name: "ptrace/not_equal",
rule: ptrace1,
other: ptrace2,
want: false,
},
{
name: "unix",
rule: unix1,
other: unix1,
want: true,
},
{
name: "dbus",
rule: dbus1,
other: dbus2,
want: false,
},
{
name: "file",
rule: file2,
other: file2,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := tt.rule
if got := r.Equals(tt.other); got != tt.want {
t.Errorf("Rule.Equals() = %v, want %v", got, tt.want)
}
})
}
}