From 913ac3131cbcf36aba2bdcfc306cb0d927278453 Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Sat, 6 May 2023 13:01:07 +0100 Subject: [PATCH] feat(prebuild): make prebuild available as an external package. Usefull for downstream repo. --- cmd/aa-log/main.go | 2 +- cmd/prebuild/main.go | 86 ++++++------------ cmd/prebuild/main_test.go | 28 +++--- go.mod | 4 +- go.sum | 4 + {cmd => pkg}/prebuild/build.go | 25 +++--- pkg/prebuild/prebuild.go | 50 +++++++++++ pkg/prebuild/prebuild_test.go | 87 +++++++++++++++++++ {cmd => pkg}/prebuild/prepare.go | 69 ++++----------- pkg/prebuild/tools.go | 78 +++++++++++++++++ .../os_test.go => prebuild/tools_test.go} | 30 +++---- pkg/util/os.go | 47 ---------- pkg/util/tools.go | 8 -- 13 files changed, 304 insertions(+), 214 deletions(-) rename {cmd => pkg}/prebuild/build.go (81%) create mode 100644 pkg/prebuild/prebuild.go create mode 100644 pkg/prebuild/prebuild_test.go rename {cmd => pkg}/prebuild/prepare.go (78%) create mode 100644 pkg/prebuild/tools.go rename pkg/{util/os_test.go => prebuild/tools_test.go} (83%) delete mode 100644 pkg/util/os.go diff --git a/cmd/aa-log/main.go b/cmd/aa-log/main.go index 41f114a15..cd6ae7781 100644 --- a/cmd/aa-log/main.go +++ b/cmd/aa-log/main.go @@ -48,7 +48,7 @@ func aaLog(logger string, path string, profile string, anonymize bool) error { case "auditd": file, err = logs.GetAuditLogs(path) case "systemd": - file, err = logs.GetJournalctlLogs(path, !util.InSlice(path, logs.LogFiles)) + file, err = logs.GetJournalctlLogs(path, !slices.Contains(logs.LogFiles, path)) default: err = fmt.Errorf("Logger %s not supported.", logger) } diff --git a/cmd/prebuild/main.go b/cmd/prebuild/main.go index ac65a4d90..0808e2a8c 100644 --- a/cmd/prebuild/main.go +++ b/cmd/prebuild/main.go @@ -9,9 +9,9 @@ import ( "fmt" "os" - "github.com/arduino/go-paths-helper" "github.com/roddhjav/apparmor.d/pkg/logging" - "github.com/roddhjav/apparmor.d/pkg/util" + "github.com/roddhjav/apparmor.d/pkg/prebuild" + "golang.org/x/exp/slices" ) const usage = `prebuild [-h] [--full] [--complain] @@ -20,82 +20,53 @@ const usage = `prebuild [-h] [--full] [--complain] Options: -h, --help Show this help message and exit. - -d, --dist The target Linux distribution. -f, --full Set AppArmor for full system policy. -c, --complain Set complain flag on all profiles. ` var ( - help bool - Full bool - Complain bool - Distribution string - DistDir *paths.Path - Root *paths.Path - RootApparmord *paths.Path - - // Prepare the build directory with the following tasks - prepare = []prepareFunc{Synchronise, Ignore, Merge, Configure, SetFlags, SetFullSystemPolicy} - - // Build the profiles with the following build tasks - build = []buildFunc{BuildUserspace, BuildComplain, BuildABI} + help bool + full bool + complain bool ) -type prepareFunc func() error -type buildFunc func(string) string - func init() { - DistDir = paths.New("dists") - Root = paths.New(".build") - RootApparmord = Root.Join("apparmor.d") - Distribution, _ = util.GetSupportedDistribution() flag.BoolVar(&help, "h", false, "Show this help message and exit.") flag.BoolVar(&help, "help", false, "Show this help message and exit.") - flag.BoolVar(&Full, "f", false, "Set AppArmor for full system policy.") - flag.BoolVar(&Full, "full", false, "Set AppArmor for full system policy.") - flag.BoolVar(&Complain, "c", false, "Set complain flag on all profiles.") - flag.BoolVar(&Complain, "complain", false, "Set complain flag on all profiles.") -} - -// Build the profiles. -func buildProfiles() error { - files, _ := RootApparmord.ReadDir(paths.FilterOutDirectories()) - for _, file := range files { - if !file.Exist() { - continue - } - content, _ := file.ReadFile() - profile := string(content) - for _, fct := range build { - profile = fct(profile) - } - if err := file.WriteFile([]byte(profile)); err != nil { - panic(err) - } - } - return nil + flag.BoolVar(&full, "f", false, "Set AppArmor for full system policy.") + flag.BoolVar(&full, "full", false, "Set AppArmor for full system policy.") + flag.BoolVar(&complain, "c", false, "Set complain flag on all profiles.") + flag.BoolVar(&complain, "complain", false, "Set complain flag on all profiles.") } func aaPrebuild() error { - logging.Step("Building apparmor.d profiles for %s.", Distribution) + logging.Step("Building apparmor.d profiles for %s.", prebuild.Distribution) - for _, fct := range prepare { - if err := fct(); err != nil { - return err - } + if full { + prebuild.Prepares = append(prebuild.Prepares, prebuild.SetFullSystemPolicy) + } + if complain { + prebuild.Builds = append(prebuild.Builds, prebuild.BuildComplain) + } + if slices.Contains([]string{"debian", "whonix"}, prebuild.Distribution) { + prebuild.Builds = append(prebuild.Builds, prebuild.BuildABI) } - if err := buildProfiles(); err != nil { + if err := prebuild.Prepare(); err != nil { return err } + + if err := prebuild.Build(); err != nil { + return err + } + logging.Success("Builded profiles with: ") logging.Bullet("Bypass userspace tools restriction") - if Complain { + if complain { logging.Bullet("Set complain flag on all profiles") } - switch Distribution { - case "debian", "whonix": - logging.Bullet("%s does not support abi 3.0 yet", Distribution) + if slices.Contains([]string{"debian", "whonix"}, prebuild.Distribution) { + logging.Bullet("%s does not support abi 3.0 yet", prebuild.Distribution) } return nil } @@ -107,8 +78,7 @@ func main() { flag.Usage() os.Exit(0) } - err := aaPrebuild() - if err != nil { + if err := aaPrebuild(); err != nil { logging.Fatal(err.Error()) } } diff --git a/cmd/prebuild/main_test.go b/cmd/prebuild/main_test.go index 46dc54db5..4b5d34d83 100644 --- a/cmd/prebuild/main_test.go +++ b/cmd/prebuild/main_test.go @@ -8,6 +8,8 @@ import ( "os" "os/exec" "testing" + + "github.com/roddhjav/apparmor.d/pkg/prebuild" ) func chdirGitRoot() { @@ -22,7 +24,7 @@ func chdirGitRoot() { } } -func Test_aaPrebuild(t *testing.T) { +func Test_AAPrebuild(t *testing.T) { tests := []struct { name string wantErr bool @@ -58,20 +60,24 @@ func Test_aaPrebuild(t *testing.T) { complain: true, dist: "opensuse", }, - { - name: "Build for Fedora", - wantErr: true, - full: false, - complain: false, - dist: "fedora", - }, + // { + // name: "Build for Fedora", + // wantErr: true, + // full: false, + // complain: false, + // dist: "fedora", + // }, } chdirGitRoot() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - Distribution = tt.dist - Complain = tt.complain - Full = tt.full + prebuild.Distribution = tt.dist + if tt.full { + prebuild.Prepares = append(prebuild.Prepares, prebuild.SetFullSystemPolicy) + } + if tt.complain { + prebuild.Builds = append(prebuild.Builds, prebuild.BuildComplain) + } if err := aaPrebuild(); (err != nil) != tt.wantErr { t.Errorf("aaPrebuild() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/go.mod b/go.mod index f712b883a..fecd2f72d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/arduino/go-paths-helper v1.8.0 + github.com/pkg/errors v0.9.1 golang.org/x/exp v0.0.0-20230418202329-0354be287a23 + gopkg.in/yaml.v2 v2.4.0 ) - -require github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 2ef380783..8ac6353f3 100644 --- a/go.sum +++ b/go.sum @@ -11,3 +11,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/exp v0.0.0-20230418202329-0354be287a23 h1:4NKENAGIctmZYLK9W+X1kDK8ObBFqOSCJM6WE7CvkJY= golang.org/x/exp v0.0.0-20230418202329-0354be287a23/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/cmd/prebuild/build.go b/pkg/prebuild/build.go similarity index 81% rename from cmd/prebuild/build.go rename to pkg/prebuild/build.go index 75046593f..62b14a329 100644 --- a/cmd/prebuild/build.go +++ b/pkg/prebuild/build.go @@ -2,16 +2,21 @@ // Copyright (C) 2023 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only -package main +package prebuild import ( "regexp" "strings" "github.com/roddhjav/apparmor.d/pkg/aa" - "github.com/roddhjav/apparmor.d/pkg/util" + "golang.org/x/exp/slices" ) +// Build the profiles with the following build tasks +var Builds = []BuildFunc{ + BuildUserspace, +} + var ( regABI = regexp.MustCompile(`abi ,\n`) regAttachments = regexp.MustCompile(`(profile .* @{exec_path})`) @@ -19,17 +24,16 @@ var ( regProfileHeader = regexp.MustCompile(` {`) ) +type BuildFunc func(string) string + // Set complain flag on all profiles func BuildComplain(profile string) string { - if !Complain { - return profile - } flags := []string{} matches := regFlag.FindStringSubmatch(profile) if len(matches) != 0 { flags = strings.Split(matches[1], ",") - if util.InSlice("complain", flags) { + if slices.Contains(flags, "complain") { return profile } } @@ -55,12 +59,7 @@ func BuildUserspace(profile string) string { return profile } -// Remove abi header for distributions that don't support it +// Remove abi header for distributions that do not support it func BuildABI(profile string) string { - switch Distribution { - case "debian", "whonix": - return regABI.ReplaceAllLiteralString(profile, "") - default: - return profile - } + return regABI.ReplaceAllLiteralString(profile, "") } diff --git a/pkg/prebuild/prebuild.go b/pkg/prebuild/prebuild.go new file mode 100644 index 000000000..a02c2424d --- /dev/null +++ b/pkg/prebuild/prebuild.go @@ -0,0 +1,50 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2023 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prebuild + +import ( + "github.com/arduino/go-paths-helper" +) + +var ( + Distribution string + DistDir *paths.Path + Root *paths.Path + RootApparmord *paths.Path +) + +func init() { + DistDir = paths.New("dists") + Root = paths.New(".build") + RootApparmord = Root.Join("apparmor.d") + Distribution = getSupportedDistribution() +} + +func Prepare() error { + for _, fct := range Prepares { + if err := fct(); err != nil { + return err + } + } + return nil +} + +func Build() error { + files, _ := RootApparmord.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + for _, file := range files { + if !file.Exist() { + continue + } + content, _ := file.ReadFile() + profile := string(content) + for _, fct := range Builds { + profile = fct(profile) + } + if err := file.WriteFile([]byte(profile)); err != nil { + panic(err) + } + } + return nil +} diff --git a/pkg/prebuild/prebuild_test.go b/pkg/prebuild/prebuild_test.go new file mode 100644 index 000000000..8604a7f4b --- /dev/null +++ b/pkg/prebuild/prebuild_test.go @@ -0,0 +1,87 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2023 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prebuild + +import ( + "os" + "os/exec" + "testing" +) + +func chdirGitRoot() { + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + out, err := cmd.Output() + if err != nil { + panic(err) + } + root := string(out)[0 : len(out)-1] + if err := os.Chdir(root); err != nil { + panic(err) + } +} + +func Test_PreBuild(t *testing.T) { + tests := []struct { + name string + wantErr bool + full bool + complain bool + dist string + }{ + { + name: "Build for Archlinux", + wantErr: false, + full: false, + complain: true, + dist: "arch", + }, + { + name: "Build for Ubuntu", + wantErr: false, + full: true, + complain: false, + dist: "ubuntu", + }, + { + name: "Build for Debian", + wantErr: false, + full: true, + complain: false, + dist: "debian", + }, + { + name: "Build for OpenSUSE Tumbleweed", + wantErr: false, + full: true, + complain: true, + dist: "opensuse", + }, + // { + // name: "Build for Fedora", + // wantErr: true, + // full: false, + // complain: false, + // dist: "fedora", + // }, + } + chdirGitRoot() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + Distribution = tt.dist + if tt.full { + Prepares = append(Prepares, SetFullSystemPolicy) + } + if tt.complain { + Builds = append(Builds, BuildComplain) + } + if err := Prepare(); (err != nil) != tt.wantErr { + t.Errorf("Prepare() error = %v, wantErr %v", err, tt.wantErr) + } + if err := Build(); (err != nil) != tt.wantErr { + t.Errorf("Build() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cmd/prebuild/prepare.go b/pkg/prebuild/prepare.go similarity index 78% rename from cmd/prebuild/prepare.go rename to pkg/prebuild/prepare.go index 223ff2d02..8cae7f6b4 100644 --- a/cmd/prebuild/prepare.go +++ b/pkg/prebuild/prepare.go @@ -2,7 +2,7 @@ // Copyright (C) 2023 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only -package main +package prebuild import ( "fmt" @@ -12,10 +12,20 @@ import ( "strings" "github.com/arduino/go-paths-helper" - "github.com/roddhjav/apparmor.d/pkg/aa" "github.com/roddhjav/apparmor.d/pkg/logging" ) +// Prepare the build directory with the following tasks +var Prepares = []PrepareFunc{ + Synchronise, + Ignore, + Merge, + Configure, + SetFlags, +} + +type PrepareFunc func() error + // Initialize a new clean apparmor.d build directory func Synchronise() error { dirs := paths.PathList{RootApparmord, Root.Join("root")} @@ -105,17 +115,13 @@ func Merge() error { } // Set the distribution specificities -func Configure() error { +func Configure() (err error) { switch Distribution { case "arch": - if err := setLibexec("/{usr/,}lib"); err != nil { - return err - } + err = setLibexec("/{usr/,}lib") case "opensuse": - if err := setLibexec("/{usr/,}libexec"); err != nil { - return err - } + err = setLibexec("/{usr/,}libexec") case "debian", "ubuntu", "whonix": if err := setLibexec("/{usr/,}libexec"); err != nil { @@ -135,54 +141,13 @@ func Configure() error { return err } - // Remove ABI on abstractions files - files, _ := RootApparmord.Join("abstractions").ReadDir(paths.FilterOutDirectories()) - for _, file := range files { - if !file.Exist() { - continue - } - content, _ := file.ReadFile() - profile := BuildABI(string(content)) - if err := file.WriteFile([]byte(profile)); err != nil { - return err - } - } default: return fmt.Errorf("%s is not a supported distribution", Distribution) } - return nil -} - -func setLibexec(libexec string) error { - aa.Tunables["libexec"] = []string{libexec} - file, err := RootApparmord.Join("tunables", "multiarch.d", "apparmor.d").Append() - if err != nil { - return err - } - defer file.Close() - _, err = file.WriteString(`@{libexec}=` + libexec) return err } -func copyTo(src *paths.Path, dst *paths.Path) error { - files, err := src.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) - if err != nil { - return err - } - for _, file := range files { - destination, err := file.RelFrom(src) - if err != nil { - return err - } - destination = dst.JoinPath(destination) - if err := file.CopyTo(destination); err != nil { - return err - } - } - return nil -} - // Set flags on some profiles according to manifest defined in `dists/flags/` func SetFlags() error { for _, name := range []string{"main.flags", Distribution + ".flags"} { @@ -228,10 +193,6 @@ func SetFlags() error { // See https://gitlab.com/apparmor/apparmor/-/wikis/FullSystemPolicy // https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorInSystemd#early-policy-loads func SetFullSystemPolicy() error { - if !Full { - return nil - } - for _, name := range []string{"init", "systemd"} { err := paths.New("apparmor.d/groups/_full/" + name).CopyTo(RootApparmord.Join(name)) if err != nil { diff --git a/pkg/prebuild/tools.go b/pkg/prebuild/tools.go new file mode 100644 index 000000000..2240f538e --- /dev/null +++ b/pkg/prebuild/tools.go @@ -0,0 +1,78 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2023 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prebuild + +import ( + "os" + "strings" + + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/aa" + "golang.org/x/exp/slices" +) + +var ( + osReleaseFile = "/etc/os-release" + firstPartyDists = []string{"arch", "debian", "ubuntu", "opensuse", "whonix"} +) + +func getSupportedDistribution() string { + dist, present := os.LookupEnv("DISTRIBUTION") + if present { + return dist + } + + lines, err := paths.New(osReleaseFile).ReadFileAsLines() + if err != nil { + panic(err) + } + + id := "" + id_like := "" + for _, line := range lines { + item := strings.Split(line, "=") + if item[0] == "ID" { + id = strings.Split(strings.Trim(item[1], "\""), " ")[0] + } else if item[0] == "ID_LIKE" { + id_like = strings.Split(strings.Trim(item[1], "\""), " ")[0] + } + } + + if slices.Contains(firstPartyDists, id) { + return id + } else if slices.Contains(firstPartyDists, id_like) { + return id_like + } + return id +} + +func setLibexec(libexec string) error { + aa.Tunables["libexec"] = []string{libexec} + file, err := RootApparmord.Join("tunables", "multiarch.d", "apparmor.d").Append() + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(`@{libexec}=` + libexec) + return err +} + +func copyTo(src *paths.Path, dst *paths.Path) error { + files, err := src.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + if err != nil { + return err + } + for _, file := range files { + destination, err := file.RelFrom(src) + if err != nil { + return err + } + destination = dst.JoinPath(destination) + if err := file.CopyTo(destination); err != nil { + return err + } + } + return nil +} diff --git a/pkg/util/os_test.go b/pkg/prebuild/tools_test.go similarity index 83% rename from pkg/util/os_test.go rename to pkg/prebuild/tools_test.go index 82cec0901..17834d509 100644 --- a/pkg/util/os_test.go +++ b/pkg/prebuild/tools_test.go @@ -2,7 +2,7 @@ // Copyright (C) 2023 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only -package util +package prebuild import ( "testing" @@ -78,59 +78,49 @@ ANSI_COLOR="0;38;2;60;110;180" LOGO=fedora-logo-icon` ) -func TestGetSupportedDistribution(t *testing.T) { +func Test_getSupportedDistribution(t *testing.T) { tests := []struct { name string osRelease string want string - wantErr bool }{ { name: "Archlinux", osRelease: Archlinux, want: "arch", - wantErr: false, }, { name: "Ubuntu", osRelease: Ubuntu, want: "ubuntu", - wantErr: false, }, { name: "Debian", osRelease: Debian, want: "debian", - wantErr: false, }, { name: "OpenSUSE Tumbleweed", osRelease: OpenSUSETumbleweed, want: "opensuse", - wantErr: false, - }, - { - name: "Fedora", - osRelease: Fedora, - want: "fedora", - wantErr: true, }, + // { + // name: "Fedora", + // osRelease: Fedora, + // want: "fedora", + // }, } - osReleaseFile = "os-release" + osReleaseFile = "/tmp/os-release" for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := paths.New(osReleaseFile).WriteFile([]byte(tt.osRelease)) if err != nil { return } - got, err := GetSupportedDistribution() - if (err != nil) != tt.wantErr { - t.Errorf("ReadLinuxDistribution() error = %v, wantErr %v", err, tt.wantErr) - return - } + got := getSupportedDistribution() if got != tt.want { - t.Errorf("ReadLinuxDistribution() = %v, want %v", got, tt.want) + t.Errorf("getSupportedDistribution() = %v, want %v", got, tt.want) } }) } diff --git a/pkg/util/os.go b/pkg/util/os.go deleted file mode 100644 index 0e12ab6c9..000000000 --- a/pkg/util/os.go +++ /dev/null @@ -1,47 +0,0 @@ -// apparmor.d - Full set of apparmor profiles -// Copyright (C) 2023 Alexandre Pujol -// SPDX-License-Identifier: GPL-2.0-only - -package util - -import ( - "fmt" - "os" - "strings" - - "github.com/arduino/go-paths-helper" -) - -var osReleaseFile = "/etc/os-release" - -var firstPartyDists = []string{"arch", "debian", "ubuntu", "opensuse", "whonix"} - -func GetSupportedDistribution() (string, error) { - dist, present := os.LookupEnv("DISTRIBUTION") - if present { - return dist, nil - } - - lines, err := paths.New(osReleaseFile).ReadFileAsLines() - if err != nil { - return "", err - } - - id := "" - id_like := "" - for _, line := range lines { - item := strings.Split(line, "=") - if item[0] == "ID" { - id = strings.Split(strings.Trim(item[1], "\""), " ")[0] - } else if item[0] == "ID_LIKE" { - id_like = strings.Split(strings.Trim(item[1], "\""), " ")[0] - } - } - - if InSlice(id, firstPartyDists) { - return id, nil - } else if InSlice(id_like, firstPartyDists) { - return id_like, nil - } - return id, fmt.Errorf("%s is not a supported distribution", id) -} diff --git a/pkg/util/tools.go b/pkg/util/tools.go index ff1dd1f0e..2780d0483 100644 --- a/pkg/util/tools.go +++ b/pkg/util/tools.go @@ -7,7 +7,6 @@ package util import ( "encoding/hex" "regexp" - "sort" ) var isHexa = regexp.MustCompile("^[0-9A-Fa-f]+$") @@ -35,10 +34,3 @@ func RemoveDuplicate[T comparable](inlist []T) []T { } return list } - -func InSlice(item string, slice []string) bool { - sort.Strings(slice) - i := sort.SearchStrings(slice, item) - return i < len(slice) && slice[i] == item -} -