// apparmor.d - Full set of apparmor profiles // Copyright (C) 2021-2023 Alexandre Pujol // 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", }, &Include{ IfExists: true, IsMagic: true, Path: "local/foo", }, }, }, }, 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, 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) } }) } }