From a1c8260305f60ca42148e4bb3000fc259039cb9c Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Wed, 23 Oct 2024 14:48:47 +0100 Subject: [PATCH] build: add initial build structure for sub packages. --- cmd/prebuild/main.go | 2 - pkg/prebuild/builder/core.go | 19 ++- pkg/prebuild/cli/cli.go | 131 +++++++++++++++----- pkg/prebuild/directories.go | 45 ++++++- pkg/prebuild/packages.go | 231 +++++++++++++++++++++++++++++++++++ 5 files changed, 391 insertions(+), 37 deletions(-) create mode 100644 pkg/prebuild/packages.go diff --git a/cmd/prebuild/main.go b/cmd/prebuild/main.go index 3f2dd9f4..940be750 100644 --- a/cmd/prebuild/main.go +++ b/cmd/prebuild/main.go @@ -37,8 +37,6 @@ func init() { // Compatibility with AppArmor 3 switch prebuild.Distribution { - case "arch": - case "ubuntu": if !slices.Contains([]string{"noble"}, prebuild.Release["VERSION_CODENAME"]) { prebuild.ABI = 3 diff --git a/pkg/prebuild/builder/core.go b/pkg/prebuild/builder/core.go index b45075e1..cce8c94b 100644 --- a/pkg/prebuild/builder/core.go +++ b/pkg/prebuild/builder/core.go @@ -6,6 +6,8 @@ package builder import ( "fmt" + "slices" + "strings" "github.com/roddhjav/apparmor.d/pkg/paths" "github.com/roddhjav/apparmor.d/pkg/prebuild" @@ -33,7 +35,7 @@ type Option struct { func NewOption(file *paths.Path) *Option { return &Option{ - Name: file.Base(), + Name: strings.TrimSuffix(file.Base(), ".apparmor.d"), File: file, } } @@ -41,13 +43,26 @@ func NewOption(file *paths.Path) *Option { func Register(names ...string) { for _, name := range names { if b, present := Builders[name]; present { - Builds = append(Builds, b) + if !slices.Contains(Builds, b) { + Builds = append(Builds, b) + } } else { panic(fmt.Sprintf("Unknown builder: %s", name)) } } } +func Unregister(names ...string) { + for _, name := range names { + for i, b := range Builds { + if b.Name() == name { + Builds = slices.Delete(Builds, i, i+1) + break + } + } + } +} + func RegisterBuilder(d Builder) { Builders[d.Name()] = d } diff --git a/pkg/prebuild/cli/cli.go b/pkg/prebuild/cli/cli.go index 2821d52c..d192dd66 100644 --- a/pkg/prebuild/cli/cli.go +++ b/pkg/prebuild/cli/cli.go @@ -19,17 +19,19 @@ import ( const ( nilABI uint = 0 - usage = `aa-prebuild [-h] [--complain | --enforce] [--full] [--abi 3|4] + usage = `aa-prebuild [-h] [-s] [--complain|--enforce] [--packages] [--full] [--abi 3|4] - Prebuild apparmor.d profiles for a given distribution and apply - internal built-in directives. + Prebuild apparmor.d profiles for a given distribution, apply + internal built-in directives and build sub-packages structure. Options: -h, --help Show this help message and exit. -c, --complain Set complain flag on all profiles. - -e, --enforce Set enforce flag on all profiles. + -e, --enforce Set enforce flag on ALL profiles. -a, --abi ABI Target apparmor ABI. -f, --full Set AppArmor for full system policy. + -p, --packages Build all split packages. + -s, --status Show build configuration. -F, --file Only prebuild a given file. ` ) @@ -39,6 +41,7 @@ var ( complain bool enforce bool full bool + packages bool abi uint file string ) @@ -54,6 +57,8 @@ func init() { flag.BoolVar(&enforce, "enforce", false, "Set enforce flag on all profiles.") flag.UintVar(&abi, "a", nilABI, "Target apparmor ABI.") flag.UintVar(&abi, "abi", nilABI, "Target apparmor ABI.") + flag.BoolVar(&packages, "p", false, "Build all split packages.") + flag.BoolVar(&packages, "packages", false, "Build all split packages.") flag.StringVar(&file, "F", "", "Only prebuild a given file.") flag.StringVar(&file, "file", "", "Only prebuild a given file.") } @@ -79,12 +84,6 @@ func Prebuild() { prepare.Register("systemd-early") } - if complain { - builder.Register("complain") - } else if enforce { - builder.Register("enforce") - } - if abi != nilABI { prebuild.ABI = abi } @@ -104,13 +103,32 @@ func Prebuild() { overwrite.OneFile = true } - logging.Step("Building apparmor.d profiles for %s on ABI%d.", prebuild.Distribution, prebuild.ABI) + // Prepare the build directories + logging.Step("Building apparmor.d profiles for %s (abi%d).", prebuild.Distribution, prebuild.ABI) + prebuild.RootApparmord = prebuild.Root.Join(prebuild.Src) if err := Prepare(); err != nil { logging.Fatal("%s", err.Error()) } + + // Generate the packages + if packages { + if err := Packages(); err != nil { + logging.Fatal("%s", err.Error()) + } + } + + // Build the apparmor.d profiles if err := Build(); err != nil { logging.Fatal("%s", err.Error()) } + + if packages { + // Move all other profiles to apparmor.d.other + prebuild.RootApparmord = prebuild.Root.Join(prebuild.Src) + if err := prebuild.RootApparmord.Rename(prebuild.Root.Join("other")); err != nil { + logging.Fatal("%s", err.Error()) + } + } } func Prepare() error { @@ -136,26 +154,64 @@ func Prepare() error { return nil } +func Packages() error { + logging.Success("Building apparmor.d.* packages structure:") + + for _, name := range prebuild.Packages { + pkg := prebuild.NewPackage(name) + msg, err := pkg.Generate() + if err != nil { + return err + } + if err = pkg.Validate(); err != nil { + return err + } + logging.Indent = " " + logging.Bullet("apparmor.d.%s", name) + logging.Indent += " " + for _, line := range util.RemoveDuplicate(msg) { + logging.Warning("%s", line) + } + logging.Indent = "" + } + return nil +} + func Build() error { - files, _ := prebuild.RootApparmord.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) - for _, file := range files { - if !file.Exist() { - continue + sources := []string{prebuild.Src} + if packages { + sources = append(sources, prebuild.Packages...) + } + + for _, src := range sources { + prebuild.RootApparmord = prebuild.Root.Join(src) + if src == prebuild.Src { + setMode("") + } else { + pkg := prebuild.NewPackage(src) + setMode(pkg.Mode) } - profile, err := file.ReadFileAsString() - if err != nil { - return err - } - profile, err = builder.Run(file, profile) - if err != nil { - return err - } - profile, err = directive.Run(file, profile) - if err != nil { - return err - } - if err := file.WriteFile([]byte(profile)); err != nil { - return err + + files, _ := prebuild.RootApparmord.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + for _, file := range files { + if !file.Exist() { + continue + } + profile, err := file.ReadFileAsString() + if err != nil { + return err + } + profile, err = builder.Run(file, profile) + if err != nil { + return err + } + profile, err = directive.Run(file, profile) + if err != nil { + return err + } + if err := file.WriteFile([]byte(profile)); err != nil { + return err + } } } @@ -173,3 +229,20 @@ func Build() error { logging.Indent = "" return nil } + +func setMode(mode string) { + if mode == "" { + if complain { + mode = "complain" + } else if enforce { + mode = "enforce" + } + } + switch mode { + case "complain": + builder.Register("complain") + builder.Unregister("enforce") + case "enforce": + builder.Unregister("complain") + } +} diff --git a/pkg/prebuild/directories.go b/pkg/prebuild/directories.go index cd5958b7..38f3f53e 100644 --- a/pkg/prebuild/directories.go +++ b/pkg/prebuild/directories.go @@ -4,17 +4,28 @@ package prebuild -import "github.com/roddhjav/apparmor.d/pkg/paths" +import ( + "os" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/paths" +) var ( // AppArmor ABI version ABI uint = 0 - // Root is the root directory for the build (default: .build) - Root *paths.Path = paths.New(".build") + // Root is the root directory for the build (default: ./.build) + Root *paths.Path = getRootBuild() // RootApparmord is the final built apparmor.d directory (default: .build/apparmor.d) - RootApparmord *paths.Path = Root.Join("apparmor.d") + RootApparmord *paths.Path = Root.Join(Src) + + // src is the basename of the source directory (default: apparmor.d) + Src = "apparmor.d" + + // SrcApparmord is the source apparmor.d directory (default: ./apparmor.d) + SrcApparmord *paths.Path = paths.New(Src) // DistDir is the directory where the distribution specific files are stored DistDir *paths.Path = paths.New("dists") @@ -25,6 +36,9 @@ var ( // IgnoreDir is the directory where the ignore files are stored IgnoreDir *paths.Path = DistDir.Join("ignore") + // PkgDir is the directory where the packages files are stored + PkgDir *paths.Path = DistDir.Join("packages") + // SystemdDir is the directory where the systemd drop-in files are stored SystemdDir *paths.Path = paths.New("systemd") @@ -34,6 +48,29 @@ var ( // DebianHide is the path to the debian/apparmor.d.hide file DebianHide = DebianHider{path: DebianDir.Join("apparmor.d.hide")} + // Packages are the packages to build + Packages = getPackages() + Ignore = Ignorer{} Flags = Flagger{} ) + +func getRootBuild() *paths.Path { + root, present := os.LookupEnv("BUILD") + if !present { + root = ".build" + } + return paths.New(root) +} + +func getPackages() []string { + files, err := PkgDir.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + if err != nil { + panic(err) + } + packages := make([]string, 0, len(files)) + for _, file := range files { + packages = append(packages, strings.TrimSuffix(file.Base(), ".conf")) + } + return packages +} diff --git a/pkg/prebuild/packages.go b/pkg/prebuild/packages.go new file mode 100644 index 00000000..56d30c04 --- /dev/null +++ b/pkg/prebuild/packages.go @@ -0,0 +1,231 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package prebuild + +import ( + "fmt" + "slices" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/paths" +) + +type Package struct { + Name string + Mode string + Required []string + Profiles []string + Ignores []string + Ignored []string + builddir *paths.Path +} + +func NewPackage(name string) *Package { + path := PkgDir.Join(name + ".conf") + if !path.Exist() { + panic(fmt.Sprintf("Unknown package: %s", name)) + } + lines := path.MustReadFilteredFileAsLines() + mode := "" + profiles := make([]string, 0, len(lines)) + ignores := []string{} + dependencies := []string{} + ignored := getFilesIgnoredByDistribution() + for _, line := range lines { + switch { + case strings.HasPrefix(line, "mode="): + mode = strings.TrimPrefix(line, "mode=") + case strings.HasPrefix(line, "require="): + dependencies = strings.Split(strings.TrimPrefix(line, "require="), ",") + case strings.HasPrefix(line, "!"): + ignores = append(ignores, strings.TrimPrefix(line, "!")) + default: + profiles = append(profiles, line) + } + } + return &Package{ + Name: name, + Mode: mode, + Required: dependencies, + Profiles: profiles, + Ignores: ignores, + Ignored: ignored, + builddir: Root.Join(name), + } +} + +func getFilesIgnoredByDistribution() []string { + res := []string{} + for _, iname := range []string{"main", Distribution} { + for _, ignore := range Ignore.Read(iname) { + if !strings.HasPrefix(ignore, Src) { + continue + } + profile := strings.TrimPrefix(ignore, Src+"/") + path := SrcApparmord.Join(profile) + if path.IsDir() { + files, err := path.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + if err != nil { + panic(err) + } + for _, file := range files { + res = append(res, file.Base()) + } + } else if path.Exist() { + res = append(res, path.Base()) + } else { + panic(fmt.Errorf("%s.ignore: no files found for '%s'", iname, profile)) + } + } + } + return res +} + +func (p *Package) Generate() ([]string, error) { + var res []string + + if err := p.builddir.RemoveAll(); err != nil { + return res, err + } + if err := p.builddir.MkdirAll(); err != nil { + return res, err + } + + explode := paths.PathList{ + paths.New("groups"), paths.New("profiles-a-f"), + paths.New("profiles-m-r"), paths.New("profiles-s-z"), + } + for _, name := range p.Profiles { + originalPath := SrcApparmord.Join(name) + + if originalPath.IsDir() { + originFiles, err := originalPath.ReadDirRecursiveFiltered(nil, paths.FilterOutDirectories()) + if err != nil { + return res, err + } + for _, originFile := range originFiles { + file, err := originFile.RelFrom(SrcApparmord) + if err != nil { + return res, err + } + + if slices.Contains(p.Ignores, file.String()) { + continue + } + + done := false + for _, e := range explode { + if ok, _ := file.IsInsideDir(e); ok { + base := file.Base() + msg, err := p.move(base) + if err != nil { + return res, err + } + res = append(res, msg) + done = true + break + } + } + + if !done { + msg, err := p.move(file) + if err != nil { + return res, err + } + res = append(res, msg) + } + } + + } else if originalPath.Exist() { + base := originalPath.Base() + if slices.Contains(p.Ignores, base) { + continue + } + msg, err := p.move(base) + if err != nil { + return res, err + } + res = append(res, msg) + + } else { + return res, fmt.Errorf("No %s", originalPath) + } + } + return res, nil +} + +func (p *Package) move(origin any) (string, error) { + var src *paths.Path + var dst *paths.Path + var srcOverridden *paths.Path + var dstOverridden *paths.Path + var srcSymlink *paths.Path + var dstSymlink *paths.Path + const ext = ".apparmor.d" + + switch value := any(origin).(type) { + case string: + src = RootApparmord.Join(value) + dst = p.builddir.Join(value) + srcOverridden = RootApparmord.Join(value + ext) + dstOverridden = p.builddir.Join(value + ext) + srcSymlink = RootApparmord.Join("disable", value) + dstSymlink = p.builddir.Join("disable", value) + + case *paths.Path: + src = RootApparmord.JoinPath(value) + dst = p.builddir.JoinPath(value) + srcOverridden = RootApparmord.JoinPath(value.Parent()).Join(value.Base() + ext) + dstOverridden = p.builddir.JoinPath(value.Parent()).Join(value.Base() + ext) + srcSymlink = RootApparmord.Join("disable").JoinPath(value) + dstSymlink = p.builddir.Join("disable").JoinPath(value) + + default: + panic("Package.move: unsupported type") + } + + if src.Exist() { + if err := dst.Parent().MkdirAll(); err != nil { + return "", nil + } + if err := src.Rename(dst); err != nil { + return "", nil + } + // fmt.Printf("%s -> %s\n", src, dst) + + } else if srcOverridden.Exist() { + if err := dstOverridden.Parent().MkdirAll(); err != nil { + return "", nil + } + if err := dstSymlink.Parent().MkdirAll(); err != nil { + return "", nil + } + if err := srcOverridden.Rename(dstOverridden); err != nil { + return "", nil + } + if err := srcSymlink.Rename(dstSymlink); err != nil { + return "", nil + } + // fmt.Printf("%s -> %s\n", srcOverridden, dstOverridden) + + } else { + srcRltv, err := src.RelFrom(RootApparmord) + if err != nil { + return "", nil + } + if !slices.Contains(p.Ignored, srcRltv.String()) { + fmt.Printf("Warning: No %s\n", src) + // return "", fmt.Errorf("No %s", src) + } + + } + return "", nil +} + +// Validate ensures a package has its required dependencies +func (p *Package) Validate() error { + return nil +} +