From f8d970faf016a93f13c4b49c3a0f1423b4c3bcc7 Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Tue, 26 Mar 2024 18:05:55 +0000 Subject: [PATCH] build: new structure for internal config files. --- pkg/prebuild/cfg/directories.go | 11 +- pkg/prebuild/cfg/files.go | 125 ++++++++++++++++++ pkg/prebuild/cfg/files_test.go | 210 ++++++++++++++++++++++++++++++ pkg/prebuild/prepare/configure.go | 62 +-------- 4 files changed, 350 insertions(+), 58 deletions(-) create mode 100644 pkg/prebuild/cfg/files.go create mode 100644 pkg/prebuild/cfg/files_test.go diff --git a/pkg/prebuild/cfg/directories.go b/pkg/prebuild/cfg/directories.go index 80f8ba682..d8bdf23a0 100644 --- a/pkg/prebuild/cfg/directories.go +++ b/pkg/prebuild/cfg/directories.go @@ -19,9 +19,18 @@ var ( // FlagDir is the directory where the flags are stored FlagDir *paths.Path = DistDir.Join("flags") + // IgnoreDir is the directory where the ignore files are stored + IgnoreDir *paths.Path = DistDir.Join("ignore") + // SystemdDir is the directory where the systemd drop-in files are stored SystemdDir *paths.Path = paths.New("systemd") + // DebianDir is the directory where the debian specific files are stored + DebianDir *paths.Path = DistDir.Join("debian") + // Either or not overwrite some upstreamed profile - Overwrite bool = false + Overwrite = Overwriter{Enabled: false} + + Ignore = Ignorer{} + Flags = Flagger{} ) diff --git a/pkg/prebuild/cfg/files.go b/pkg/prebuild/cfg/files.go new file mode 100644 index 000000000..48f2c9f76 --- /dev/null +++ b/pkg/prebuild/cfg/files.go @@ -0,0 +1,125 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +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 +} + +type Flagger struct{} + +func (f Flagger) Read(name string) map[string][]string { + res := map[string][]string{} + path := FlagDir.Join(name + ".flags") + if !path.Exist() { + return res + } + + lines, _ := path.ReadFileAsLines() + for _, line := range lines { + line, next := filterComment(line) + if next { + continue + } + manifest := strings.Split(line, " ") + profile := manifest[0] + flags := []string{} + if len(manifest) > 1 { + flags = strings.Split(manifest[1], ",") + } + res[profile] = flags + } + return res +} + +type Ignorer struct{} + +func (i Ignorer) Read(name string) []string { + res := []string{} + path := IgnoreDir.Join(name + ".ignore") + if !path.Exist() { + return res + } + + lines, _ := path.ReadFileAsLines() + for _, line := range lines { + line, next := filterComment(line) + if next { + continue + } + res = append(res, line) + } + return res +} + +type Overwriter struct { + Enabled bool +} + +// 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) + } + for _, line := range lines { + line, next := filterComment(line) + if next { + continue + } + res = append(res, line) + } + return res +} + +// Overwrite upstream profile for APT: rename our profile & hide upstream +func (o Overwriter) Apt(files []string) { + const ext = ".apparmor.d" + file, err := DebianDir.Join("apparmor.d.hide").Append() + if err != nil { + panic(err) + } + for _, name := range files { + origin := RootApparmord.Join(name) + dest := RootApparmord.Join(name + ext) + if err := origin.Rename(dest); err != nil { + panic(err) + } + if _, err := file.WriteString("/etc/apparmor.d/" + name + "\n"); err != nil { + panic(err) + } + } +} + +// Clean the debian/apparmor.d.hide file +func (o Overwriter) AptClean() { + const debianHide = `# This file is generated by "make", all edit will be lost. + +/etc/apparmor.d/usr.bin.firefox +/etc/apparmor.d/usr.sbin.cups-browsed +/etc/apparmor.d/usr.sbin.cupsd +/etc/apparmor.d/usr.sbin.rsyslogd +` + path := DebianDir.Join("apparmor.d.hide") + if err := path.WriteFile([]byte(debianHide)); err != nil { + panic(err) + } +} diff --git a/pkg/prebuild/cfg/files_test.go b/pkg/prebuild/cfg/files_test.go new file mode 100644 index 000000000..cffa631e3 --- /dev/null +++ b/pkg/prebuild/cfg/files_test.go @@ -0,0 +1,210 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package cfg + +import ( + "reflect" + "testing" + + "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 + content string + want map[string][]string + }{ + { + name: "empty", + content: ` + +`, + want: map[string][]string{}, + }, + { + name: "main", + content: ` +# 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: map[string][]string{ + "akonadi_akonotes_resource": {"complain"}, + "bwrap": {"attach_disconnected", "mediate_deleted", "complain"}, + "bwrap-app": {"attach_disconnected", "complain"}, + "gnome-disks": {"complain"}, + }, + }, + } + FlagDir = paths.New("/tmp/") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := FlagDir.Join(tt.name + ".flags").WriteFile([]byte(tt.content)) + if err != nil { + return + } + if got := Flags.Read(tt.name); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Flagger.Read() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIgnore_Read(t *testing.T) { + tests := []struct { + name string + content string + want []string + }{ + { + name: "empty", + content: ` + +`, + want: []string{}, + }, + { + name: "main", + content: ` +# Contains profiles and configuration for full system confinement, only included +# when built with 'make full' +apparmor.d/groups/_full + +apparmor.d/groups/apps # should be sandboxed +code +`, + want: []string{ + "apparmor.d/groups/_full", + "apparmor.d/groups/apps", + "code", + }, + }, + } + IgnoreDir = paths.New("/tmp/") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := IgnoreDir.Join(tt.name + ".ignore").WriteFile([]byte(tt.content)) + if err != nil { + return + } + if got := Ignore.Read(tt.name); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Ignore.Read() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOverwriter_Get(t *testing.T) { + tests := []struct { + name string + content string + want []string + }{ + { + name: "empty", + content: ` + +`, + want: []string{}, + }, + { + name: "main", + content: ` +# This is managed globally +brave # not so brave +chrome +firefox +`, + want: []string{ + "brave", + "chrome", + "firefox", + }, + }, + } + DistDir = paths.New("/tmp/") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := DistDir.Join("overwrite").WriteFile([]byte(tt.content)) + if err != nil { + return + } + if got := Overwrite.Get(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Overwriter.Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOverwriter_Apt(t *testing.T) { + tests := []struct { + name string + files []string + }{ + { + name: "empty", + files: []string{}, + }, + } + DebianDir = paths.New("/tmp/") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + Overwrite.Apt(tt.files) + Overwrite.AptClean() + }) + } +} diff --git a/pkg/prebuild/prepare/configure.go b/pkg/prebuild/prepare/configure.go index fee59d26c..ba0c700c4 100644 --- a/pkg/prebuild/prepare/configure.go +++ b/pkg/prebuild/prepare/configure.go @@ -6,9 +6,7 @@ package prepare import ( "fmt" - "strings" - "github.com/arduino/go-paths-helper" "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" "github.com/roddhjav/apparmor.d/pkg/util" ) @@ -32,10 +30,10 @@ func (p Configure) Apply() ([]string, error) { case "arch", "opensuse": case "ubuntu": - debianOverwriteClean() - if cfg.Overwrite { - profiles := getOverwriteProfiles() - debianOverwrite(profiles) + cfg.Overwrite.AptClean() + if cfg.Overwrite.Enabled { + profiles := cfg.Overwrite.Get() + cfg.Overwrite.Apt(profiles) } else { if err := util.CopyTo(cfg.DistDir.Join("ubuntu"), cfg.RootApparmord); err != nil { return res, err @@ -43,7 +41,7 @@ func (p Configure) Apply() ([]string, error) { } case "debian", "whonix": - debianOverwriteClean() + cfg.Overwrite.AptClean() // Copy Debian specific abstractions if err := util.CopyTo(cfg.DistDir.Join("ubuntu"), cfg.RootApparmord); err != nil { @@ -56,53 +54,3 @@ func (p Configure) Apply() ([]string, error) { } return res, nil } - -// Overwrite upstream profile: rename our profile & hide upstream -func debianOverwrite(files []string) { - const ext = ".apparmor.d" - file, err := paths.New("debian/apparmor.d.hide").Append() - if err != nil { - panic(err) - } - for _, name := range files { - origin := cfg.RootApparmord.Join(name) - dest := cfg.RootApparmord.Join(name + ext) - if err := origin.Rename(dest); err != nil { - panic(err) - } - if _, err := file.WriteString("/etc/apparmor.d/" + name + "\n"); err != nil { - panic(err) - } - } -} - -// Clean the debian/apparmor.d.hide file -func debianOverwriteClean() { - const debianHide = `# This file is generated by "make", all edit will be lost. - -/etc/apparmor.d/usr.bin.firefox -/etc/apparmor.d/usr.sbin.cups-browsed -/etc/apparmor.d/usr.sbin.cupsd -/etc/apparmor.d/usr.sbin.rsyslogd -` - path := paths.New("debian/apparmor.d.hide") - if err := path.WriteFile([]byte(debianHide)); err != nil { - panic(err) - } -} - -// Get the list of upstream profiles to overwrite from dist/overwrite -func getOverwriteProfiles() []string { - res := []string{} - lines, err := cfg.DistDir.Join("overwrite").ReadFileAsLines() - if err != nil { - panic(err) - } - for _, line := range lines { - if strings.HasPrefix(line, "#") || line == "" { - continue - } - res = append(res, line) - } - return res -}