From 4b753210e72daeeb3e84ca1a9059d6c03f916c0b Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Mon, 15 Apr 2024 14:09:04 +0100 Subject: [PATCH] feat(aa): modify the apparmor struct to support multiple profiles and subprofile. --- pkg/aa/profile.go | 143 ++++++++++------ pkg/aa/profile_test.go | 72 ++++---- pkg/aa/rlimit.go | 1 + pkg/aa/template.go | 1 + pkg/aa/templates/file.j2 | 284 +----------------------------- pkg/aa/templates/profile.j2 | 303 +++++++++++++++++++++++++++++++++ pkg/aa/variables.go | 11 +- pkg/aa/variables_test.go | 8 +- pkg/logs/logs.go | 5 +- pkg/logs/logs_test.go | 12 +- pkg/prebuild/directive/dbus.go | 10 +- pkg/prebuild/directive/exec.go | 11 +- 12 files changed, 467 insertions(+), 394 deletions(-) create mode 100644 pkg/aa/templates/profile.j2 diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index 48596aca..54f288dd 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -6,6 +6,7 @@ package aa import ( "bytes" + "maps" "reflect" "slices" "sort" @@ -20,16 +21,16 @@ var MagicRoot = paths.New("/etc/apparmor.d") // AppArmorProfiles represents a full set of apparmor profiles type AppArmorProfiles map[string]*AppArmorProfile -// ApparmorProfile represents a full apparmor profile. +// ApparmorProfile represents a full apparmor profile file. // Warning: close to the BNF grammar of apparmor profile but not exactly the same (yet): // - Some rules are not supported yet (subprofile, hat...) // - The structure is simplified as it only aims at writing profile, not parsing it. type AppArmorProfile struct { Preamble - Profile + Profiles []*Profile } -// Preamble section of a profile +// Preamble section of a profile file, type Preamble struct { Abi []*Abi Includes []*Include @@ -37,13 +38,29 @@ type Preamble struct { Variables []*Variable } -// Profile section of a profile +// Profile represent a single AppArmor profile. type Profile struct { + Rule + Header + Rules Rules +} + +type Header struct { Name string Attachments []string Attributes map[string]string Flags []string - Rules Rules +} + +func (r *Profile) Less(other any) bool { + return false // TBD +} + +func (r *Profile) Equals(other any) bool { + o, _ := other.(*Profile) + return r.Name == o.Name && slices.Equal(r.Attachments, o.Attachments) && + maps.Equal(r.Attributes, o.Attributes) && + slices.Equal(r.Flags, o.Flags) } // ApparmorRule generic interface @@ -68,8 +85,20 @@ func (p *AppArmorProfile) String() string { return res.String() } +// GetDefaultProfile ensure a profile is always present in the profile file and +// return it, as a default profile. +func (p *AppArmorProfile) GetDefaultProfile() *Profile { + if len(p.Profiles) == 0 { + p.Profiles = append(p.Profiles, &Profile{}) + } + return p.Profiles[0] +} + // AddRule adds a new rule to the profile from a log map -func (p *AppArmorProfile) AddRule(log map[string]string) { +// See utils/apparmor/logparser.py for the format of the log map +func (profile *AppArmorProfile) AddRule(log map[string]string) { + p := profile.GetDefaultProfile() + // Generate profile flags and extra rules switch log["error"] { case "-2": @@ -138,23 +167,25 @@ func (p *AppArmorProfile) AddRule(log map[string]string) { // Sort the rules in the profile // Follow: https://apparmor.pujol.io/development/guidelines/#guidelines -func (p *AppArmorProfile) Sort() { - sort.Slice(p.Rules, func(i, j int) bool { - typeOfI := reflect.TypeOf(p.Rules[i]) - typeOfJ := reflect.TypeOf(p.Rules[j]) - if typeOfI != typeOfJ { - valueOfI := typeToValue(typeOfI) - valueOfJ := typeToValue(typeOfJ) - if typeOfI == reflect.TypeOf((*Include)(nil)) && p.Rules[i].(*Include).IfExists { - valueOfI = "include_if_exists" +func (profile *AppArmorProfile) Sort() { + for _, p := range profile.Profiles { + sort.Slice(p.Rules, func(i, j int) bool { + typeOfI := reflect.TypeOf(p.Rules[i]) + typeOfJ := reflect.TypeOf(p.Rules[j]) + if typeOfI != typeOfJ { + valueOfI := typeToValue(typeOfI) + valueOfJ := typeToValue(typeOfJ) + if typeOfI == reflect.TypeOf((*Include)(nil)) && p.Rules[i].(*Include).IfExists { + valueOfI = "include_if_exists" + } + if typeOfJ == reflect.TypeOf((*Include)(nil)) && p.Rules[j].(*Include).IfExists { + valueOfJ = "include_if_exists" + } + return ruleWeights[valueOfI] < ruleWeights[valueOfJ] } - if typeOfJ == reflect.TypeOf((*Include)(nil)) && p.Rules[j].(*Include).IfExists { - valueOfJ = "include_if_exists" - } - return ruleWeights[valueOfI] < ruleWeights[valueOfJ] - } - return p.Rules[i].Less(p.Rules[j]) - }) + return p.Rules[i].Less(p.Rules[j]) + }) + } } // MergeRules merge similar rules together. @@ -163,19 +194,21 @@ func (p *AppArmorProfile) Sort() { // - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw' // // Note: logs.regCleanLogs helps a lot to do a first cleaning -func (p *AppArmorProfile) MergeRules() { - for i := 0; i < len(p.Rules); i++ { - for j := i + 1; j < len(p.Rules); j++ { - typeOfI := reflect.TypeOf(p.Rules[i]) - typeOfJ := reflect.TypeOf(p.Rules[j]) - if typeOfI != typeOfJ { - continue - } +func (profile *AppArmorProfile) MergeRules() { + for _, p := range profile.Profiles { + for i := 0; i < len(p.Rules); i++ { + for j := i + 1; j < len(p.Rules); j++ { + typeOfI := reflect.TypeOf(p.Rules[i]) + typeOfJ := reflect.TypeOf(p.Rules[j]) + if typeOfI != typeOfJ { + continue + } - // If rules are identical, merge them - if p.Rules[i].Equals(p.Rules[j]) { - p.Rules = append(p.Rules[:j], p.Rules[j+1:]...) - j-- + // If rules are identical, merge them + if p.Rules[i].Equals(p.Rules[j]) { + p.Rules = append(p.Rules[:j], p.Rules[j+1:]...) + j-- + } } } } @@ -183,30 +216,32 @@ func (p *AppArmorProfile) MergeRules() { // Format the profile for better readability before printing it. // Follow: https://apparmor.pujol.io/development/guidelines/#the-file-block -func (p *AppArmorProfile) Format() { +func (profile *AppArmorProfile) Format() { const prefixOwner = " " - hasOwnerRule := false - for i := len(p.Rules) - 1; i > 0; i-- { - j := i - 1 - typeOfI := reflect.TypeOf(p.Rules[i]) - typeOfJ := reflect.TypeOf(p.Rules[j]) + for _, p := range profile.Profiles { + hasOwnerRule := false + for i := len(p.Rules) - 1; i > 0; i-- { + j := i - 1 + typeOfI := reflect.TypeOf(p.Rules[i]) + typeOfJ := reflect.TypeOf(p.Rules[j]) - // File rule - if typeOfI == reflect.TypeOf((*File)(nil)) && typeOfJ == reflect.TypeOf((*File)(nil)) { - letterI := getLetterIn(fileAlphabet, p.Rules[i].(*File).Path) - letterJ := getLetterIn(fileAlphabet, p.Rules[j].(*File).Path) + // File rule + if typeOfI == reflect.TypeOf((*File)(nil)) && typeOfJ == reflect.TypeOf((*File)(nil)) { + letterI := getLetterIn(fileAlphabet, p.Rules[i].(*File).Path) + letterJ := getLetterIn(fileAlphabet, p.Rules[j].(*File).Path) - // Add prefix before rule path to align with other rule - if p.Rules[i].(*File).Owner { - hasOwnerRule = true - } else if hasOwnerRule { - p.Rules[i].(*File).Prefix = prefixOwner - } + // Add prefix before rule path to align with other rule + if p.Rules[i].(*File).Owner { + hasOwnerRule = true + } else if hasOwnerRule { + p.Rules[i].(*File).Prefix = prefixOwner + } - if letterI != letterJ { - // Add a new empty line between Files rule of different type - hasOwnerRule = false - p.Rules = append(p.Rules[:i], append([]ApparmorRule{&Rule{}}, p.Rules[i:]...)...) + if letterI != letterJ { + // Add a new empty line between Files rule of different type + hasOwnerRule = false + p.Rules = append(p.Rules[:i], append([]ApparmorRule{&Rule{}}, p.Rules[i:]...)...) + } } } } diff --git a/pkg/aa/profile_test.go b/pkg/aa/profile_test.go index 14403e47..9866cf89 100644 --- a/pkg/aa/profile_test.go +++ b/pkg/aa/profile_test.go @@ -51,11 +51,13 @@ func TestAppArmorProfile_String(t *testing.T) { 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"}, + Profiles: []*Profile{{ + Header: Header{ + 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"}, @@ -108,7 +110,7 @@ func TestAppArmorProfile_String(t *testing.T) { &File{Path: "@{sys}/devices/@{pci}/class", Access: "r"}, includeLocal1, }, - }, + }}, }, want: readprofile("tests/string.aa"), }, @@ -132,72 +134,72 @@ func TestAppArmorProfile_AddRule(t *testing.T) { name: "capability", log: capability1Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{capability1}, - }, + }}, }, }, { name: "network", log: network1Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{network1}, - }, + }}, }, }, { name: "mount", log: mount2Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{mount2}, - }, + }}, }, }, { name: "signal", log: signal1Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{signal1}, - }, + }}, }, }, { name: "ptrace", log: ptrace2Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{ptrace2}, - }, + }}, }, }, { name: "unix", log: unix1Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{unix1}, - }, + }}, }, }, { name: "dbus", log: dbus2Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{dbus2}, - }, + }}, }, }, { name: "file", log: file2Log, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{file2}, - }, + }}, }, }, } @@ -221,20 +223,20 @@ func TestAppArmorProfile_Sort(t *testing.T) { { name: "all", origin: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{ file2, network1, includeLocal1, dbus2, signal1, ptrace1, capability2, file1, dbus1, unix2, signal2, mount2, }, - }, + }}, }, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{ capability2, network1, mount2, signal1, signal2, ptrace1, unix2, dbus2, dbus1, file1, file2, includeLocal1, }, - }, + }}, }, }, } @@ -258,14 +260,14 @@ func TestAppArmorProfile_MergeRules(t *testing.T) { { name: "all", origin: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{capability1, capability1, network1, network1, file1, file1}, - }, + }}, }, want: &AppArmorProfile{ - Profile: Profile{ + Profiles: []*Profile{{ Rules: []ApparmorRule{capability1, network1, file1}, - }, + }}, }, }, } @@ -297,9 +299,11 @@ func TestAppArmorProfile_Integration(t *testing.T) { Values: []string{"@{bin}/aa-status", "@{bin}/apparmor_status"}, }}, }, - Profile: Profile{ - Name: "aa-status", - Attachments: []string{"@{exec_path}"}, + Profiles: []*Profile{{ + Header: Header{ + Name: "aa-status", + Attachments: []string{"@{exec_path}"}, + }, Rules: Rules{ &Include{IfExists: true, IsMagic: true, Path: "local/aa-status"}, &Capability{Name: "dac_read_search"}, @@ -316,7 +320,7 @@ func TestAppArmorProfile_Integration(t *testing.T) { &Capability{Name: "sys_ptrace"}, &Ptrace{Access: "read"}, }, - }, + }}, }, want: readprofile("apparmor.d/profiles-a-f/aa-status"), }, diff --git a/pkg/aa/rlimit.go b/pkg/aa/rlimit.go index b3d0e782..d484b352 100644 --- a/pkg/aa/rlimit.go +++ b/pkg/aa/rlimit.go @@ -5,6 +5,7 @@ package aa type Rlimit struct { + Rule Key string Op string Value string diff --git a/pkg/aa/template.go b/pkg/aa/template.go index 3c7e7084..88235e8c 100644 --- a/pkg/aa/template.go +++ b/pkg/aa/template.go @@ -64,6 +64,7 @@ var ( "iouring", "dbus", "file", + "profile", "include_if_exists", } ruleWeights = map[string]int{} diff --git a/pkg/aa/templates/file.j2 b/pkg/aa/templates/file.j2 index 8c9587e1..821341b5 100644 --- a/pkg/aa/templates/file.j2 +++ b/pkg/aa/templates/file.j2 @@ -22,286 +22,6 @@ {{ "@{" }}{{ .Name }}{{ "} = " }}{{ join .Values }} {{ end -}} -{{- if or .Name .Attachments .Attributes .Flags -}} - {{- "profile" -}} - {{- with .Name -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .Attachments -}} - {{ " " }}{{ join . }} - {{- end -}} - {{- with .Attributes -}} - {{ " xattrs=(" }}{{ join . }}{{ ")" }} - {{- end -}} - {{- with .Flags -}} - {{ " flags=(" }}{{ join . }}{{ ")" }} - {{- end -}} - {{ " {\n" }} -{{- end -}} - -{{- $oldtype := "" -}} -{{- range .Rules -}} - {{- $type := typeof . -}} - {{- if eq $type "Rule" -}} - {{- "\n" -}} - {{- continue -}} - {{- end -}} - {{- if and (ne $type $oldtype) (ne $oldtype "") -}} - {{- "\n" -}} - {{- end -}} - {{- indent "" -}} - - {{- if eq $type "Include" -}} - {{ template "include" . }} - {{- end -}} - - {{- if eq $type "Rlimit" -}} - {{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }}{{ template "comment" . }} - {{- end -}} - - {{- if eq $type "Capability" -}} - {{ template "qualifier" . }}{{ "capability " }}{{ .Name }}{{ "," }}{{ template "comment" . }} - {{- end -}} - - {{- if eq $type "Network" -}} - {{- template "qualifier" . -}} - {{ "network" }} - {{- with .Domain -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .Type -}} - {{ " " }}{{ . }} - {{- else -}} - {{- with .Protocol -}} - {{ " " }}{{ . }} - {{- end -}} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Mount" -}} - {{- template "qualifier" . -}} - {{- "mount" -}} - {{- with .FsType -}} - {{ " fstype=" }}{{ . }} - {{- end -}} - {{- with .Options -}} - {{ " options=(" }}{{ join . }}{{ ")" }} - {{- end -}} - {{- with .Source -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .MountPoint -}} - {{ " -> " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Umount" -}} - {{- template "qualifier" . -}} - {{- "umount" -}} - {{- with .FsType -}} - {{ " fstype=" }}{{ . }} - {{- end -}} - {{- with .Options -}} - {{ " options=(" }}{{ join . }}{{ ")" }} - {{- end -}} - {{- with .MountPoint -}} - {{ " " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Remount" -}} - {{- template "qualifier" . -}} - {{- "remount" -}} - {{- with .FsType -}} - {{ " fstype=" }}{{ . }} - {{- end -}} - {{- with .Options -}} - {{ " options=(" }}{{ join . }}{{ ")" }} - {{- end -}} - {{- with .MountPoint -}} - {{ " " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "PivotRoot" -}} - {{- template "qualifier" . -}} - {{- "pivot_root" -}} - {{- with .OldRoot -}} - {{ " oldroot=" }}{{ . }} - {{- end -}} - {{- with .NewRoot -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .TargetProfile -}} - {{ " -> " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "ChangeProfile" -}} - {{- template "qualifier" . -}} - {{- "change_profile" -}} - {{- with .ExecMode -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .Exec -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .ProfileName -}} - {{ " -> " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Mqueue" -}} - {{- template "qualifier" . -}} - {{- "mqueue" -}} - {{- with .Access -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .Type -}} - {{ " type=" }}{{ . }} - {{- end -}} - {{- with .Label -}} - {{ " label=" }}{{ . }} - {{- end -}} - {{- with .Name -}} - {{ " " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Unix" -}} - {{- template "qualifier" . -}} - {{- "unix" -}} - {{- with .Access -}} - {{ " (" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .Type -}} - {{ " type=" }}{{ . }} - {{- end -}} - {{- with .Protocol -}} - {{ " protocol=" }}{{ . }} - {{- end -}} - {{- with .Address -}} - {{ " addr=" }}{{ . }} - {{- end -}} - {{- with .Label -}} - {{ " label=" }}{{ . }} - {{- end -}} - {{- if and .PeerLabel .PeerAddr -}} - {{ " peer=(label=" }}{{ .PeerLabel }}{{ ", addr="}}{{ .PeerAddr }}{{ ")" }} - {{- else -}} - {{- with .PeerLabel -}} - {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .PeerAddr -}} - {{ overindent "peer=(addr=" }}{{ . }}{{ ")" }} - {{- end -}} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Ptrace" -}} - {{- template "qualifier" . -}} - {{- "ptrace" -}} - {{- with .Access -}} - {{ " (" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .Peer -}} - {{ " peer=" }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Signal" -}} - {{- template "qualifier" . -}} - {{- "signal" -}} - {{- with .Access -}} - {{ " (" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .Set -}} - {{ " set=(" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .Peer -}} - {{ " peer=" }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "Dbus" -}} - {{- template "qualifier" . -}} - {{- "dbus" -}} - {{- if eq .Access "bind" -}} - {{ " bind bus=" }}{{ .Bus }}{{ " name=" }}{{ .Name }} - {{- else -}} - {{- with .Access -}} - {{ " " }}{{ . }} - {{- end -}} - {{- with .Bus -}} - {{ " bus=" }}{{ . }} - {{- end -}} - {{- with .Path -}} - {{ " path=" }}{{ . }} - {{- end -}} - {{ "\n" }} - {{- with .Interface -}} - {{ overindent "interface=" }}{{ . }}{{ "\n" }} - {{- end -}} - {{- with .Member -}} - {{ overindent "member=" }}{{ . }}{{ "\n" }} - {{- end -}} - {{- if and .PeerName .PeerLabel -}} - {{ overindent "peer=(name=" }}{{ .PeerName }}{{ ", label="}}{{ .PeerLabel }}{{ ")" }} - {{- else -}} - {{- with .PeerName -}} - {{ overindent "peer=(name=" }}{{ . }}{{ ")" }} - {{- end -}} - {{- with .PeerLabel -}} - {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} - {{- end -}} - {{- end -}} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- if eq $type "File" -}} - {{- template "qualifier" . -}} - {{- if .Owner -}} - {{- "owner " -}} - {{- end -}} - {{- .Path -}} - {{- " " -}} - {{- with .Padding -}} - {{ . }} - {{- end -}} - {{- .Access -}} - {{- with .Target -}} - {{ " -> " }}{{ . }} - {{- end -}} - {{- "," -}} - {{- template "comment" . -}} - {{- end -}} - - {{- "\n" -}} - {{- $oldtype = $type -}} -{{- end -}} - -{{- if or .Name .Attachments .Attributes .Flags -}} - {{- "}\n" -}} +{{- range .Profiles -}} + {{ template "profile" . }} {{- end -}} diff --git a/pkg/aa/templates/profile.j2 b/pkg/aa/templates/profile.j2 new file mode 100644 index 00000000..9875587c --- /dev/null +++ b/pkg/aa/templates/profile.j2 @@ -0,0 +1,303 @@ +{{- /* apparmor.d - Full set of apparmor profiles */ -}} +{{- /* Copyright (C) 2021-2024 Alexandre Pujol */ -}} +{{- /* SPDX-License-Identifier: GPL-2.0-only */ -}} + +{{- define "profile" -}} + + {{- if or .Name .Attachments .Attributes .Flags -}} + {{- "profile" -}} + {{- with .Name -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .Attachments -}} + {{ " " }}{{ join . }} + {{- end -}} + {{- with .Attributes -}} + {{ " xattrs=(" }}{{ join . }}{{ ")" }} + {{- end -}} + {{- with .Flags -}} + {{ " flags=(" }}{{ join . }}{{ ")" }} + {{- end -}} + {{ " {" }} + {{- template "comment" . -}} + {{- "\n" -}} + {{- end -}} + + {{- $oldtype := "" -}} + {{- range .Rules -}} + {{- $type := typeof . -}} + {{- if eq $type "Rule" -}} + {{- "\n" -}} + {{- continue -}} + {{- end -}} + {{- if and (ne $type $oldtype) (ne $oldtype "") -}} + {{- "\n" -}} + {{- end -}} + {{- indent "" -}} + + {{- if eq $type "Include" -}} + {{ template "include" . }} + {{- end -}} + + {{- if eq $type "Rlimit" -}} + {{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }}{{ template "comment" . }} + {{- end -}} + + {{- if eq $type "Userns" -}} + {{- if .Create -}} + {{ template "qualifier" . }}{{ "userns," }}{{ template "comment" . }} + {{- end -}} + {{- end -}} + + {{- if eq $type "Capability" -}} + {{ template "qualifier" . }}{{ "capability " }}{{ .Name }}{{ "," }}{{ template "comment" . }} + {{- end -}} + + {{- if eq $type "Network" -}} + {{- template "qualifier" . -}} + {{ "network" }} + {{- with .Domain -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .Type -}} + {{ " " }}{{ . }} + {{- else -}} + {{- with .Protocol -}} + {{ " " }}{{ . }} + {{- end -}} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Mount" -}} + {{- template "qualifier" . -}} + {{- "mount" -}} + {{- with .FsType -}} + {{ " fstype=" }}{{ . }} + {{- end -}} + {{- with .Options -}} + {{ " options=(" }}{{ join . }}{{ ")" }} + {{- end -}} + {{- with .Source -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .MountPoint -}} + {{ " -> " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Umount" -}} + {{- template "qualifier" . -}} + {{- "umount" -}} + {{- with .FsType -}} + {{ " fstype=" }}{{ . }} + {{- end -}} + {{- with .Options -}} + {{ " options=(" }}{{ join . }}{{ ")" }} + {{- end -}} + {{- with .MountPoint -}} + {{ " " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Remount" -}} + {{- template "qualifier" . -}} + {{- "remount" -}} + {{- with .FsType -}} + {{ " fstype=" }}{{ . }} + {{- end -}} + {{- with .Options -}} + {{ " options=(" }}{{ join . }}{{ ")" }} + {{- end -}} + {{- with .MountPoint -}} + {{ " " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "PivotRoot" -}} + {{- template "qualifier" . -}} + {{- "pivot_root" -}} + {{- with .OldRoot -}} + {{ " oldroot=" }}{{ . }} + {{- end -}} + {{- with .NewRoot -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .TargetProfile -}} + {{ " -> " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "ChangeProfile" -}} + {{- template "qualifier" . -}} + {{- "change_profile" -}} + {{- with .ExecMode -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .Exec -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .ProfileName -}} + {{ " -> " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Mqueue" -}} + {{- template "qualifier" . -}} + {{- "mqueue" -}} + {{- with .Access -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .Type -}} + {{ " type=" }}{{ . }} + {{- end -}} + {{- with .Label -}} + {{ " label=" }}{{ . }} + {{- end -}} + {{- with .Name -}} + {{ " " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Unix" -}} + {{- template "qualifier" . -}} + {{- "unix" -}} + {{- with .Access -}} + {{ " (" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .Type -}} + {{ " type=" }}{{ . }} + {{- end -}} + {{- with .Protocol -}} + {{ " protocol=" }}{{ . }} + {{- end -}} + {{- with .Address -}} + {{ " addr=" }}{{ . }} + {{- end -}} + {{- with .Label -}} + {{ " label=" }}{{ . }} + {{- end -}} + {{- if and .PeerLabel .PeerAddr -}} + {{ " peer=(label=" }}{{ .PeerLabel }}{{ ", addr="}}{{ .PeerAddr }}{{ ")" }} + {{- else -}} + {{- with .PeerLabel -}} + {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .PeerAddr -}} + {{ overindent "peer=(addr=" }}{{ . }}{{ ")" }} + {{- end -}} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Ptrace" -}} + {{- template "qualifier" . -}} + {{- "ptrace" -}} + {{- with .Access -}} + {{ " (" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .Peer -}} + {{ " peer=" }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Signal" -}} + {{- template "qualifier" . -}} + {{- "signal" -}} + {{- with .Access -}} + {{ " (" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .Set -}} + {{ " set=(" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .Peer -}} + {{ " peer=" }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Dbus" -}} + {{- template "qualifier" . -}} + {{- "dbus" -}} + {{- if eq .Access "bind" -}} + {{ " bind bus=" }}{{ .Bus }}{{ " name=" }}{{ .Name }} + {{- else -}} + {{- with .Access -}} + {{ " " }}{{ . }} + {{- end -}} + {{- with .Bus -}} + {{ " bus=" }}{{ . }} + {{- end -}} + {{- with .Path -}} + {{ " path=" }}{{ . }} + {{- end -}} + {{ "\n" }} + {{- with .Interface -}} + {{ overindent "interface=" }}{{ . }}{{ "\n" }} + {{- end -}} + {{- with .Member -}} + {{ overindent "member=" }}{{ . }}{{ "\n" }} + {{- end -}} + {{- if and .PeerName .PeerLabel -}} + {{ overindent "peer=(name=" }}{{ .PeerName }}{{ ", label="}}{{ .PeerLabel }}{{ ")" }} + {{- else -}} + {{- with .PeerName -}} + {{ overindent "peer=(name=" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .PeerLabel -}} + {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} + {{- end -}} + {{- end -}} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "File" -}} + {{- template "qualifier" . -}} + {{- if .Owner -}} + {{- "owner " -}} + {{- end -}} + {{- .Path -}} + {{- " " -}} + {{- with .Padding -}} + {{ . }} + {{- end -}} + {{- .Access -}} + {{- with .Target -}} + {{ " -> " }}{{ . }} + {{- end -}} + {{- "," -}} + {{- template "comment" . -}} + {{- end -}} + + {{- if eq $type "Profile" -}} + {{ template "profile" . }} + {{- end -}} + + {{- "\n" -}} + {{- $oldtype = $type -}} + {{- end -}} + + {{- if or .Name .Attachments .Attributes .Flags -}} + {{- "}\n" -}} + {{- end -}} + +{{- end -}} diff --git a/pkg/aa/variables.go b/pkg/aa/variables.go index 5081b5ba..cd34bd1a 100644 --- a/pkg/aa/variables.go +++ b/pkg/aa/variables.go @@ -83,11 +83,13 @@ func (p *AppArmorProfile) resolve(str string) []string { } // ResolveAttachments resolve profile attachments defined in exec_path -func (p *AppArmorProfile) ResolveAttachments() { - for _, variable := range p.Variables { +func (profile *AppArmorProfile) ResolveAttachments() { + p := profile.GetDefaultProfile() + + for _, variable := range profile.Variables { if variable.Name == "exec_path" { for _, value := range variable.Values { - attachments := p.resolve(value) + attachments := profile.resolve(value) if len(attachments) == 0 { panic("Variable not defined in: " + value) } @@ -98,7 +100,8 @@ func (p *AppArmorProfile) ResolveAttachments() { } // NestAttachments return a nested attachment string -func (p *AppArmorProfile) NestAttachments() string { +func (profile *AppArmorProfile) NestAttachments() string { + p := profile.GetDefaultProfile() if len(p.Attachments) == 0 { return "" } else if len(p.Attachments) == 1 { diff --git a/pkg/aa/variables_test.go b/pkg/aa/variables_test.go index 2660c5b9..2232e65a 100644 --- a/pkg/aa/variables_test.go +++ b/pkg/aa/variables_test.go @@ -193,8 +193,9 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) { p := NewAppArmorProfile() p.Variables = tt.variables p.ResolveAttachments() - if !reflect.DeepEqual(p.Attachments, tt.want) { - t.Errorf("AppArmorProfile.ResolveAttachments() = %v, want %v", p.Attachments, tt.want) + profile := p.GetDefaultProfile() + if !reflect.DeepEqual(profile.Attachments, tt.want) { + t.Errorf("AppArmorProfile.ResolveAttachments() = %v, want %v", profile.Attachments, tt.want) } }) } @@ -242,7 +243,8 @@ func TestAppArmorProfile_NestAttachments(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := NewAppArmorProfile() - p.Attachments = tt.Attachments + profile := p.GetDefaultProfile() + profile.Attachments = tt.Attachments if got := p.NestAttachments(); got != tt.want { t.Errorf("AppArmorProfile.NestAttachments() = %v, want %v", got, tt.want) } diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index 913f1172..5b2ea712 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -208,8 +208,9 @@ func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfiles { } if _, ok := profiles[name]; !ok { - profile := &aa.AppArmorProfile{} - profile.Name = name + profile := &aa.AppArmorProfile{ + Profiles: []*aa.Profile{{Header: aa.Header{Name: name}}}, + } profile.AddRule(log) profiles[name] = profile } else { diff --git a/pkg/logs/logs_test.go b/pkg/logs/logs_test.go index d0aa0f5b..e970da5f 100644 --- a/pkg/logs/logs_test.go +++ b/pkg/logs/logs_test.go @@ -299,8 +299,8 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { aaLogs: append(append(refKmod, refPowerProfiles...), refKmod...), want: aa.AppArmorProfiles{ "kmod": &aa.AppArmorProfile{ - Profile: aa.Profile{ - Name: "kmod", + Profiles: []*aa.Profile{{ + Header: aa.Header{Name: "kmod"}, Rules: aa.Rules{ &aa.Unix{ Rule: aa.Rule{FileInherit: true}, @@ -315,11 +315,11 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { Protocol: "0", }, }, - }, + }}, }, "power-profiles-daemon": &aa.AppArmorProfile{ - Profile: aa.Profile{ - Name: "power-profiles-daemon", + Profiles: []*aa.Profile{{ + Header: aa.Header{Name: "power-profiles-daemon"}, Rules: aa.Rules{ &aa.Dbus{ Access: "send", @@ -331,7 +331,7 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { PeerLabel: "dbus-daemon", }, }, - }, + }}, }, }, }, diff --git a/pkg/prebuild/directive/dbus.go b/pkg/prebuild/directive/dbus.go index f0922c55..98c2306f 100644 --- a/pkg/prebuild/directive/dbus.go +++ b/pkg/prebuild/directive/dbus.go @@ -97,7 +97,8 @@ func (d Dbus) sanityCheck(opt *Option) string { func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { interfaces := setInterfaces(rules) - p := &aa.AppArmorProfile{} + profile := &aa.AppArmorProfile{} + p := profile.GetDefaultProfile() p.Rules = append(p.Rules, &aa.Dbus{ Access: "bind", Bus: rules["bus"], Name: rules["name"], }) @@ -127,12 +128,13 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { Member: "Introspect", PeerName: `":1.@{int}"`, }) - return p + return profile } func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile { interfaces := setInterfaces(rules) - p := &aa.AppArmorProfile{} + profile := &aa.AppArmorProfile{} + p := profile.GetDefaultProfile() for _, iface := range interfaces { p.Rules = append(p.Rules, &aa.Dbus{ Access: "send", @@ -153,5 +155,5 @@ func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile { PeerLabel: rules["label"], }) } - return p + return profile } diff --git a/pkg/prebuild/directive/exec.go b/pkg/prebuild/directive/exec.go index db08ba6e..e622d7af 100644 --- a/pkg/prebuild/directive/exec.go +++ b/pkg/prebuild/directive/exec.go @@ -27,7 +27,7 @@ func init() { }) } -func (d Exec) Apply(opt *Option, profile string) string { +func (d Exec) Apply(opt *Option, profileRaw string) string { transition := "Px" transitions := []string{"P", "U", "p", "u", "PU", "pu"} t := opt.ArgList[0] @@ -36,7 +36,8 @@ func (d Exec) Apply(opt *Option, profile string) string { delete(opt.ArgMap, t) } - p := &aa.AppArmorProfile{} + profile := &aa.AppArmorProfile{} + p := profile.GetDefaultProfile() for name := range opt.ArgMap { profiletoTransition := util.MustReadFile(cfg.RootApparmord.Join(name)) dstProfile := aa.DefaultTunables() @@ -53,9 +54,9 @@ func (d Exec) Apply(opt *Option, profile string) string { } } } - p.Sort() - rules := p.String() + profile.Sort() + rules := profile.String() lenRules := len(rules) rules = rules[:lenRules-1] - return strings.Replace(profile, opt.Raw, rules, -1) + return strings.Replace(profileRaw, opt.Raw, rules, -1) }