apparmor.d/pkg/aa/profile_test.go
2023-10-01 19:03:12 +01:00

385 lines
8.4 KiB
Go

// 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"
"strings"
"testing"
"github.com/arduino/go-paths-helper"
)
func readprofile(path string) string {
file := paths.New("../../").Join(path)
lines, err := file.ReadFileAsLines()
if err != nil {
panic(err)
}
res := ""
for _, line := range lines {
if strings.HasPrefix(line, "#") {
continue
}
res += line + "\n"
}
return res[:len(res)-1]
}
func TestAppArmorProfile_String(t *testing.T) {
tests := []struct {
name string
p *AppArmorProfile
want string
}{
{
name: "empty",
p: &AppArmorProfile{},
want: `profile {
}
`,
},
{
name: "foo",
p: &AppArmorProfile{
Preamble: Preamble{
Abi: []Abi{
{
IsMagic: true,
Path: "abi/4.0",
},
},
Includes: []Include{
{
IsMagic: true,
Path: "tunables/global",
},
},
Aliases: []Alias{
{
Path: "/mnt/usr",
RewrittenPath: "/usr",
},
},
Variables: []Variable{
{
Name: "exec_path",
Values: []string{"@{bin}/foo", "@{lib}/foo"},
},
},
},
Profile: Profile{
Name: "foo",
Attachments: []string{"@{exec_path}"},
Attributes: map[string]string{"security.tagged": "allowed"},
Flags: []string{"complain", "attach_disconnected"},
Rules: []ApparmorRule{
&Include{
IsMagic: true,
Path: "abstractions/base",
},
&Include{
IsMagic: true,
Path: "abstractions/nameservice-strict",
},
&Rlimit{
Key: "nproc",
Op: "<=",
Value: "200",
},
&Capability{Name: "dac_read_search"},
&Capability{Name: "dac_override"},
&Network{
Domain: "inet",
Type: "stream",
},
&Network{
Domain: "inet6",
Type: "stream",
},
&Mount{
MountConditions: MountConditions{
FsType: "fuse.portal",
Options: []string{"rw", "rbind"},
},
Source: "@{run}/user/@{uid}/ ",
MountPoint: "/",
},
&Umount{
MountConditions: MountConditions{},
MountPoint: "@{run}/user/@{uid}/",
},
&Signal{
Access: "receive",
Set: "term",
Peer: "at-spi-bus-launcher",
},
&Ptrace{
Access: "read",
Peer: "nautilus",
},
&Unix{
Access: "send receive",
Type: "stream",
Address: "@/tmp/.ICE-unix/1995",
Peer: "gnome-shell",
PeerAddr: "none",
},
&Dbus{
Access: "bind",
Bus: "session",
Name: "org.gnome.*",
},
&Dbus{
Access: "receive",
Bus: "system",
Name: ":1.3",
Path: "/org/freedesktop/DBus",
Interface: "org.freedesktop.DBus",
Member: "AddMatch",
Label: "power-profiles-daemon",
},
&File{
Path: "/opt/intel/oneapi/compiler/*/linux/lib/*.so./*",
Access: "rm",
},
&File{
Path: "@{PROC}/@{pid}/task/@{tid}/comm",
Access: "rw",
},
&File{
Path: "@{sys}/devices/pci[0-9]*/**/class",
Access: "r",
},
includeLocal1,
},
},
},
want: readprofile("tests/string.aa"),
},
}
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)
}
})
}
}
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, includeLocal1, dbus2, signal1, ptrace1,
capability2, file1, dbus1, unix2, signal2, mount2,
},
},
},
want: &AppArmorProfile{
Profile: Profile{
Rules: []ApparmorRule{
capability2, network1, mount2, signal1, signal2, ptrace1,
unix2, dbus2, dbus1, file1, file2, includeLocal1,
},
},
},
},
}
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)
}
})
}
}
func TestAppArmorProfile_Integration(t *testing.T) {
tests := []struct {
name string
p *AppArmorProfile
want string
}{
{
name: "aa-status",
p: &AppArmorProfile{
Preamble: Preamble{
Abi: []Abi{{IsMagic: true, Path: "abi/3.0"}},
Includes: []Include{{IsMagic: true, Path: "tunables/global"}},
Variables: []Variable{{
Name: "exec_path",
Values: []string{"@{bin}/aa-status", "@{bin}/apparmor_status"},
}},
},
Profile: Profile{
Name: "aa-status",
Attachments: []string{"@{exec_path}"},
Rules: Rules{
&Include{IfExists: true, IsMagic: true, Path: "local/aa-status"},
&Capability{Name: "dac_read_search"},
&File{Path: "@{exec_path}", Access: "mr"},
&File{Path: "@{PROC}/@{pids}/attr/apparmor/current", Access: "r"},
&File{Path: "@{PROC}/", Access: "r"},
&File{Path: "@{sys}/module/apparmor/parameters/enabled", Access: "r"},
&File{Path: "@{sys}/kernel/security/apparmor/profiles", Access: "r"},
&File{Path: "@{PROC}/@{pids}/attr/current", Access: "r"},
&Include{IsMagic: true, Path: "abstractions/consoles"},
&File{Qualifier: Qualifier{Owner: true}, Path: "@{PROC}/@{pid}/mounts", Access: "r"},
&Include{IsMagic: true, Path: "abstractions/base"},
&File{Path: "/dev/tty@{int}", Access: "rw"},
&Capability{Name: "sys_ptrace"},
&Ptrace{Access: "read"},
},
},
},
want: readprofile("apparmor.d/profiles-a-f/aa-status") + "\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.p.Sort()
tt.p.MergeRules()
tt.p.Format()
if got := tt.p.String(); "\n"+got != tt.want {
t.Errorf("AppArmorProfile = |%v|, want |%v|", "\n"+got, tt.want)
}
})
}
}