diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index 2d74f7d0d..b0f559950 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -10,9 +10,13 @@ import ( "sort" "strings" + "github.com/arduino/go-paths-helper" "golang.org/x/exp/slices" ) +// Default Apparmor magic directory: /etc/apparmor.d/. +var MagicRoot = paths.New("/etc/apparmor.d") + // AppArmorProfiles represents a full set of apparmor profiles type AppArmorProfiles map[string]*AppArmorProfile diff --git a/pkg/aa/variables.go b/pkg/aa/variables.go index 6ed6bbe51..3a6b4f619 100644 --- a/pkg/aa/variables.go +++ b/pkg/aa/variables.go @@ -11,16 +11,12 @@ import ( "regexp" "strings" - "github.com/arduino/go-paths-helper" "golang.org/x/exp/slices" ) var ( regVariablesDef = regexp.MustCompile(`@{(.*)}\s*[+=]+\s*(.*)`) regVariablesRef = regexp.MustCompile(`@{([^{}]+)}`) - - // Default Apparmor magic directory: /etc/apparmor.d/. - MagicRoot = paths.New("/etc/apparmor.d") ) type Variable struct { diff --git a/pkg/prebuild/builder/dev.go b/pkg/prebuild/builder/dev.go index 3f4840b45..e555e5d9f 100644 --- a/pkg/prebuild/builder/dev.go +++ b/pkg/prebuild/builder/dev.go @@ -11,6 +11,7 @@ import ( var ( regDev = util.ToRegexRepl([]string{ + `Cx`, `cx`, `PUx`, `pux`, `Px`, `px`, `Ux`, `ux`, diff --git a/pkg/prebuild/cfg/files.go b/pkg/prebuild/cfg/files.go index 48f2c9f76..90acc2d80 100644 --- a/pkg/prebuild/cfg/files.go +++ b/pkg/prebuild/cfg/files.go @@ -6,22 +6,9 @@ package cfg import ( "strings" -) -// Filter out comments from a text configuration file -func filterComment(line string) (string, bool) { - if strings.HasPrefix(line, "#") || line == "" { - return "", true - } - if strings.Contains(line, "#") { - line = strings.Split(line, "#")[0] - line = strings.TrimSpace(line) - if line == "" { - return "", true - } - } - return line, false -} + "github.com/roddhjav/apparmor.d/pkg/util" +) type Flagger struct{} @@ -32,12 +19,8 @@ func (f Flagger) Read(name string) map[string][]string { return res } - lines, _ := path.ReadFileAsLines() + lines := util.MustReadFileAsLines(path) for _, line := range lines { - line, next := filterComment(line) - if next { - continue - } manifest := strings.Split(line, " ") profile := manifest[0] flags := []string{} @@ -52,21 +35,11 @@ func (f Flagger) Read(name string) map[string][]string { type Ignorer struct{} func (i Ignorer) Read(name string) []string { - res := []string{} path := IgnoreDir.Join(name + ".ignore") if !path.Exist() { - return res + return []string{} } - - lines, _ := path.ReadFileAsLines() - for _, line := range lines { - line, next := filterComment(line) - if next { - continue - } - res = append(res, line) - } - return res + return util.MustReadFileAsLines(path) } type Overwriter struct { @@ -75,19 +48,11 @@ type Overwriter struct { // Get the list of upstream profiles to overwrite from dist/overwrite func (o Overwriter) Get() []string { - res := []string{} - lines, err := DistDir.Join("overwrite").ReadFileAsLines() - if err != nil { - panic(err) + path := DistDir.Join("overwrite") + if !path.Exist() { + return []string{} } - for _, line := range lines { - line, next := filterComment(line) - if next { - continue - } - res = append(res, line) - } - return res + return util.MustReadFileAsLines(path) } // Overwrite upstream profile for APT: rename our profile & hide upstream diff --git a/pkg/prebuild/cfg/files_test.go b/pkg/prebuild/cfg/files_test.go index cffa631e3..00aadc032 100644 --- a/pkg/prebuild/cfg/files_test.go +++ b/pkg/prebuild/cfg/files_test.go @@ -11,51 +11,6 @@ import ( "github.com/arduino/go-paths-helper" ) -func Test_filterComment(t *testing.T) { - tests := []struct { - name string - line string - wantLine string - wantNext bool - }{ - { - name: "comment", - line: "# comment", - wantLine: "", - wantNext: true, - }, - { - name: "comment with space", - line: " # comment", - wantLine: "", - wantNext: true, - }, - { - name: "no comment", - line: "no comment", - wantLine: "no comment", - wantNext: false, - }, - { - name: "no comment # comment", - line: "no comment # comment", - wantLine: "no comment", - wantNext: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotLine, gotNext := filterComment(tt.line) - if gotLine != tt.wantLine { - t.Errorf("filterComment() got = %v, want %v", gotLine, tt.wantLine) - } - if gotNext != tt.wantNext { - t.Errorf("filterComment() got1 = %v, want %v", gotNext, tt.wantNext) - } - }) - } -} - func TestFlagger_Read(t *testing.T) { tests := []struct { name string diff --git a/pkg/prebuild/directive/core.go b/pkg/prebuild/directive/core.go index 75346d35d..e6f97e02a 100644 --- a/pkg/prebuild/directive/core.go +++ b/pkg/prebuild/directive/core.go @@ -13,10 +13,10 @@ import ( "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" ) -// Define the directive keyword globally -const Keyword = "#aa:" - var ( + // Define the directive keyword globally + Keyword = "#aa:" + // Build the profiles with the following directive applied Directives = map[string]Directive{} diff --git a/pkg/prebuild/directive/exec.go b/pkg/prebuild/directive/exec.go index 88514572f..e0b1e2e17 100644 --- a/pkg/prebuild/directive/exec.go +++ b/pkg/prebuild/directive/exec.go @@ -9,6 +9,7 @@ import ( "github.com/roddhjav/apparmor.d/pkg/aa" "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" "golang.org/x/exp/slices" ) @@ -21,7 +22,7 @@ func init() { Base: cfg.Base{ Keyword: "exec", Msg: "Exec directive applied", - Help: `#aa:exec [P|U|p|u|PU|pu|] profiles...`, + Help: Keyword + `exec [P|U|p|u|PU|pu|] profiles...`, }, }) } @@ -37,12 +38,7 @@ func (d Exec) Apply(opt *Option, profile string) string { p := &aa.AppArmorProfile{} for name := range opt.ArgMap { - content, err := cfg.RootApparmord.Join(name).ReadFile() - if err != nil { - panic(err) - } - profiletoTransition := string(content) - + profiletoTransition := util.MustReadFile(cfg.RootApparmord.Join(name)) dstProfile := aa.DefaultTunables() dstProfile.ParseVariables(profiletoTransition) for _, variable := range dstProfile.Variables { diff --git a/pkg/prebuild/directive/filter.go b/pkg/prebuild/directive/filter.go index 58a0675b9..8b5502ca5 100644 --- a/pkg/prebuild/directive/filter.go +++ b/pkg/prebuild/directive/filter.go @@ -25,14 +25,14 @@ func init() { Base: cfg.Base{ Keyword: "only", Msg: "Only directive applied", - Help: `#aa:only filters...`, + Help: Keyword + `only filters...`, }, }) RegisterDirective(&FilterExclude{ Base: cfg.Base{ Keyword: "exclude", Msg: "Exclude directive applied", - Help: `#aa:exclude filters...`, + Help: Keyword + `exclude filters...`, }, }) } diff --git a/pkg/prebuild/directive/stack.go b/pkg/prebuild/directive/stack.go index 23c4f19d1..cb891acc5 100644 --- a/pkg/prebuild/directive/stack.go +++ b/pkg/prebuild/directive/stack.go @@ -33,7 +33,7 @@ func init() { Base: cfg.Base{ Keyword: "stack", Msg: "Stack directive applied", - Help: `#aa:stack profiles...`, + Help: Keyword + `stack profiles...`, }, }) } @@ -41,12 +41,7 @@ func init() { func (s Stack) Apply(opt *Option, profile string) string { res := "" for name := range opt.ArgMap { - tmp, err := cfg.RootApparmord.Join(name).ReadFile() - if err != nil { - panic(err) - } - stackedProfile := string(tmp) - + stackedProfile := util.MustReadFile(cfg.RootApparmord.Join(name)) m := regRules.FindStringSubmatch(stackedProfile) if len(m) < 2 { panic(fmt.Sprintf("No profile found in %s", name)) diff --git a/pkg/prebuild/prebuild.go b/pkg/prebuild/prebuild.go index 412f465ee..b56644eb6 100644 --- a/pkg/prebuild/prebuild.go +++ b/pkg/prebuild/prebuild.go @@ -13,6 +13,7 @@ import ( "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" "github.com/roddhjav/apparmor.d/pkg/prebuild/directive" "github.com/roddhjav/apparmor.d/pkg/prebuild/prepare" + "github.com/roddhjav/apparmor.d/pkg/util" ) func init() { @@ -65,11 +66,10 @@ func Build() error { if !file.Exist() { continue } - content, err := file.ReadFile() + profile, err := util.ReadFile(file) if err != nil { return err } - profile := string(content) for _, b := range builder.Builds { profile = b.Apply(profile) } diff --git a/pkg/prebuild/prepare/flags.go b/pkg/prebuild/prepare/flags.go index 995b13916..cd6c2f54e 100644 --- a/pkg/prebuild/prepare/flags.go +++ b/pkg/prebuild/prepare/flags.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" ) var ( @@ -43,13 +44,13 @@ func (p SetFlags) Apply() ([]string, error) { // Overwrite profile flags if len(flags) > 0 { flagsStr := " flags=(" + strings.Join(flags, ",") + ") {" - content, err := file.ReadFile() + out, err := util.ReadFile(file) if err != nil { return res, err } // Remove all flags definition, then set manifest' flags - out := regFlags.ReplaceAllLiteralString(string(content), "") + out = regFlags.ReplaceAllLiteralString(out, "") out = regProfileHeader.ReplaceAllLiteralString(out, flagsStr) if err := file.WriteFile([]byte(out)); err != nil { return res, err diff --git a/pkg/prebuild/prepare/fsp.go b/pkg/prebuild/prepare/fsp.go index e893560b6..29a7cce2b 100644 --- a/pkg/prebuild/prepare/fsp.go +++ b/pkg/prebuild/prepare/fsp.go @@ -35,11 +35,11 @@ func (p FullSystemPolicy) Apply() ([]string, error) { // Set systemd profile name path := cfg.RootApparmord.Join("tunables/multiarch.d/system") - content, err := path.ReadFile() + out, err := util.ReadFile(path) if err != nil { return res, err } - out := strings.Replace(string(content), "@{p_systemd}=unconfined", "@{p_systemd}=systemd", -1) + out = strings.Replace(out, "@{p_systemd}=unconfined", "@{p_systemd}=systemd", -1) out = strings.Replace(out, "@{p_systemd_user}=unconfined", "@{p_systemd_user}=systemd-user", -1) if err := path.WriteFile([]byte(out)); err != nil { return res, err @@ -47,11 +47,10 @@ func (p FullSystemPolicy) Apply() ([]string, error) { // Fix conflicting x modifiers in abstractions - FIXME: Temporary solution path = cfg.RootApparmord.Join("abstractions/gstreamer") - content, err = path.ReadFile() + out, err = util.ReadFile(path) if err != nil { return res, err } - out = string(content) regFixConflictX := util.ToRegexRepl([]string{`.*gst-plugin-scanner.*`, ``}) out = regFixConflictX.Replace(out) if err := path.WriteFile([]byte(out)); err != nil { diff --git a/pkg/util/tools.go b/pkg/util/tools.go index 75a7258fe..604b88745 100644 --- a/pkg/util/tools.go +++ b/pkg/util/tools.go @@ -7,10 +7,20 @@ package util import ( "encoding/hex" "regexp" + "slices" + "strings" "github.com/arduino/go-paths-helper" ) +var ( + Comment = `#` + regFilter = ToRegexRepl([]string{ + `\s*` + Comment + `.*`, ``, + `(?m)^(?:[\t\s]*(?:\r?\n|\r))+`, ``, + }) +) + type RegexReplList []RegexRepl type RegexRepl struct { @@ -40,7 +50,7 @@ func (rr RegexReplList) Replace(str string) string { return str } -// DecodeHexInString decode and replace all hex value in a given string constitued of "key=value". +// DecodeHexInString decode and replace all hex value in a given string of "key=value" format. func DecodeHexInString(str string) string { toDecode := []string{"name", "comm", "profile"} for _, name := range toDecode { @@ -94,3 +104,37 @@ func CopyTo(src *paths.Path, dst *paths.Path) error { } return nil } + +// Filter out comments and empty line from a string +func Filter(src string) string { + return regFilter.Replace(src) +} + +// ReadFile read a file and return its content as a string. +func ReadFile(path *paths.Path) (string, error) { + content, err := path.ReadFile() + if err != nil { + return "", err + } + return string(content), nil +} + +// MustReadFile read a file and return its content as a string. Panic if an error occurs. +func MustReadFile(path *paths.Path) string { + content, err := path.ReadFile() + if err != nil { + panic(err) + } + return string(content) +} + +// MustReadFileAsLines read a file and return its content as a slice of string. +// It panics if an error occurs and filter out comments and empty lines. +func MustReadFileAsLines(path *paths.Path) []string { + res := strings.Split(Filter(MustReadFile(path)), "\n") + if slices.Contains(res, "") { + idx := slices.Index(res, "") + res = slices.Delete(res, idx, idx+1) + } + return res +} diff --git a/pkg/util/tools_test.go b/pkg/util/tools_test.go index 5f5a1ef8f..a253f5631 100644 --- a/pkg/util/tools_test.go +++ b/pkg/util/tools_test.go @@ -151,3 +151,66 @@ func TestCopyTo(t *testing.T) { }) } } + +func Test_Filter(t *testing.T) { + tests := []struct { + name string + src string + want string + }{ + { + name: "comment", + src: "# comment", + want: "", + }, + { + name: "comment with space", + src: " # comment", + want: "", + }, + { + name: "no comment", + src: "no comment", + want: "no comment", + }, + { + name: "no comment # comment", + src: "no comment # comment", + want: "no comment", + }, + { + name: "empty", + src: ` + +`, + want: ``, + }, + { + name: "main", + src: ` +# Common profile flags definition for all distributions +# File format: one profile by line using the format: ' ' + +bwrap attach_disconnected,mediate_deleted,complain +bwrap-app attach_disconnected,complain + +akonadi_akonotes_resource complain # Dev +gnome-disks complain + +`, + want: `bwrap attach_disconnected,mediate_deleted,complain +bwrap-app attach_disconnected,complain +akonadi_akonotes_resource complain +gnome-disks complain +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotLine := Filter(tt.src) + if gotLine != tt.want { + t.Errorf("FilterComment() got = |%v|, want |%v|", gotLine, tt.want) + } + }) + } +}