From 2dea78a59c108369815940717a69d2aeb41ec826 Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Mon, 25 Mar 2024 23:34:14 +0000 Subject: [PATCH] refractor(build): move prepare tasks to the prepare sub package. --- pkg/prebuild/prepare/configure.go | 108 ++++++++++++++++++++++++++++ pkg/prebuild/prepare/core.go | 39 ++++++++++ pkg/prebuild/prepare/core_test.go | 36 ++++++++++ pkg/prebuild/prepare/flags.go | 72 +++++++++++++++++++ pkg/prebuild/prepare/fsp.go | 63 ++++++++++++++++ pkg/prebuild/prepare/ignore.go | 59 +++++++++++++++ pkg/prebuild/prepare/merge.go | 61 ++++++++++++++++ pkg/prebuild/prepare/synchronise.go | 40 +++++++++++ pkg/prebuild/prepare/systemd.go | 41 +++++++++++ 9 files changed, 519 insertions(+) create mode 100644 pkg/prebuild/prepare/configure.go create mode 100644 pkg/prebuild/prepare/core.go create mode 100644 pkg/prebuild/prepare/core_test.go create mode 100644 pkg/prebuild/prepare/flags.go create mode 100644 pkg/prebuild/prepare/fsp.go create mode 100644 pkg/prebuild/prepare/ignore.go create mode 100644 pkg/prebuild/prepare/merge.go create mode 100644 pkg/prebuild/prepare/synchronise.go create mode 100644 pkg/prebuild/prepare/systemd.go diff --git a/pkg/prebuild/prepare/configure.go b/pkg/prebuild/prepare/configure.go new file mode 100644 index 00000000..fee59d26 --- /dev/null +++ b/pkg/prebuild/prepare/configure.go @@ -0,0 +1,108 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +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" +) + +type Configure struct { + cfg.Base +} + +func init() { + RegisterTask(&Configure{ + Base: cfg.Base{ + Keyword: "configure", + Msg: "Set distribution specificities", + }, + }) +} + +func (p Configure) Apply() ([]string, error) { + res := []string{} + switch cfg.Distribution { + case "arch", "opensuse": + + case "ubuntu": + debianOverwriteClean() + if cfg.Overwrite { + profiles := getOverwriteProfiles() + debianOverwrite(profiles) + } else { + if err := util.CopyTo(cfg.DistDir.Join("ubuntu"), cfg.RootApparmord); err != nil { + return res, err + } + } + + case "debian", "whonix": + debianOverwriteClean() + + // Copy Debian specific abstractions + if err := util.CopyTo(cfg.DistDir.Join("ubuntu"), cfg.RootApparmord); err != nil { + return res, err + } + + default: + return []string{}, fmt.Errorf("%s is not a supported distribution", cfg.Distribution) + + } + 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 +} diff --git a/pkg/prebuild/prepare/core.go b/pkg/prebuild/prepare/core.go new file mode 100644 index 00000000..3daf19d8 --- /dev/null +++ b/pkg/prebuild/prepare/core.go @@ -0,0 +1,39 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "fmt" + + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +var ( + // Prepare the build directory with the following tasks + Prepares = []Task{} + + // Available prepare tasks + Tasks = map[string]Task{} +) + +// Main directive interface +type Task interface { + cfg.BaseInterface + Apply() ([]string, error) +} + +func Register(names ...string) { + for _, name := range names { + if b, present := Tasks[name]; present { + Prepares = append(Prepares, b) + } else { + panic(fmt.Sprintf("Unknown task: %s", name)) + } + } +} + +func RegisterTask(t Task) { + Tasks[t.Name()] = t +} diff --git a/pkg/prebuild/prepare/core_test.go b/pkg/prebuild/prepare/core_test.go new file mode 100644 index 00000000..3d856cb3 --- /dev/null +++ b/pkg/prebuild/prepare/core_test.go @@ -0,0 +1,36 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "slices" + "testing" +) + +func TestRegister(t *testing.T) { + tests := []struct { + name string + names []string + wantSuccess bool + }{ + { + name: "test", + names: []string{"synchronise", "ignore"}, + wantSuccess: true, + }, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + Register(tt.names...) + for _, name := range tt.names { + if got := slices.Contains(Prepares, Tasks[name]); got != tt.wantSuccess { + t.Errorf("Register() = %v, want %v", got, tt.wantSuccess) + } + + } + }) + } +} diff --git a/pkg/prebuild/prepare/flags.go b/pkg/prebuild/prepare/flags.go new file mode 100644 index 00000000..ee55a28a --- /dev/null +++ b/pkg/prebuild/prepare/flags.go @@ -0,0 +1,72 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "fmt" + "regexp" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +var ( + regFlags = regexp.MustCompile(`flags=\(([^)]+)\)`) + regProfileHeader = regexp.MustCompile(` {`) +) + +type SetFlags struct { + cfg.Base +} + +func init() { + RegisterTask(&SetFlags{ + Base: cfg.Base{ + Keyword: "setflags", + Msg: "Set flags on some profiles", + }, + }) +} + +func (p SetFlags) Apply() ([]string, error) { + res := []string{} + for _, name := range []string{"main.flags", cfg.Distribution + ".flags"} { + path := cfg.FlagDir.Join(name) + if !path.Exist() { + continue + } + lines, _ := path.ReadFileAsLines() + for _, line := range lines { + if strings.HasPrefix(line, "#") || line == "" { + continue + } + manifest := strings.Split(line, " ") + profile := manifest[0] + file := cfg.RootApparmord.Join(profile) + if !file.Exist() { + res = append(res, fmt.Sprintf("Profile %s not found, ignoring", profile)) + continue + } + + // If flags is set, overwrite profile flag + if len(manifest) > 1 { + flags := " flags=(" + manifest[1] + ") {" + content, err := file.ReadFile() + if err != nil { + return res, err + } + + // Remove all flags definition, then set manifest' flags + out := regFlags.ReplaceAllLiteralString(string(content), "") + out = regProfileHeader.ReplaceAllLiteralString(out, flags) + if err := file.WriteFile([]byte(out)); err != nil { + return res, err + } + } + } + res = append(res, path.String()) + } + return res, nil +} diff --git a/pkg/prebuild/prepare/fsp.go b/pkg/prebuild/prepare/fsp.go new file mode 100644 index 00000000..b9f27460 --- /dev/null +++ b/pkg/prebuild/prepare/fsp.go @@ -0,0 +1,63 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "strings" + + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +type FullSystemPolicy struct { + cfg.Base +} + +func init() { + RegisterTask(&FullSystemPolicy{ + Base: cfg.Base{ + Keyword: "fsp", + Msg: "Configure AppArmor for full system policy", + }, + }) +} + +func (p FullSystemPolicy) Apply() ([]string, error) { + res := []string{} + + // Install full system policy profiles + if err := util.CopyTo(paths.New("apparmor.d/groups/_full/"), cfg.Root.Join("apparmor.d")); err != nil { + return res, err + } + + // Set systemd profile name + path := cfg.RootApparmord.Join("tunables/multiarch.d/system") + content, err := path.ReadFile() + if err != nil { + return res, err + } + out := strings.Replace(string(content), "@{systemd}=unconfined", "@{systemd}=systemd", -1) + out = strings.Replace(out, "@{systemd_user}=unconfined", "@{systemd_user}=systemd-user", -1) + if err := path.WriteFile([]byte(out)); err != nil { + return res, err + } + + // Fix conflicting x modifiers in abstractions - FIXME: Temporary solution + path = cfg.RootApparmord.Join("abstractions/gstreamer") + content, err = path.ReadFile() + 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 { + return res, err + } + + // Set systemd unit drop-in files + return res, util.CopyTo(cfg.SystemdDir.Join("full"), cfg.Root.Join("systemd")) +} diff --git a/pkg/prebuild/prepare/ignore.go b/pkg/prebuild/prepare/ignore.go new file mode 100644 index 00000000..23b14422 --- /dev/null +++ b/pkg/prebuild/prepare/ignore.go @@ -0,0 +1,59 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "strings" + + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +type Ignore struct { + cfg.Base +} + +func init() { + RegisterTask(&Ignore{ + Base: cfg.Base{ + Keyword: "ignore", + Msg: "Ignore profiles and files from:", + }, + }) +} + +func (p Ignore) Apply() ([]string, error) { + res := []string{} + for _, name := range []string{"main.ignore", cfg.Distribution + ".ignore"} { + path := cfg.DistDir.Join("ignore", name) + if !path.Exist() { + continue + } + lines, _ := path.ReadFileAsLines() + for _, line := range lines { + if strings.HasPrefix(line, "#") || line == "" { + continue + } + profile := cfg.Root.Join(line) + if profile.NotExist() { + files, err := cfg.RootApparmord.ReadDirRecursiveFiltered(nil, paths.FilterNames(line)) + if err != nil { + return res, err + } + for _, path := range files { + if err := path.RemoveAll(); err != nil { + return res, err + } + } + } else { + if err := profile.RemoveAll(); err != nil { + return res, err + } + } + } + res = append(res, path.String()) + } + return res, nil +} diff --git a/pkg/prebuild/prepare/merge.go b/pkg/prebuild/prepare/merge.go new file mode 100644 index 00000000..d88f5126 --- /dev/null +++ b/pkg/prebuild/prepare/merge.go @@ -0,0 +1,61 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "os" + "path/filepath" + + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +type Merge struct { + cfg.Base +} + +func init() { + RegisterTask(&Merge{ + Base: cfg.Base{ + Keyword: "merge", + Msg: "Merge all profiles into a unified apparmor.d directory", + }, + }) +} + +func (p Merge) Apply() ([]string, error) { + res := []string{} + dirToMerge := []string{ + "groups/*/*", "groups", + "profiles-*-*/*", "profiles-*", + } + + idx := 0 + for idx < len(dirToMerge)-1 { + dirMoved, dirRemoved := dirToMerge[idx], dirToMerge[idx+1] + files, err := filepath.Glob(cfg.RootApparmord.Join(dirMoved).String()) + if err != nil { + return res, err + } + for _, file := range files { + err := os.Rename(file, cfg.RootApparmord.Join(filepath.Base(file)).String()) + if err != nil { + return res, err + } + } + + files, err = filepath.Glob(cfg.RootApparmord.Join(dirRemoved).String()) + if err != nil { + return []string{}, err + } + for _, file := range files { + if err := paths.New(file).RemoveAll(); err != nil { + return res, err + } + } + idx = idx + 2 + } + return res, nil +} diff --git a/pkg/prebuild/prepare/synchronise.go b/pkg/prebuild/prepare/synchronise.go new file mode 100644 index 00000000..57e27a69 --- /dev/null +++ b/pkg/prebuild/prepare/synchronise.go @@ -0,0 +1,40 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +type Synchronise struct { + cfg.Base +} + +func init() { + RegisterTask(&Synchronise{ + Base: cfg.Base{ + Keyword: "synchronise", + Msg: "Initialize a new clean apparmor.d build directory", + }, + }) +} + +func (p Synchronise) Apply() ([]string, error) { + res := []string{} + dirs := paths.PathList{cfg.RootApparmord, cfg.Root.Join("root"), cfg.Root.Join("systemd")} + for _, dir := range dirs { + if err := dir.RemoveAll(); err != nil { + return res, err + } + } + for _, name := range []string{"apparmor.d", "root"} { + if err := util.CopyTo(paths.New(name), cfg.Root.Join(name)); err != nil { + return res, err + } + } + return res, nil +} diff --git a/pkg/prebuild/prepare/systemd.go b/pkg/prebuild/prepare/systemd.go new file mode 100644 index 00000000..5681783c --- /dev/null +++ b/pkg/prebuild/prepare/systemd.go @@ -0,0 +1,41 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prepare + +import ( + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +type SystemdDefault struct { + cfg.Base +} + +type SystemdEarly struct { + cfg.Base +} + +func init() { + RegisterTask(&SystemdDefault{ + Base: cfg.Base{ + Keyword: "systemd-default", + Msg: "Configure systemd unit drop in files to a profile for some units", + }, + }) + RegisterTask(&SystemdEarly{ + Base: cfg.Base{ + Keyword: "systemd-early", + Msg: "Configure systemd unit drop in files to ensure some service start after apparmor", + }, + }) +} + +func (p SystemdDefault) Apply() ([]string, error) { + return []string{}, util.CopyTo(cfg.SystemdDir.Join("default"), cfg.Root.Join("systemd")) +} + +func (p SystemdEarly) Apply() ([]string, error) { + return []string{}, util.CopyTo(cfg.SystemdDir.Join("early"), cfg.Root.Join("systemd")) +}