2024-10-19 23:50:09 +02:00
|
|
|
// apparmor.d - Full set of apparmor profiles
|
|
|
|
// Copyright (C) 2023-2024 Alexandre Pujol <alexandre@pujol.io>
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"html/template"
|
|
|
|
"os/exec"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/roddhjav/apparmor.d/pkg/aa"
|
|
|
|
"github.com/roddhjav/apparmor.d/pkg/paths"
|
|
|
|
)
|
|
|
|
|
|
|
|
const tmplTest = `#!/usr/bin/env bats
|
|
|
|
# apparmor.d - Full set of apparmor profiles
|
|
|
|
# Copyright (C) 2024 Alexandre Pujol <alexandre@pujol.io>
|
|
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
2024-10-21 15:13:25 +02:00
|
|
|
|
|
|
|
load common
|
|
|
|
|
|
|
|
setup_file() {
|
|
|
|
aa_setup
|
|
|
|
}
|
2024-10-19 23:50:09 +02:00
|
|
|
{{ $name := .Name -}}
|
|
|
|
{{ range .Commands }}
|
|
|
|
# bats test_tags={{ $name }}
|
|
|
|
@test "{{ $name }}: {{ .Description }}" {
|
|
|
|
{{ .Cmd }}
|
2024-10-21 15:13:25 +02:00
|
|
|
aa_check
|
2024-10-19 23:50:09 +02:00
|
|
|
}
|
|
|
|
{{ end }}
|
|
|
|
`
|
|
|
|
|
|
|
|
var (
|
|
|
|
Profiles = getProfiles() // List of profiles in apparmor.d
|
|
|
|
tmpl = template.Must(template.New("bats").Parse(tmplTest))
|
|
|
|
)
|
|
|
|
|
|
|
|
type Tests []Test
|
|
|
|
|
|
|
|
// Filter returns a new list of tests with only the ones that have a profile
|
|
|
|
func (t Tests) Filter() Tests {
|
|
|
|
for i := len(t) - 1; i >= 0; i-- {
|
|
|
|
if !t[i].HasProfile() {
|
|
|
|
t = slices.Delete(t, i, i+1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test represents of a list of tests for a given program
|
|
|
|
type Test struct {
|
|
|
|
Name string
|
|
|
|
Commands []Command
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command is a command line to run as part of a test
|
|
|
|
type Command struct {
|
|
|
|
Description string
|
|
|
|
Cmd string
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasProfile returns true if the program in the scenario is profiled in apparmor.d
|
2024-10-21 15:13:25 +02:00
|
|
|
func (t Test) HasProfile() bool {
|
2024-10-19 23:50:09 +02:00
|
|
|
return slices.Contains(Profiles, t.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsInstalled returns true if the program in the scenario is installed on the system
|
2024-10-21 15:13:25 +02:00
|
|
|
func (t Test) IsInstalled() bool {
|
2024-10-19 23:50:09 +02:00
|
|
|
if _, err := exec.LookPath(t.Name); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Test) Write(dir *paths.Path) error {
|
|
|
|
if !t.HasProfile() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
path := dir.Join(t.Name + ".bats")
|
2024-10-21 15:13:25 +02:00
|
|
|
if paths.New("tests/bats").Join(t.Name + ".bats").Exist() {
|
|
|
|
path = dir.Join("00." + t.Name + ".bats")
|
|
|
|
}
|
2024-10-19 23:50:09 +02:00
|
|
|
content := renderBatsFile(t)
|
|
|
|
if err := path.WriteFile([]byte(content)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderBatsFile(data any) string {
|
|
|
|
var res strings.Builder
|
|
|
|
err := tmpl.Execute(&res, data)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return res.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getProfiles() []string {
|
|
|
|
p := []string{}
|
|
|
|
files, err := aa.MagicRoot.ReadDir(paths.FilterOutDirectories())
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
for _, path := range files {
|
|
|
|
p = append(p, path.Base())
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|