diff --git a/pkg/prebuild/builder/abi.go b/pkg/prebuild/builder/abi.go new file mode 100644 index 00000000..4790ba4c --- /dev/null +++ b/pkg/prebuild/builder/abi.go @@ -0,0 +1,35 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +var ( + regAbi4To3 = util.ToRegexRepl([]string{ // Currently Abi3 -> Abi4 + `abi/3.0`, `abi/4.0`, + `# userns,`, `userns,`, + `# mqueue`, `mqueue`, + }) +) + +type ABI3 struct { + cfg.Base +} + +func init() { + RegisterBuilder(&ABI3{ + Base: cfg.Base{ + Keyword: "abi3", + Msg: "Convert all profiles from abi 4.0 to abi 3.0", + }, + }) +} + +func (b ABI3) Apply(profile string) string { + return regAbi4To3.Replace(profile) +} diff --git a/pkg/prebuild/builder/complain.go b/pkg/prebuild/builder/complain.go new file mode 100644 index 00000000..3970e6df --- /dev/null +++ b/pkg/prebuild/builder/complain.go @@ -0,0 +1,48 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "regexp" + "slices" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +var ( + regFlags = regexp.MustCompile(`flags=\(([^)]+)\)`) + regProfileHeader = regexp.MustCompile(` {`) +) + +type Complain struct { + cfg.Base +} + +func init() { + RegisterBuilder(&Complain{ + Base: cfg.Base{ + Keyword: "complain", + Msg: "Set complain flag on all profiles", + }, + }) +} + +func (b Complain) Apply(profile string) string { + flags := []string{} + matches := regFlags.FindStringSubmatch(profile) + if len(matches) != 0 { + flags = strings.Split(matches[1], ",") + if slices.Contains(flags, "complain") { + return profile + } + } + flags = append(flags, "complain") + strFlags := " flags=(" + strings.Join(flags, ",") + ") {" + + // Remove all flags definition, then set manifest' flags + profile = regFlags.ReplaceAllLiteralString(profile, "") + return regProfileHeader.ReplaceAllLiteralString(profile, strFlags) +} diff --git a/pkg/prebuild/builder/core.go b/pkg/prebuild/builder/core.go new file mode 100644 index 00000000..b8dbcbc8 --- /dev/null +++ b/pkg/prebuild/builder/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 builder + +import ( + "fmt" + + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +var ( + // Build the profiles with the following directive applied + Builds = []Builder{} + + // Available builders + Builders = map[string]Builder{} +) + +// Main directive interface +type Builder interface { + cfg.BaseInterface + Apply(profile string) string +} + +func Register(names ...string) { + for _, name := range names { + if b, present := Builders[name]; present { + Builds = append(Builds, b) + } else { + panic(fmt.Sprintf("Unknown builder: %s", name)) + } + } +} + +func RegisterBuilder(d Builder) { + Builders[d.Name()] = d +} diff --git a/pkg/prebuild/builder/core_test.go b/pkg/prebuild/builder/core_test.go new file mode 100644 index 00000000..b0c59e77 --- /dev/null +++ b/pkg/prebuild/builder/core_test.go @@ -0,0 +1,271 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "slices" + "testing" +) + +func TestBuilder_Apply(t *testing.T) { + tests := []struct { + name string + b Builder + profile string + want string + }{ + { + name: "abi3", + b: Builders["abi3"], + profile: ` + abi , + profile test { + # userns, + # mqueue r type=posix /, + }`, + want: ` + abi , + profile test { + userns, + mqueue r type=posix /, + }`, + }, + { + name: "complain-1", + b: Builders["complain"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(complain) { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "complain-2", + b: Builders["complain"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(complain) { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(complain) { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "complain-3", + b: Builders["complain"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(attach_disconnected) { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(attach_disconnected,complain) { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "enforce-1", + b: Builders["enforce"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "enforce-2", + b: Builders["enforce"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(complain) { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "complain-3", + b: Builders["enforce"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(attach_disconnected,complain) { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} flags=(attach_disconnected) { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "fsp", + b: Builders["fsp"], + profile: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + @{bin}/* rPUx, + @{lib}/* rUx, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/foo + profile foo @{exec_path} { + include + + @{exec_path} mr, + @{bin}/* rPx, + @{lib}/* rPx, + + include if exists + }`, + }, + { + name: "userspace-1", + b: Builders["userspace"], + profile: ` + @{exec_path} = @{bin}/baloo_file @{lib}/{,kf6/}baloo_file + @{exec_path} += @{lib}/@{multiarch}/{,libexec/}baloo_file + profile baloo @{exec_path} { + include + + @{exec_path} mr, + + include if exists + }`, + want: ` + @{exec_path} = @{bin}/baloo_file @{lib}/{,kf6/}baloo_file + @{exec_path} += @{lib}/@{multiarch}/{,libexec/}baloo_file + profile baloo /{{,usr/}{,s}bin/baloo_file,{,usr/}lib{,exec,32,64}/{,kf6/}baloo_file,{,usr/}lib{,exec,32,64}/*-linux-gnu*/{,libexec/}baloo_file} { + include + + @{exec_path} mr, + + include if exists + }`, + }, + { + name: "userspace-1", + b: Builders["userspace"], + profile: ` + profile foo /usr/bin/foo { + include + + /usr/bin/foo mr, + + include if exists + }`, + want: ` + profile foo /usr/bin/foo { + include + + /usr/bin/foo mr, + + include if exists + }`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.b.Apply(tt.profile); got != tt.want { + t.Errorf("Builder.Apply() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRegister(t *testing.T) { + tests := []struct { + name string + names []string + wantSuccess bool + }{ + { + name: "test", + names: []string{"complain", "enforce"}, + 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(Builds, Builders[name]); got != tt.wantSuccess { + t.Errorf("Register() = %v, want %v", got, tt.wantSuccess) + } + + } + }) + } +} diff --git a/pkg/prebuild/builder/enforce.go b/pkg/prebuild/builder/enforce.go new file mode 100644 index 00000000..a3bd2c1d --- /dev/null +++ b/pkg/prebuild/builder/enforce.go @@ -0,0 +1,47 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "slices" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +type Enforce struct { + cfg.Base +} + +func init() { + RegisterBuilder(&Enforce{ + Base: cfg.Base{ + Keyword: "enforce", + Msg: "All profiles have been enforced", + }, + }) +} + +func (b Enforce) Apply(profile string) string { + matches := regFlags.FindStringSubmatch(profile) + if len(matches) == 0 { + return profile + } + + flags := strings.Split(matches[1], ",") + idx := slices.Index(flags, "complain") + if idx == -1 { + return profile + } + flags = slices.Delete(flags, idx, idx+1) + strFlags := "{" + if len(flags) >= 1 { + strFlags = " flags=(" + strings.Join(flags, ",") + ") {" + } + + // Remove all flags definition, then set new flags + profile = regFlags.ReplaceAllLiteralString(profile, "") + return regProfileHeader.ReplaceAllLiteralString(profile, strFlags) +} diff --git a/pkg/prebuild/builder/fsp.go b/pkg/prebuild/builder/fsp.go new file mode 100644 index 00000000..07bbbb8a --- /dev/null +++ b/pkg/prebuild/builder/fsp.go @@ -0,0 +1,33 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +var ( + regFullSystemPolicy = util.ToRegexRepl([]string{ + `r(PU|U)x,`, `rPx,`, + }) +) + +type FullSystemPolicy struct { + cfg.Base +} + +func init() { + RegisterBuilder(&FullSystemPolicy{ + Base: cfg.Base{ + Keyword: "fsp", + Msg: "Prevent unconfined transitions in profile rules", + }, + }) +} + +func (b FullSystemPolicy) Apply(profile string) string { + return regFullSystemPolicy.Replace(profile) +} diff --git a/pkg/prebuild/builder/userspace.go b/pkg/prebuild/builder/userspace.go new file mode 100644 index 00000000..702fd56d --- /dev/null +++ b/pkg/prebuild/builder/userspace.go @@ -0,0 +1,43 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package builder + +import ( + "regexp" + "strings" + + "github.com/roddhjav/apparmor.d/pkg/aa" + "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" +) + +var ( + regAttachments = regexp.MustCompile(`(profile .* @{exec_path})`) +) + +type Userspace struct { + cfg.Base +} + +func init() { + RegisterBuilder(&Userspace{ + Base: cfg.Base{ + Keyword: "userspace", + Msg: "Bypass userspace tools restriction", + }, + }) +} + +func (b Userspace) Apply(profile string) string { + p := aa.DefaultTunables() + p.ParseVariables(profile) + p.ResolveAttachments() + att := p.NestAttachments() + matches := regAttachments.FindAllString(profile, -1) + if len(matches) > 0 { + strheader := strings.Replace(matches[0], "@{exec_path}", att, -1) + return regAttachments.ReplaceAllLiteralString(profile, strheader) + } + return profile +}