This is a long-requested feature, and it solves several usability problems with the default AppArmor profile: 1. The profile is completely opaque because we don't write to /etc/apparmor.d anymore -- this leads to lots of headaches when trying to run certain semi-privileged programs inside containers. It's much nicer to be able to have a starting point for a custom AppArmor profile. 2. Changes to AppArmor have broken backwards compatibility in the past (leading to containers not working properly) -- the poster-child for this problem was signal mediation. In order to fix this problem, distributions had to patch Docker when it would've been much nicer to provide a hotfix in the form of a custom configuration they could apply to their hosts. It also bridges the feature gap between the default seccomp profile (which has been configurable for a very long time). This patch also fixes a few minor bugs in the AppArmor profile implementation (notably, Docker would not forcefully load its built-in profile at start time -- meaning a Docker update wouldn't actually update the profile). This feature should probably be ported to other container runtimes (containerd, podman/cri-o, etc) so that we can continue with the effort to merge all of the disparate AppArmor profile implementations. Ideally they would all use the same template variables too. Signed-off-by: Aleksa Sarai <asarai@suse.de> --- The above was reapplied from the original commit on top of the current daemon code. The daemon already force-loads the default AppArmor profile at startup, so this change wires the configured template into that existing reload path instead of introducing the reload behavior itself. The built-in profile now lives in github.com/moby/profiles/apparmor, so the custom template data follows the current profile variables. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
//go:build linux
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/v2/daemon/config"
|
|
"gotest.tools/v3/assert"
|
|
"gotest.tools/v3/fs"
|
|
)
|
|
|
|
func TestSetupAppArmorProfile(t *testing.T) {
|
|
profile := fs.NewFile(t, "profile", fs.WithContent(`profile "{{.Name}}" {}`))
|
|
|
|
d := &Daemon{}
|
|
err := d.setupAppArmorProfile(&config.Config{AppArmorProfile: profile.Path()})
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, d.appArmorProfilePath, profile.Path())
|
|
assert.Assert(t, d.appArmorProfile != nil)
|
|
}
|
|
|
|
func TestSetupAppArmorProfileErrors(t *testing.T) {
|
|
d := &Daemon{}
|
|
err := d.setupAppArmorProfile(&config.Config{AppArmorProfile: filepath.Join(t.TempDir(), "missing")})
|
|
assert.ErrorContains(t, err, "opening AppArmor profile")
|
|
|
|
profile := fs.NewFile(t, "profile", fs.WithContent(`profile "{{.Name}" {}`))
|
|
err = d.setupAppArmorProfile(&config.Config{AppArmorProfile: profile.Path()})
|
|
assert.ErrorContains(t, err, "parsing AppArmor profile")
|
|
}
|
|
|
|
func TestGenerateCustomAppArmorProfile(t *testing.T) {
|
|
profile := fs.NewFile(t, "profile", fs.WithContent(`{{range .Imports}}{{.}}
|
|
{{end}}profile "{{.Name}}" {
|
|
signal (receive) peer="{{.DaemonProfile}}",
|
|
}`))
|
|
|
|
d := &Daemon{}
|
|
err := d.setupAppArmorProfile(&config.Config{AppArmorProfile: profile.Path()})
|
|
assert.NilError(t, err)
|
|
|
|
generated, err := d.generateCustomAppArmorProfile()
|
|
assert.NilError(t, err)
|
|
out := generated.String()
|
|
assert.Assert(t, strings.Contains(out, `profile "docker-default"`))
|
|
assert.Assert(t, strings.Contains(out, `#include <tunables/global>`) || strings.Contains(out, `@{PROC}=/proc/`))
|
|
}
|
|
|
|
func TestGenerateCustomAppArmorProfileMissingKey(t *testing.T) {
|
|
profile := fs.NewFile(t, "profile", fs.WithContent(`profile "{{.Unknown}}" {}`))
|
|
|
|
d := &Daemon{}
|
|
err := d.setupAppArmorProfile(&config.Config{AppArmorProfile: profile.Path()})
|
|
assert.NilError(t, err)
|
|
|
|
_, err = d.generateCustomAppArmorProfile()
|
|
assert.ErrorContains(t, err, `can't evaluate field Unknown`)
|
|
}
|