feat(aa): rename the main profile struct.

This commit is contained in:
Alexandre Pujol 2024-04-16 21:51:56 +01:00
parent 4b753210e7
commit 890275fb22
Failed to generate hash of commit
11 changed files with 287 additions and 274 deletions

214
pkg/aa/apparmor.go Normal file
View file

@ -0,0 +1,214 @@
// 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 (
"bytes"
"reflect"
"slices"
"sort"
"strings"
"github.com/arduino/go-paths-helper"
)
// Default Apparmor magic directory: /etc/apparmor.d/.
var MagicRoot = paths.New("/etc/apparmor.d")
// AppArmorProfileFiles represents a full set of apparmor profiles
type AppArmorProfileFiles map[string]*AppArmorProfileFile
// AppArmorProfileFile 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 AppArmorProfileFile struct {
Preamble
Profiles []*Profile
}
// Preamble section of a profile file,
type Preamble struct {
Abi []*Abi
Includes []*Include
Aliases []*Alias
Variables []*Variable
}
func NewAppArmorProfile() *AppArmorProfileFile {
return &AppArmorProfileFile{}
}
// String returns the formatted representation of a profile as a string
func (f *AppArmorProfileFile) String() string {
var res bytes.Buffer
err := tmplAppArmorProfile.Execute(&res, f)
if err != nil {
return err.Error()
}
return res.String()
}
// GetDefaultProfile ensure a profile is always present in the profile file and
// return it, as a default profile.
func (f *AppArmorProfileFile) GetDefaultProfile() *Profile {
if len(f.Profiles) == 0 {
f.Profiles = append(f.Profiles, &Profile{})
}
return f.Profiles[0]
}
// AddRule adds a new rule to the profile from a log map
// See utils/apparmor/logparser.py for the format of the log map
func (f *AppArmorProfileFile) AddRule(log map[string]string) {
p := f.GetDefaultProfile()
// Generate profile flags and extra rules
switch log["error"] {
case "-2":
if !slices.Contains(p.Flags, "mediate_deleted") {
p.Flags = append(p.Flags, "mediate_deleted")
}
case "-13":
if strings.Contains(log["info"], "namespace creation restricted") {
p.Rules = append(p.Rules, newUsernsFromLog(log))
} else if strings.Contains(log["info"], "disconnected path") && !slices.Contains(p.Flags, "attach_disconnected") {
p.Flags = append(p.Flags, "attach_disconnected")
}
default:
}
switch log["class"] {
case "cap":
p.Rules = append(p.Rules, newCapabilityFromLog(log))
case "net":
if log["family"] == "unix" {
p.Rules = append(p.Rules, newUnixFromLog(log))
} else {
p.Rules = append(p.Rules, newNetworkFromLog(log))
}
case "mount":
if strings.Contains(log["flags"], "remount") {
p.Rules = append(p.Rules, newRemountFromLog(log))
} else {
switch log["operation"] {
case "mount":
p.Rules = append(p.Rules, newMountFromLog(log))
case "umount":
p.Rules = append(p.Rules, newUmountFromLog(log))
case "remount":
p.Rules = append(p.Rules, newRemountFromLog(log))
case "pivotroot":
p.Rules = append(p.Rules, newPivotRootFromLog(log))
}
}
case "posix_mqueue", "sysv_mqueue":
p.Rules = append(p.Rules, newMqueueFromLog(log))
case "signal":
p.Rules = append(p.Rules, newSignalFromLog(log))
case "ptrace":
p.Rules = append(p.Rules, newPtraceFromLog(log))
case "namespace":
p.Rules = append(p.Rules, newUsernsFromLog(log))
case "unix":
p.Rules = append(p.Rules, newUnixFromLog(log))
case "dbus":
p.Rules = append(p.Rules, newDbusFromLog(log))
case "file":
if log["operation"] == "change_onexec" {
p.Rules = append(p.Rules, newChangeProfileFromLog(log))
} else {
p.Rules = append(p.Rules, newFileFromLog(log))
}
default:
if strings.Contains(log["operation"], "dbus") {
p.Rules = append(p.Rules, newDbusFromLog(log))
} else if log["family"] == "unix" {
p.Rules = append(p.Rules, newUnixFromLog(log))
}
}
}
// Sort the rules in the profile
// Follow: https://apparmor.pujol.io/development/guidelines/#guidelines
func (f *AppArmorProfileFile) Sort() {
for _, p := range f.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]
}
return p.Rules[i].Less(p.Rules[j])
})
}
}
// MergeRules merge similar rules together.
// Steps:
// - Remove identical rules
// - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw'
//
// Note: logs.regCleanLogs helps a lot to do a first cleaning
func (f *AppArmorProfileFile) MergeRules() {
for _, p := range f.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--
}
}
}
}
}
// Format the profile for better readability before printing it.
// Follow: https://apparmor.pujol.io/development/guidelines/#the-file-block
func (f *AppArmorProfileFile) Format() {
const prefixOwner = " "
for _, p := range f.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)
// 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:]...)...)
}
}
}
}
}

