From aa2b3fddd370df6bc6b8174444aa742be27ac344 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 25 Sep 2026 10:08:59 +0200 Subject: [PATCH] apparmor: validate generated profiles with apparmor_parser Add a helper that parses generated profiles with apparmor_parser without loading them into the kernel, and use it for golden profile tests where the referenced includes are available. This gives the tests an additional syntax/validation check beyond comparing the generated output with golden files. Signed-off-by: Sebastiaan van Stijn --- apparmor/apparmor_linux_test.go | 37 ++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apparmor/apparmor_linux_test.go b/apparmor/apparmor_linux_test.go index fbbc713..7a0306f 100644 --- a/apparmor/apparmor_linux_test.go +++ b/apparmor/apparmor_linux_test.go @@ -212,10 +212,14 @@ func TestIsLoaded(t *testing.T) { } func TestGenerateDefault(t *testing.T) { + _, err := exec.LookPath("apparmor_parser") + apparmorParserAvailable := err == nil + tests := []struct { name string data profileData macroExists func(string) bool + skipParse bool }{ { name: "default", @@ -264,6 +268,7 @@ func TestGenerateDefault(t *testing.T) { Name: "custom-imports", Imports: []string{"#include ", "#include "}, }, + skipParse: true, // Skip parsing because we use non-existing includes. }, { name: "with-custom-inner-imports", @@ -271,6 +276,7 @@ func TestGenerateDefault(t *testing.T) { Name: "custom-inner-imports", InnerImports: []string{"#include ", "#include "}, }, + skipParse: true, // Skip parsing because we use non-existing includes. }, } @@ -287,7 +293,18 @@ func TestGenerateDefault(t *testing.T) { t.Fatal(err) } - assertGolden(t, sb.String(), tc.name) + got := sb.String() + assertGolden(t, got, tc.name) + + if tc.skipParse { + return + } + t.Run("validate", func(t *testing.T) { + if !apparmorParserAvailable { + t.Skip("apparmor_parser not available") + } + validateProfile(t, got) + }) }) } } @@ -353,6 +370,24 @@ func hostSupportsAppArmor() bool { return err == nil && len(buf) > 0 && buf[0] == 'Y' } +// validateProfile parses the profile with apparmor_parser without loading it +// into the kernel, and returns the declared profile names. +func validateProfile(t *testing.T, profile string) []string { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, "apparmor_parser", "-N", "-Q", "-K") + cmd.Stdin = strings.NewReader(profile) + + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("parsing generated profile: %v\n%s", err, out) + } + return strings.Split(strings.TrimSuffix(string(out), "\n"), "\n") +} + func unloadProfile(t *testing.T, name string) { t.Helper()