From 079e8a9eefc639772d8849cea26727ea0918a74b Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 11 Oct 2023 21:19:12 +1100 Subject: [PATCH 5/5] SLE12: revert "apparmor: remove version-conditionals from template" This reverts the following commits: * 7008a514493a ("profiles/apparmor: remove version-conditional constraints (< 2.8.96)") * 2e19a4d56bf2 ("contrib/apparmor: remove version-conditionals (< 2.9) from template") * d169a5730649 ("contrib/apparmor: remove remaining version-conditionals (< 2.9) from template") * ecaab085db4b ("profiles/apparmor: remove use of aaparser.GetVersion()") * e3e715666f95 ("pkg/aaparser: deprecate GetVersion, as it's no longer used") These version conditionals are still required on SLE 12, where our apparmor_parser version is quite old. Signed-off-by: Aleksa Sarai --- contrib/apparmor/main.go | 16 ++++++- contrib/apparmor/template.go | 16 +++++++ pkg/aaparser/aaparser.go | 86 +++++++++++++++++++++++++++++++++++ profiles/apparmor/apparmor.go | 16 ++++++- profiles/apparmor/template.go | 4 ++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 pkg/aaparser/aaparser.go diff --git a/contrib/apparmor/main.go b/contrib/apparmor/main.go index 899d8378ed..93f98cbd20 100644 --- a/contrib/apparmor/main.go +++ b/contrib/apparmor/main.go @@ -6,9 +6,13 @@ import ( "os" "path" "text/template" + + "github.com/docker/docker/pkg/aaparser" ) -type profileData struct{} +type profileData struct { + Version int +} func main() { if len(os.Args) < 2 { @@ -18,6 +22,15 @@ func main() { // parse the arg apparmorProfilePath := os.Args[1] + version, err := aaparser.GetVersion() + if err != nil { + log.Fatal(err) + } + data := profileData{ + Version: version, + } + fmt.Printf("apparmor_parser is of version %+v\n", data) + // parse the template compiled, err := template.New("apparmor_profile").Parse(dockerProfileTemplate) if err != nil { @@ -35,7 +48,6 @@ func main() { } defer f.Close() - data := profileData{} if err := compiled.Execute(f, data); err != nil { log.Fatalf("executing template failed: %v", err) } diff --git a/contrib/apparmor/template.go b/contrib/apparmor/template.go index 58afcbe845..e6d0b6d37c 100644 --- a/contrib/apparmor/template.go +++ b/contrib/apparmor/template.go @@ -20,9 +20,11 @@ profile /usr/bin/docker (attach_disconnected, complain) { umount, pivot_root, +{{if ge .Version 209000}} signal (receive) peer=@{profile_name}, signal (receive) peer=unconfined, signal (send), +{{end}} network, capability, owner /** rw, @@ -45,10 +47,12 @@ profile /usr/bin/docker (attach_disconnected, complain) { /etc/ld.so.cache r, /etc/passwd r, +{{if ge .Version 209000}} ptrace peer=@{profile_name}, ptrace (read) peer=docker-default, deny ptrace (trace) peer=docker-default, deny ptrace peer=/usr/bin/docker///bin/ps, +{{end}} /usr/lib/** rm, /lib/** rm, @@ -69,9 +73,11 @@ profile /usr/bin/docker (attach_disconnected, complain) { /sbin/zfs rCx, /sbin/apparmor_parser rCx, +{{if ge .Version 209000}} # Transitions change_profile -> docker-*, change_profile -> unconfined, +{{end}} profile /bin/cat (complain) { /etc/ld.so.cache r, @@ -93,8 +99,10 @@ profile /usr/bin/docker (attach_disconnected, complain) { /dev/null rw, /bin/ps mr, +{{if ge .Version 209000}} # We don't need ptrace so we'll deny and ignore the error. deny ptrace (read, trace), +{{end}} # Quiet dac_override denials deny capability dac_override, @@ -112,11 +120,15 @@ profile /usr/bin/docker (attach_disconnected, complain) { /proc/tty/drivers r, } profile /sbin/iptables (complain) { +{{if ge .Version 209000}} signal (receive) peer=/usr/bin/docker, +{{end}} capability net_admin, } profile /sbin/auplink flags=(attach_disconnected, complain) { +{{if ge .Version 209000}} signal (receive) peer=/usr/bin/docker, +{{end}} capability sys_admin, capability dac_override, @@ -135,7 +147,9 @@ profile /usr/bin/docker (attach_disconnected, complain) { /proc/[0-9]*/mounts rw, } profile /sbin/modprobe /bin/kmod (complain) { +{{if ge .Version 209000}} signal (receive) peer=/usr/bin/docker, +{{end}} capability sys_module, /etc/ld.so.cache r, /lib/** rm, @@ -149,7 +163,9 @@ profile /usr/bin/docker (attach_disconnected, complain) { } # xz works via pipes, so we do not need access to the filesystem. profile /usr/bin/xz (complain) { +{{if ge .Version 209000}} signal (receive) peer=/usr/bin/docker, +{{end}} /etc/ld.so.cache r, /lib/** rm, /usr/bin/xz rm, diff --git a/pkg/aaparser/aaparser.go b/pkg/aaparser/aaparser.go new file mode 100644 index 0000000000..89b48b2dba --- /dev/null +++ b/pkg/aaparser/aaparser.go @@ -0,0 +1,86 @@ +// Package aaparser is a convenience package interacting with `apparmor_parser`. +package aaparser // import "github.com/docker/docker/pkg/aaparser" + +import ( + "fmt" + "os/exec" + "strconv" + "strings" +) + +const ( + binary = "apparmor_parser" +) + +// GetVersion returns the major and minor version of apparmor_parser. +func GetVersion() (int, error) { + output, err := cmd("", "--version") + if err != nil { + return -1, err + } + + return parseVersion(output) +} + +// cmd runs `apparmor_parser` with the passed arguments. +func cmd(dir string, arg ...string) (string, error) { + c := exec.Command(binary, arg...) + c.Dir = dir + + output, err := c.CombinedOutput() + if err != nil { + return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err) + } + + return string(output), nil +} + +// parseVersion takes the output from `apparmor_parser --version` and returns +// a representation of the {major, minor, patch} version as a single number of +// the form MMmmPPP {major, minor, patch}. +func parseVersion(output string) (int, error) { + // output is in the form of the following: + // AppArmor parser version 2.9.1 + // Copyright (C) 1999-2008 Novell Inc. + // Copyright 2009-2012 Canonical Ltd. + + lines := strings.SplitN(output, "\n", 2) + words := strings.Split(lines[0], " ") + version := words[len(words)-1] + + // trim "-beta1" suffix from version="3.0.0-beta1" if exists + version = strings.SplitN(version, "-", 2)[0] + // also trim "~..." suffix used historically (https://gitlab.com/apparmor/apparmor/-/commit/bca67d3d27d219d11ce8c9cc70612bd637f88c10) + version = strings.SplitN(version, "~", 2)[0] + + // split by major minor version + v := strings.Split(version, ".") + if len(v) == 0 || len(v) > 3 { + return -1, fmt.Errorf("parsing version failed for output: `%s`", output) + } + + // Default the versions to 0. + var majorVersion, minorVersion, patchLevel int + + majorVersion, err := strconv.Atoi(v[0]) + if err != nil { + return -1, err + } + + if len(v) > 1 { + minorVersion, err = strconv.Atoi(v[1]) + if err != nil { + return -1, err + } + } + if len(v) > 2 { + patchLevel, err = strconv.Atoi(v[2]) + if err != nil { + return -1, err + } + } + + // major*10^5 + minor*10^3 + patch*10^0 + numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel + return numericVersion, nil +} diff --git a/profiles/apparmor/apparmor.go b/profiles/apparmor/apparmor.go index 1edfc53002..0d23b940bd 100644 --- a/profiles/apparmor/apparmor.go +++ b/profiles/apparmor/apparmor.go @@ -11,10 +11,14 @@ import ( "path" "strings" "text/template" + + "github.com/docker/docker/pkg/aaparser" ) -// profileDirectory is the file store for apparmor profiles and macros. -const profileDirectory = "/etc/apparmor.d" +var ( + // profileDirectory is the file store for apparmor profiles and macros. + profileDirectory = "/etc/apparmor.d" +) // profileData holds information about the given profile for generation. type profileData struct { @@ -26,6 +30,8 @@ type profileData struct { Imports []string // InnerImports defines the apparmor functions to import in the profile. InnerImports []string + // Version is the {major, minor, patch} version of apparmor_parser as a single number. + Version int } // generateDefault creates an apparmor profile from ProfileData. @@ -45,6 +51,12 @@ func (p *profileData) generateDefault(out io.Writer) error { p.InnerImports = append(p.InnerImports, "#include ") } + ver, err := aaparser.GetVersion() + if err != nil { + return err + } + p.Version = ver + return compiled.Execute(out, p) } diff --git a/profiles/apparmor/template.go b/profiles/apparmor/template.go index cf8c34ce8a..4ebd647e14 100644 --- a/profiles/apparmor/template.go +++ b/profiles/apparmor/template.go @@ -23,12 +23,14 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { capability, file, umount, +{{if ge .Version 208096}} # Host (privileged) processes may send signals to container processes. signal (receive) peer=unconfined, # dockerd may send signals to container processes (for "docker kill"). signal (receive) peer={{.DaemonProfile}}, # Container processes may send signals amongst themselves. signal (send,receive) peer={{.Name}}, +{{end}} deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) # deny write to files not in /proc//** or /proc/sys/** @@ -49,7 +51,9 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { deny /sys/devices/virtual/powercap/** rwklx, deny /sys/kernel/security/** rwklx, +{{if ge .Version 208095}} # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container ptrace (trace,read,tracedby,readby) peer={{.Name}}, +{{end}} } ` -- 2.39.0