mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2024-12-27 23:48:00 +01:00
feat(aa): add function to resolve include preamble.
This commit is contained in:
parent
04a91bbd9b
commit
dc0e0084a0
6 changed files with 191 additions and 36 deletions
|
@ -8,21 +8,34 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/arduino/go-paths-helper"
|
||||||
|
"github.com/roddhjav/apparmor.d/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
includeCache map[*Include]*AppArmorProfileFile = make(map[*Include]*AppArmorProfileFile)
|
||||||
|
|
||||||
regVariableReference = regexp.MustCompile(`@{([^{}]+)}`)
|
regVariableReference = regexp.MustCompile(`@{([^{}]+)}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resolve resolves all variables and includes in the profile and merge the rules in the profile
|
// Resolve resolves variables and includes definied in the profile preamble
|
||||||
func (f *AppArmorProfileFile) Resolve() error {
|
func (f *AppArmorProfileFile) Resolve() error {
|
||||||
|
// Resolve preamble includes
|
||||||
|
for _, include := range f.Preamble.GetIncludes() {
|
||||||
|
err := f.resolveInclude(include)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve variables
|
// Resolve variables
|
||||||
for _, variable := range f.Preamble.GetVariables() {
|
for _, variable := range f.Preamble.GetVariables() {
|
||||||
newValues := []string{}
|
newValues := []string{}
|
||||||
for _, value := range variable.Values {
|
for _, value := range variable.Values {
|
||||||
vars := f.resolveVariable(value)
|
vars, err := f.resolveValues(value)
|
||||||
if len(vars) == 0 {
|
if err != nil {
|
||||||
return fmt.Errorf("Variable not defined in: %s", value)
|
return err
|
||||||
}
|
}
|
||||||
newValues = append(newValues, vars...)
|
newValues = append(newValues, vars...)
|
||||||
}
|
}
|
||||||
|
@ -33,9 +46,9 @@ func (f *AppArmorProfileFile) Resolve() error {
|
||||||
for _, profile := range f.Profiles {
|
for _, profile := range f.Profiles {
|
||||||
attachments := []string{}
|
attachments := []string{}
|
||||||
for _, att := range profile.Attachments {
|
for _, att := range profile.Attachments {
|
||||||
vars := f.resolveVariable(att)
|
vars, err := f.resolveValues(att)
|
||||||
if len(vars) == 0 {
|
if err != nil {
|
||||||
return fmt.Errorf("Variable not defined in: %s", att)
|
return err
|
||||||
}
|
}
|
||||||
attachments = append(attachments, vars...)
|
attachments = append(attachments, vars...)
|
||||||
}
|
}
|
||||||
|
@ -45,27 +58,108 @@ func (f *AppArmorProfileFile) Resolve() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AppArmorProfileFile) resolveVariable(input string) []string {
|
func (f *AppArmorProfileFile) resolveValues(input string) ([]string, error) {
|
||||||
if !strings.Contains(input, tokVARIABLE) {
|
if !strings.Contains(input, tokVARIABLE) {
|
||||||
return []string{input}
|
return []string{input}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := []string{}
|
values := []string{}
|
||||||
match := regVariableReference.FindStringSubmatch(input)
|
match := regVariableReference.FindStringSubmatch(input)
|
||||||
if len(match) > 1 {
|
if len(match) == 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid variable reference: %s", input)
|
||||||
|
}
|
||||||
|
|
||||||
variable := match[0]
|
variable := match[0]
|
||||||
varname := match[1]
|
varname := match[1]
|
||||||
|
found := false
|
||||||
for _, vrbl := range f.Preamble.GetVariables() {
|
for _, vrbl := range f.Preamble.GetVariables() {
|
||||||
if vrbl.Name == varname {
|
if vrbl.Name == varname {
|
||||||
|
found = true
|
||||||
for _, v := range vrbl.Values {
|
for _, v := range vrbl.Values {
|
||||||
newVar := strings.ReplaceAll(input, variable, v)
|
if strings.Contains(v, tokVARIABLE+varname+"}") {
|
||||||
res := f.resolveVariable(newVar)
|
return nil, fmt.Errorf("recursive variable found in: %s", varname)
|
||||||
vars = append(vars, res...)
|
}
|
||||||
|
newValues := strings.ReplaceAll(input, variable, v)
|
||||||
|
newValues = strings.ReplaceAll(newValues, "//", "/")
|
||||||
|
res, err := f.resolveValues(newValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values = append(values, res...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("Variable %s not defined", varname)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveInclude resolves all includes defined in the profile preamble
|
||||||
|
func (f *AppArmorProfileFile) resolveInclude(include *Include) error {
|
||||||
|
if include == nil || include.Path == "" {
|
||||||
|
return fmt.Errorf("Invalid include: %v", include)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isCached := includeCache[include]
|
||||||
|
if !isCached {
|
||||||
|
var files paths.PathList
|
||||||
|
var err error
|
||||||
|
|
||||||
|
path := MagicRoot.Join(include.Path)
|
||||||
|
if !include.IsMagic {
|
||||||
|
path = paths.New(include.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.IsDir() {
|
||||||
|
files, err = path.ReadDir(paths.FilterOutDirectories())
|
||||||
|
if err != nil {
|
||||||
|
if include.IfExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("File %s not found: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if path.Exist() {
|
||||||
|
files = append(files, path)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vars = append(vars, input)
|
if include.IfExists {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return vars
|
return fmt.Errorf("File %s not found", path)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
iFile := &AppArmorProfileFile{}
|
||||||
|
for _, file := range files {
|
||||||
|
raw, err := util.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := iFile.Parse(raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := iFile.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inc := range iFile.Preamble.GetIncludes() {
|
||||||
|
if err := iFile.resolveInclude(inc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all includes in iFile
|
||||||
|
iFile.Preamble = iFile.Preamble.DeleteKind(tokINCLUDE)
|
||||||
|
|
||||||
|
// Cache the included file
|
||||||
|
includeCache[include] = iFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert iFile in the place of include in the current file
|
||||||
|
index := f.Preamble.IndexOf(include)
|
||||||
|
f.Preamble = f.Preamble.Insert(index, includeCache[include].Preamble...)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,24 +7,75 @@ package aa
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/arduino/go-paths-helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAppArmorProfileFile_resolveVariable(t *testing.T) {
|
func TestAppArmorProfileFile_resolveInclude(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
f AppArmorProfileFile
|
include *Include
|
||||||
input string
|
want *AppArmorProfileFile
|
||||||
want []string
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil",
|
name: "empty",
|
||||||
input: "@{newvar}",
|
include: &Include{Path: "", IsMagic: true},
|
||||||
want: []string{},
|
want: &AppArmorProfileFile{Preamble: Rules{&Include{Path: "", IsMagic: true}}},
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "tunables",
|
||||||
|
include: &Include{Path: "tunables/global", IsMagic: true},
|
||||||
|
want: &AppArmorProfileFile{
|
||||||
|
Preamble: Rules{
|
||||||
|
&Alias{Path: "/usr/", RewrittenPath: "/User/"},
|
||||||
|
&Alias{Path: "/lib/", RewrittenPath: "/Libraries/"},
|
||||||
|
&Comment{RuleBase: RuleBase{IsLineRule: true, Comment: " variable declarations for inclusion"}},
|
||||||
|
&Variable{
|
||||||
|
Name: "FOO", Define: true,
|
||||||
|
Values: []string{
|
||||||
|
"/foo", "/bar", "/baz", "/biff", "/lib", "/tmp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
MagicRoot = paths.New("../../tests/testdata")
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := &AppArmorProfileFile{}
|
||||||
|
got.Preamble = append(got.Preamble, tt.include)
|
||||||
|
if err := got.resolveInclude(tt.include); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("AppArmorProfileFile.resolveInclude() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("AppArmorProfileFile.resolveValues() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppArmorProfileFile_resolveValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not-defined",
|
||||||
|
input: "@{newvar}",
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-name",
|
||||||
input: "@{}",
|
input: "@{}",
|
||||||
want: []string{"@{}"},
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "default",
|
||||||
|
@ -45,9 +96,12 @@ func TestAppArmorProfileFile_resolveVariable(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) {
|
||||||
f := DefaultTunables()
|
f := DefaultTunables()
|
||||||
got := f.resolveVariable(tt.input)
|
got, err := f.resolveValues(tt.input)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("AppArmorProfileFile.resolveValues() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("AppArmorProfileFile.resolveVariable() = %v, want %v", got, tt.want)
|
t.Errorf("AppArmorProfileFile.resolveValues() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ func (r Rules) Remove(rule Rule) Rules {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rules) Insert(idx int, rules ...Rule) Rules {
|
func (r Rules) Insert(idx int, rules ...Rule) Rules {
|
||||||
return append(r[:idx], append(rules, r[idx:]...)...)
|
return append(r[:idx], append(rules, r[idx+1:]...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rules) Sort() Rules {
|
func (r Rules) Sort() Rules {
|
||||||
|
|
2
tests/testdata/tunables/dir.d/aliases
vendored
Normal file
2
tests/testdata/tunables/dir.d/aliases
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
alias /usr/ -> /User/,
|
||||||
|
alias /lib/ -> /Libraries/,
|
2
tests/testdata/tunables/dir.d/vars
vendored
Normal file
2
tests/testdata/tunables/dir.d/vars
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# variable declarations for inclusion
|
||||||
|
@{FOO} = /foo /bar /baz /biff /lib /tmp
|
3
tests/testdata/tunables/global
vendored
Normal file
3
tests/testdata/tunables/global
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
include <tunables/dir.d>
|
||||||
|
|
Loading…
Reference in a new issue