View file

@ -31,17 +31,17 @@ func readprofile(path string) string {
func TestAppArmorProfile_String(t *testing.T) { func TestAppArmorProfile_String(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
p *AppArmorProfile f *AppArmorProfileFile
want string want string
}{ }{
{ {
name: "empty", name: "empty",
p: &AppArmorProfile{}, f: &AppArmorProfileFile{},
want: ``, want: ``,
}, },
{ {
name: "foo", name: "foo",
p: &AppArmorProfile{ f: &AppArmorProfileFile{
Preamble: Preamble{ Preamble: Preamble{
Abi: []*Abi{{IsMagic: true, Path: "abi/4.0"}}, Abi: []*Abi{{IsMagic: true, Path: "abi/4.0"}},
Includes: []*Include{{IsMagic: true, Path: "tunables/global"}}, Includes: []*Include{{IsMagic: true, Path: "tunables/global"}},
@ -117,7 +117,7 @@ func TestAppArmorProfile_String(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := tt.p.String(); got != tt.want { if got := tt.f.String(); got != tt.want {
t.Errorf("AppArmorProfile.String() = |%v|, want |%v|", got, tt.want) t.Errorf("AppArmorProfile.String() = |%v|, want |%v|", got, tt.want)
} }
}) })
@ -128,12 +128,12 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
log map[string]string log map[string]string
want *AppArmorProfile want *AppArmorProfileFile
}{ }{
{ {
name: "capability", name: "capability",
log: capability1Log, log: capability1Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{capability1}, Rules: []ApparmorRule{capability1},
}}, }},
@ -142,7 +142,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "network", name: "network",
log: network1Log, log: network1Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{network1}, Rules: []ApparmorRule{network1},
}}, }},
@ -151,7 +151,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "mount", name: "mount",
log: mount2Log, log: mount2Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{mount2}, Rules: []ApparmorRule{mount2},
}}, }},
@ -160,7 +160,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "signal", name: "signal",
log: signal1Log, log: signal1Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{signal1}, Rules: []ApparmorRule{signal1},
}}, }},
@ -169,7 +169,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "ptrace", name: "ptrace",
log: ptrace2Log, log: ptrace2Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{ptrace2}, Rules: []ApparmorRule{ptrace2},
}}, }},
@ -178,7 +178,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "unix", name: "unix",
log: unix1Log, log: unix1Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{unix1}, Rules: []ApparmorRule{unix1},
}}, }},
@ -187,7 +187,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "dbus", name: "dbus",
log: dbus2Log, log: dbus2Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{dbus2}, Rules: []ApparmorRule{dbus2},
}}, }},
@ -196,7 +196,7 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
{ {
name: "file", name: "file",
log: file2Log, log: file2Log,
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{file2}, Rules: []ApparmorRule{file2},
}}, }},
@ -217,12 +217,12 @@ func TestAppArmorProfile_AddRule(t *testing.T) {
func TestAppArmorProfile_Sort(t *testing.T) { func TestAppArmorProfile_Sort(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
origin *AppArmorProfile origin *AppArmorProfileFile
want *AppArmorProfile want *AppArmorProfileFile
}{ }{
{ {
name: "all", name: "all",
origin: &AppArmorProfile{ origin: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{ Rules: []ApparmorRule{
file2, network1, includeLocal1, dbus2, signal1, ptrace1, file2, network1, includeLocal1, dbus2, signal1, ptrace1,
@ -230,7 +230,7 @@ func TestAppArmorProfile_Sort(t *testing.T) {
}, },
}}, }},
}, },
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{ Rules: []ApparmorRule{
capability2, network1, mount2, signal1, signal2, ptrace1, capability2, network1, mount2, signal1, signal2, ptrace1,
@ -254,17 +254,17 @@ func TestAppArmorProfile_Sort(t *testing.T) {
func TestAppArmorProfile_MergeRules(t *testing.T) { func TestAppArmorProfile_MergeRules(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
origin *AppArmorProfile origin *AppArmorProfileFile
want *AppArmorProfile want *AppArmorProfileFile
}{ }{
{ {
name: "all", name: "all",
origin: &AppArmorProfile{ origin: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{capability1, capability1, network1, network1, file1, file1}, Rules: []ApparmorRule{capability1, capability1, network1, network1, file1, file1},
}}, }},
}, },
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Profiles: []*Profile{{ Profiles: []*Profile{{
Rules: []ApparmorRule{capability1, network1, file1}, Rules: []ApparmorRule{capability1, network1, file1},
}}, }},
@ -285,12 +285,12 @@ func TestAppArmorProfile_MergeRules(t *testing.T) {
func TestAppArmorProfile_Integration(t *testing.T) { func TestAppArmorProfile_Integration(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
p *AppArmorProfile f *AppArmorProfileFile
want string want string
}{ }{
{ {
name: "aa-status", name: "aa-status",
p: &AppArmorProfile{ f: &AppArmorProfileFile{
Preamble: Preamble{ Preamble: Preamble{
Abi: []*Abi{{IsMagic: true, Path: "abi/3.0"}}, Abi: []*Abi{{IsMagic: true, Path: "abi/3.0"}},
Includes: []*Include{{IsMagic: true, Path: "tunables/global"}}, Includes: []*Include{{IsMagic: true, Path: "tunables/global"}},
@ -327,10 +327,10 @@ func TestAppArmorProfile_Integration(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.p.Sort() tt.f.Sort()
tt.p.MergeRules() tt.f.MergeRules()
tt.p.Format() tt.f.Format()
if got := tt.p.String(); "\n"+got != tt.want { if got := tt.f.String(); "\n"+got != tt.want {
t.Errorf("AppArmorProfile = |%v|, want |%v|", "\n"+got, tt.want) t.Errorf("AppArmorProfile = |%v|, want |%v|", "\n"+got, tt.want)
} }
}) })

View file

@ -5,46 +5,19 @@
package aa package aa
import ( import (
"bytes"
"maps" "maps"
"reflect"
"slices" "slices"
"sort"
"strings" "strings"
"github.com/arduino/go-paths-helper"
) )
// Default Apparmor magic directory: /etc/apparmor.d/. // Profile represents a single AppArmor profile.
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 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
Profiles []*Profile
}
// Preamble section of a profile file,
type Preamble struct {
Abi []*Abi
Includes []*Include
Aliases []*Alias
Variables []*Variable
}
// Profile represent a single AppArmor profile.
type Profile struct { type Profile struct {
Rule Rule
Header Header
Rules Rules Rules Rules
} }
// Header represents the header of a profile.
type Header struct { type Header struct {
Name string Name string
Attachments []string Attachments []string
@ -53,7 +26,11 @@ type Header struct {
} }
func (r *Profile) Less(other any) bool { func (r *Profile) Less(other any) bool {
return false // TBD o, _ := other.(*Profile)
if r.Name != o.Name {
return r.Name < o.Name
}
return len(r.Attachments) < len(o.Attachments)
} }
func (r *Profile) Equals(other any) bool { func (r *Profile) Equals(other any) bool {
@ -62,187 +39,3 @@ func (r *Profile) Equals(other any) bool {
maps.Equal(r.Attributes, o.Attributes) && maps.Equal(r.Attributes, o.Attributes) &&
slices.Equal(r.Flags, o.Flags) slices.Equal(r.Flags, o.Flags)
} }
// ApparmorRule generic interface
type ApparmorRule interface {
Less(other any) bool
Equals(other any) bool
}
type Rules []ApparmorRule
func NewAppArmorProfile() *AppArmorProfile {
return &AppArmorProfile{}
}
// String returns the formatted representation of a profile as a string
func (p *AppArmorProfile) String() string {
var res bytes.Buffer
err := tmplAppArmorProfile.Execute(&res, p)
if err != nil {
return err.Error()
}
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
// 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":
if !slices.Contains(p.Flags, "mediate_deleted") {
p.Flags = append(p.Flags, "mediate_deleted")
}
case "-13":
if strings.Contains(log["info"], "namespace creation restricted") {
p.Rules = append(p.Rules, newUsernsFromLog(log))
} else if strings.Contains(log["info"], "disconnected path") && !slices.Contains(p.Flags, "attach_disconnected") {
p.Flags = append(p.Flags, "attach_disconnected")
}
default:
}
switch log["class"] {
case "cap":
p.Rules = append(p.Rules, newCapabilityFromLog(log))
case "net":
if log["family"] == "unix" {
p.Rules = append(p.Rules, newUnixFromLog(log))
} else {
p.Rules = append(p.Rules, newNetworkFromLog(log))
}
case "mount":
if strings.Contains(log["flags"], "remount") {
p.Rules = append(p.Rules, newRemountFromLog(log))
} else {
switch log["operation"] {
case "mount":
p.Rules = append(p.Rules, newMountFromLog(log))
case "umount":
p.Rules = append(p.Rules, newUmountFromLog(log))
case "remount":
p.Rules = append(p.Rules, newRemountFromLog(log))
case "pivotroot":
p.Rules = append(p.Rules, newPivotRootFromLog(log))
}
}
case "posix_mqueue", "sysv_mqueue":
p.Rules = append(p.Rules, newMqueueFromLog(log))
case "signal":
p.Rules = append(p.Rules, newSignalFromLog(log))
case "ptrace":
p.Rules = append(p.Rules, newPtraceFromLog(log))
case "namespace":
p.Rules = append(p.Rules, newUsernsFromLog(log))
case "unix":
p.Rules = append(p.Rules, newUnixFromLog(log))
case "dbus":
p.Rules = append(p.Rules, newDbusFromLog(log))
case "file":
if log["operation"] == "change_onexec" {
p.Rules = append(p.Rules, newChangeProfileFromLog(log))
} else {
p.Rules = append(p.Rules, newFileFromLog(log))
}
default:
if strings.Contains(log["operation"], "dbus") {
p.Rules = append(p.Rules, newDbusFromLog(log))
} else if log["family"] == "unix" {
p.Rules = append(p.Rules, newUnixFromLog(log))
}
}
}
// Sort the rules in the profile
// Follow: https://apparmor.pujol.io/development/guidelines/#guidelines
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]
}
return p.Rules[i].Less(p.Rules[j])
})
}
}
// MergeRules merge similar rules together.
// Steps:
// - Remove identical rules
// - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw'
//
// Note: logs.regCleanLogs helps a lot to do a first cleaning
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--
}
}
}
}
}
// Format the profile for better readability before printing it.
// Follow: https://apparmor.pujol.io/development/guidelines/#the-file-block
func (profile *AppArmorProfile) Format() {
const prefixOwner = " "
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)
// 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:]...)...)
}
}
}
}
}

