apparmor.d/pkg/aa/apparmor_test.go

259 lines
7.7 KiB
Go
Raw Normal View History

2023-08-18 00:36:46 +02:00
// apparmor.d - Full set of apparmor profiles
// Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>
2023-08-18 00:36:46 +02:00
// SPDX-License-Identifier: GPL-2.0-only
package aa
import (
"reflect"
2024-06-15 23:28:37 +02:00
"strings"
2023-08-18 00:36:46 +02:00
"testing"
"github.com/roddhjav/apparmor.d/pkg/paths"
"github.com/roddhjav/apparmor.d/pkg/util"
2023-08-18 00:36:46 +02:00
)
var (
testData = paths.New("../../tests/testdata/")
intData = paths.New("../../apparmor.d")
)
2024-06-15 23:28:37 +02:00
// mustReadProfileFile read a file and return its content as a slice of string.
// It panics if an error occurs. It removes the last comment line.
func mustReadProfileFile(path *paths.Path) string {
res := strings.Split(util.MustReadFile(path), "\n")
return strings.Join(res[:len(res)-2], "\n")
}
2024-04-23 22:35:23 +02:00
func TestAppArmorProfileFile_String(t *testing.T) {
2023-08-18 00:36:46 +02:00
tests := []struct {
name string
f *AppArmorProfileFile
2023-08-18 00:36:46 +02:00
want string
}{
{
name: "empty",
f: &AppArmorProfileFile{},
want: ``,
2023-08-18 00:36:46 +02:00
},
{
name: "foo",
f: &AppArmorProfileFile{
Preamble: Rules{
&Comment{Base: Base{Comment: " Simple test profile for the AppArmorProfileFile.String() method", IsLineRule: true}},
nil,
&Abi{IsMagic: true, Path: "abi/4.0"},
&Alias{Path: "/mnt/usr", RewrittenPath: "/usr"},
&Include{IsMagic: true, Path: "tunables/global"},
&Variable{
Name: "exec_path", Define: true,
2023-10-01 20:04:43 +02:00
Values: []string{"@{bin}/foo", "@{lib}/foo"},
},
},
Profiles: []*Profile{{
Header: Header{
Name: "foo",
Attachments: []string{"@{exec_path}"},
Attributes: map[string]string{"security.tagged": "allowed"},
Flags: []string{"complain", "attach_disconnected"},
},
2024-05-05 00:45:36 +02:00
Rules: Rules{
2023-10-01 20:04:43 +02:00
&Include{IsMagic: true, Path: "abstractions/base"},
&Include{IsMagic: true, Path: "abstractions/nameservice-strict"},
rlimit1,
&Capability{Names: []string{"dac_read_search"}},
&Capability{Names: []string{"dac_override"}},
2023-10-01 20:04:43 +02:00
&Network{Domain: "inet", Type: "stream"},
&Network{Domain: "inet6", Type: "stream"},
&Mount{
Base: Base{Comment: " failed perms check"},
MountConditions: MountConditions{
FsType: "fuse.portal",
Options: []string{"rw", "rbind"},
},
Source: "@{run}/user/@{uid}/",
MountPoint: "/",
},
&Umount{
MountConditions: MountConditions{},
MountPoint: "@{run}/user/@{uid}/",
},
&Signal{
Access: []string{"receive"},
Set: []string{"term"},
Peer: "at-spi-bus-launcher",
},
&Ptrace{Access: []string{"read"}, Peer: "nautilus"},
&Unix{
Access: []string{"send", "receive"},
2024-04-15 00:58:34 +02:00
Type: "stream",
Address: "@/tmp/.ICE-unix/1995",
PeerLabel: "gnome-shell",
PeerAddr: "none",
},
2024-05-05 00:45:36 +02:00
&Dbus{Access: []string{"bind"}, Bus: "session", Name: "org.gnome.*"},
&Dbus{
Access: []string{"receive"},
Bus: "system",
Path: "/org/freedesktop/DBus",
Interface: "org.freedesktop.DBus",
Member: "AddMatch",
2024-04-15 00:58:34 +02:00
PeerName: ":1.3",
PeerLabel: "power-profiles-daemon",
},
&File{Path: "/opt/intel/oneapi/compiler/*/linux/lib/*.so./*", Access: []string{"r", "m"}},
&File{Path: "@{PROC}/@{pid}/task/@{tid}/comm", Access: []string{"r", "w"}},
&File{Path: "@{sys}/devices/@{pci}/class", Access: []string{"r"}},
2023-09-30 14:55:56 +02:00
includeLocal1,
},
}},
},
want: util.MustReadFile(testData.Join("string.aa")),
},
2023-08-18 00:36:46 +02:00
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.f.String(); got != tt.want {
t.Errorf("AppArmorProfile.String() = |%v|, want |%v|", got, tt.want)
}
})
}
}
2024-04-23 22:35:23 +02:00
func TestAppArmorProfileFile_Sort(t *testing.T) {
tests := []struct {
name string
origin *AppArmorProfileFile
want *AppArmorProfileFile
}{
{
name: "all",
origin: &AppArmorProfileFile{
Profiles: []*Profile{{
Rules: []Rule{
2024-05-29 22:18:30 +02:00
file2, network1, userns1, include1, dbus2, signal1,
ptrace1, includeLocal1, rlimit3, capability1, network2,
mqueue2, iouring2, dbus1, link2, capability2, file1,
unix2, signal2, mount2, all1, umount2, mount1, remount2,
pivotroot1, changeprofile2,
2023-09-30 14:55:56 +02:00
},
}},
},
want: &AppArmorProfileFile{
Profiles: []*Profile{{
Rules: []Rule{
2024-05-29 22:18:30 +02:00
include1, all1, rlimit3, userns1, capability1, capability2,
network2, network1, mount2, mount1, remount2, umount2,
2024-06-20 00:30:36 +02:00
pivotroot1, changeprofile2, mqueue2, iouring2, signal1,
signal2, ptrace1, unix2, dbus2, dbus1, file1, file2,
2024-05-29 22:18:30 +02:00
link2, includeLocal1,
2023-09-30 14:55:56 +02:00
},
}},
},
},
}
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)
}
})
}
}
2024-04-23 22:35:23 +02:00
func TestAppArmorProfileFile_MergeRules(t *testing.T) {
tests := []struct {
name string
origin *AppArmorProfileFile
want *AppArmorProfileFile
}{
{
name: "all",
origin: &AppArmorProfileFile{
Profiles: []*Profile{{
Rules: []Rule{capability1, capability1, network1, network1, file1, file1},
}},
},
want: &AppArmorProfileFile{
Profiles: []*Profile{{
Rules: []Rule{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)
2023-08-18 00:36:46 +02:00
}
})
}
}
2024-04-23 22:35:23 +02:00
func TestAppArmorProfileFile_Integration(t *testing.T) {
tests := []struct {
name string
f *AppArmorProfileFile
want string
}{
{
name: "aa-status",
f: &AppArmorProfileFile{
Preamble: Rules{
&Comment{Base: Base{Comment: " apparmor.d - Full set of apparmor profiles", IsLineRule: true}},
&Comment{Base: Base{Comment: " Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>", IsLineRule: true}},
&Comment{Base: Base{Comment: " SPDX-License-Identifier: GPL-2.0-only", IsLineRule: true}},
nil,
&Abi{IsMagic: true, Path: "abi/4.0"},
&Include{IsMagic: true, Path: "tunables/global"},
&Variable{
Name: "exec_path", Define: true,
Values: []string{"@{bin}/aa-status", "@{bin}/apparmor_status"},
},
},
Profiles: []*Profile{{
Header: Header{
Name: "aa-status",
Attachments: []string{"@{exec_path}"},
},
Rules: Rules{
&Include{IfExists: true, IsMagic: true, Path: "local/aa-status"},
&Capability{Names: []string{"dac_read_search"}},
&File{Path: "@{exec_path}", Access: []string{"m", "r"}},
&File{Path: "@{PROC}/@{pids}/attr/apparmor/current", Access: []string{"r"}},
&File{Path: "@{PROC}/", Access: []string{"r"}},
&File{Path: "@{sys}/module/apparmor/parameters/enabled", Access: []string{"r"}},
&File{Path: "@{sys}/kernel/security/apparmor/profiles", Access: []string{"r"}},
&File{Path: "@{PROC}/@{pids}/attr/current", Access: []string{"r"}},
&Include{IsMagic: true, Path: "abstractions/consoles"},
&File{Owner: true, Path: "@{PROC}/@{pid}/mounts", Access: []string{"r"}},
&Include{IsMagic: true, Path: "abstractions/base"},
&File{Path: "/dev/tty@{int}", Access: []string{"r", "w"}},
&Capability{Names: []string{"sys_ptrace"}},
&Ptrace{Access: []string{"read"}},
},
}},
},
2024-06-15 23:28:37 +02:00
want: mustReadProfileFile(intData.Join("profiles-a-f/aa-status")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.f.Sort()
tt.f.MergeRules()
tt.f.Format()
2024-05-29 22:18:30 +02:00
err := tt.f.Validate()
if err != nil {
t.Errorf("AppArmorProfile.Validate() = %v", err)
}
if got := tt.f.String(); got != tt.want {
t.Errorf("AppArmorProfile = |%v|, want |%v|", got, tt.want)
}
})
}
}