View file

@ -9,6 +9,14 @@ import (
"strings" "strings"
) )
// ApparmorRule generic interface
type ApparmorRule interface {
Less(other any) bool
Equals(other any) bool
}
type Rules []ApparmorRule
type Rule struct { type Rule struct {
Comment string Comment string
NoNewPrivs bool NoNewPrivs bool

View file

@ -4,7 +4,7 @@
{{- define "profile" -}} {{- define "profile" -}}
{{- if or .Name .Attachments .Attributes .Flags -}} {{- with .Header -}}
{{- "profile" -}} {{- "profile" -}}
{{- with .Name -}} {{- with .Name -}}
{{ " " }}{{ . }} {{ " " }}{{ . }}
@ -18,9 +18,7 @@
{{- with .Flags -}} {{- with .Flags -}}
{{ " flags=(" }}{{ join . }}{{ ")" }} {{ " flags=(" }}{{ join . }}{{ ")" }}
{{- end -}} {{- end -}}
{{ " {" }} {{- "{\n" -}}
{{- template "comment" . -}}
{{- "\n" -}}
{{- end -}} {{- end -}}
{{- $oldtype := "" -}} {{- $oldtype := "" -}}
@ -296,7 +294,7 @@
{{- $oldtype = $type -}} {{- $oldtype = $type -}}
{{- end -}} {{- end -}}
{{- if or .Name .Attachments .Attributes .Flags -}} {{- with .Header -}}
{{- "}\n" -}} {{- "}\n" -}}
{{- end -}} {{- end -}}

View file

@ -19,8 +19,8 @@ var (
// DefaultTunables return a minimal working profile to build the profile // DefaultTunables return a minimal working profile to build the profile
// It should not be used when loading file from /etc/apparmor.d // It should not be used when loading file from /etc/apparmor.d
func DefaultTunables() *AppArmorProfile { func DefaultTunables() *AppArmorProfileFile {
return &AppArmorProfile{ return &AppArmorProfileFile{
Preamble: Preamble{ Preamble: Preamble{
Variables: []*Variable{ Variables: []*Variable{
{Name: "bin", Values: []string{"/{,usr/}{,s}bin"}}, {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}},
@ -36,41 +36,41 @@ func DefaultTunables() *AppArmorProfile {
} }
// ParseVariables extract all variables from the profile // ParseVariables extract all variables from the profile
func (p *AppArmorProfile) ParseVariables(content string) { func (f *AppArmorProfileFile) ParseVariables(content string) {
matches := regVariablesDef.FindAllStringSubmatch(content, -1) matches := regVariablesDef.FindAllStringSubmatch(content, -1)
for _, match := range matches { for _, match := range matches {
if len(match) > 2 { if len(match) > 2 {
key := match[1] key := match[1]
values := strings.Split(match[2], " ") values := strings.Split(match[2], " ")
found := false found := false
for idx, variable := range p.Variables { for idx, variable := range f.Variables {
if variable.Name == key { if variable.Name == key {
p.Variables[idx].Values = append(p.Variables[idx].Values, values...) f.Variables[idx].Values = append(f.Variables[idx].Values, values...)
found = true found = true
break break
} }
} }
if !found { if !found {
variable := &Variable{Name: key, Values: values} variable := &Variable{Name: key, Values: values}
p.Variables = append(p.Variables, variable) f.Variables = append(f.Variables, variable)
} }
} }
} }
} }
// resolve recursively resolves all variables references // resolve recursively resolves all variables references
func (p *AppArmorProfile) resolve(str string) []string { func (f *AppArmorProfileFile) resolve(str string) []string {
if strings.Contains(str, "@{") { if strings.Contains(str, "@{") {
vars := []string{} vars := []string{}
match := regVariablesRef.FindStringSubmatch(str) match := regVariablesRef.FindStringSubmatch(str)
if len(match) > 1 { if len(match) > 1 {
variable := match[0] variable := match[0]
varname := match[1] varname := match[1]
for _, vrbl := range p.Variables { for _, vrbl := range f.Variables {
if vrbl.Name == varname { if vrbl.Name == varname {
for _, value := range vrbl.Values { for _, value := range vrbl.Values {
newVar := strings.ReplaceAll(str, variable, value) newVar := strings.ReplaceAll(str, variable, value)
vars = append(vars, p.resolve(newVar)...) vars = append(vars, f.resolve(newVar)...)
} }
} }
} }
@ -83,8 +83,8 @@ func (p *AppArmorProfile) resolve(str string) []string {
} }
// ResolveAttachments resolve profile attachments defined in exec_path // ResolveAttachments resolve profile attachments defined in exec_path
func (profile *AppArmorProfile) ResolveAttachments() { func (f *AppArmorProfileFile) ResolveAttachments() {
p := profile.GetDefaultProfile() p := f.GetDefaultProfile()
for _, variable := range profile.Variables { for _, variable := range profile.Variables {
if variable.Name == "exec_path" { if variable.Name == "exec_path" {
@ -100,8 +100,8 @@ func (profile *AppArmorProfile) ResolveAttachments() {
} }
// NestAttachments return a nested attachment string // NestAttachments return a nested attachment string
func (profile *AppArmorProfile) NestAttachments() string { func (f *AppArmorProfileFile) NestAttachments() string {
p := profile.GetDefaultProfile() p := f.GetDefaultProfile()
if len(p.Attachments) == 0 { if len(p.Attachments) == 0 {
return "" return ""
} else if len(p.Attachments) == 1 { } else if len(p.Attachments) == 1 {

View file

@ -16,11 +16,11 @@ import (
func TestDefaultTunables(t *testing.T) { func TestDefaultTunables(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
want *AppArmorProfile want *AppArmorProfileFile
}{ }{
{ {
name: "aa", name: "aa",
want: &AppArmorProfile{ want: &AppArmorProfileFile{
Preamble: Preamble{ Preamble: Preamble{
Variables: []*Variable{ Variables: []*Variable{
{Name: "bin", Values: []string{"/{,usr/}{,s}bin"}}, {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}},

View file

@ -197,8 +197,8 @@ func (aaLogs AppArmorLogs) String() string {
} }
// ParseToProfiles convert the log data into a new AppArmorProfiles // ParseToProfiles convert the log data into a new AppArmorProfiles
func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfiles { func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfileFiles {
profiles := make(aa.AppArmorProfiles, 0) profiles := make(aa.AppArmorProfileFiles, 0)
for _, log := range aaLogs { for _, log := range aaLogs {
name := "" name := ""
if strings.Contains(log["operation"], "dbus") { if strings.Contains(log["operation"], "dbus") {
@ -208,7 +208,7 @@ func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfiles {
} }
if _, ok := profiles[name]; !ok { if _, ok := profiles[name]; !ok {
profile := &aa.AppArmorProfile{ profile := &aa.AppArmorProfileFile{
Profiles: []*aa.Profile{{Header: aa.Header{Name: name}}}, Profiles: []*aa.Profile{{Header: aa.Header{Name: name}}},
} }
profile.AddRule(log) profile.AddRule(log)

View file

@ -292,13 +292,13 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
aaLogs AppArmorLogs aaLogs AppArmorLogs
want aa.AppArmorProfiles want aa.AppArmorProfileFiles
}{ }{
{ {
name: "", name: "",
aaLogs: append(append(refKmod, refPowerProfiles...), refKmod...), aaLogs: append(append(refKmod, refPowerProfiles...), refKmod...),
want: aa.AppArmorProfiles{ want: aa.AppArmorProfileFiles{
"kmod": &aa.AppArmorProfile{ "kmod": &aa.AppArmorProfileFile{
Profiles: []*aa.Profile{{ Profiles: []*aa.Profile{{
Header: aa.Header{Name: "kmod"}, Header: aa.Header{Name: "kmod"},
Rules: aa.Rules{ Rules: aa.Rules{
@ -317,7 +317,7 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) {
}, },
}}, }},
}, },
"power-profiles-daemon": &aa.AppArmorProfile{ "power-profiles-daemon": &aa.AppArmorProfileFile{
Profiles: []*aa.Profile{{ Profiles: []*aa.Profile{{
Header: aa.Header{Name: "power-profiles-daemon"}, Header: aa.Header{Name: "power-profiles-daemon"},
Rules: aa.Rules{ Rules: aa.Rules{

View file

@ -51,7 +51,7 @@ func setInterfaces(rules map[string]string) []string {
} }
func (d Dbus) Apply(opt *Option, profile string) string { func (d Dbus) Apply(opt *Option, profile string) string {
var p *aa.AppArmorProfile var p *aa.AppArmorProfileFile
action := d.sanityCheck(opt) action := d.sanityCheck(opt)
switch action { switch action {
@ -95,9 +95,9 @@ func (d Dbus) sanityCheck(opt *Option) string {
return action return action
} }
func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { func (d Dbus) own(rules map[string]string) *aa.AppArmorProfileFile {
interfaces := setInterfaces(rules) interfaces := setInterfaces(rules)
profile := &aa.AppArmorProfile{} profile := &aa.AppArmorProfileFile{}
p := profile.GetDefaultProfile() p := profile.GetDefaultProfile()
p.Rules = append(p.Rules, &aa.Dbus{ p.Rules = append(p.Rules, &aa.Dbus{
Access: "bind", Bus: rules["bus"], Name: rules["name"], Access: "bind", Bus: rules["bus"], Name: rules["name"],
@ -131,9 +131,9 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile {
return profile return profile
} }
func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile { func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfileFile {
interfaces := setInterfaces(rules) interfaces := setInterfaces(rules)
profile := &aa.AppArmorProfile{} profile := &aa.AppArmorProfileFile{}
p := profile.GetDefaultProfile() p := profile.GetDefaultProfile()
for _, iface := range interfaces { for _, iface := range interfaces {
p.Rules = append(p.Rules, &aa.Dbus{ p.Rules = append(p.Rules, &aa.Dbus{

View file

@ -36,7 +36,7 @@ func (d Exec) Apply(opt *Option, profileRaw string) string {
delete(opt.ArgMap, t) delete(opt.ArgMap, t)
} }
profile := &aa.AppArmorProfile{} profile := &aa.AppArmorProfileFile{}
p := profile.GetDefaultProfile() p := profile.GetDefaultProfile()
for name := range opt.ArgMap { for name := range opt.ArgMap {
profiletoTransition := util.MustReadFile(cfg.RootApparmord.Join(name)) profiletoTransition := util.MustReadFile(cfg.RootApparmord.Join(name))