The 'deny ptrace' statement was supposed to only ignore ptrace failures in the AUDIT log. However, ptrace was implicitly allowed from unconfined processes (such as the docker daemon and its integration tests) due to the abstractions/base include. This rule narrows the definition such that it will only ignore the failures originating inside of the container and will not cause denials when the daemon or its tests ptrace inside processes. Introduces positive and negative tests for ptrace /w apparmor. Signed-off-by: Eric Windisch <eric@windisch.us> Upstream-commit: f5c388b35a9ddd699b3dbbe85b80fa02234f8355 Component: engine
165 lines
3.3 KiB
Go
165 lines
3.3 KiB
Go
// +build linux
|
|
|
|
package native
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/apparmor"
|
|
)
|
|
|
|
const (
|
|
apparmorProfilePath = "/etc/apparmor.d/docker"
|
|
)
|
|
|
|
type data struct {
|
|
Name string
|
|
Imports []string
|
|
InnerImports []string
|
|
}
|
|
|
|
const baseTemplate = `
|
|
{{range $value := .Imports}}
|
|
{{$value}}
|
|
{{end}}
|
|
|
|
profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
|
{{range $value := .InnerImports}}
|
|
{{$value}}
|
|
{{end}}
|
|
|
|
network,
|
|
capability,
|
|
file,
|
|
umount,
|
|
|
|
signal (receive) peer=/usr/bin/docker,
|
|
signal (receive) peer=docker-unconfined,
|
|
|
|
deny @{PROC}/sys/fs/** wklx,
|
|
deny @{PROC}/fs/** wklx,
|
|
deny @{PROC}/sysrq-trigger rwklx,
|
|
deny @{PROC}/mem rwklx,
|
|
deny @{PROC}/kmem rwklx,
|
|
deny @{PROC}/kore rwklx,
|
|
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
|
|
deny @{PROC}/sys/kernel/*/** wklx,
|
|
|
|
deny mount,
|
|
deny ptrace (trace) peer=docker-default,
|
|
|
|
deny /sys/[^f]*/** wklx,
|
|
deny /sys/f[^s]*/** wklx,
|
|
deny /sys/fs/[^c]*/** wklx,
|
|
deny /sys/fs/c[^g]*/** wklx,
|
|
deny /sys/fs/cg[^r]*/** wklx,
|
|
deny /sys/firmware/efi/efivars/** rwklx,
|
|
deny /sys/kernel/security/** rwklx,
|
|
}
|
|
|
|
profile docker-unconfined flags=(attach_disconnected,mediate_deleted,complain) {
|
|
#include <abstractions/base>
|
|
|
|
network,
|
|
capability,
|
|
file,
|
|
umount,
|
|
mount,
|
|
pivot_root,
|
|
change_profile -> *,
|
|
|
|
ptrace,
|
|
signal,
|
|
}
|
|
`
|
|
|
|
func generateProfile(out io.Writer) error {
|
|
compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data := &data{
|
|
Name: "docker-default",
|
|
}
|
|
if tunablesExists() {
|
|
data.Imports = append(data.Imports, "#include <tunables/global>")
|
|
} else {
|
|
data.Imports = append(data.Imports, "@{PROC}=/proc/")
|
|
}
|
|
if abstractionsExists() {
|
|
data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
|
|
}
|
|
if err := compiled.Execute(out, data); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// check if the tunables/global exist
|
|
func tunablesExists() bool {
|
|
_, err := os.Stat("/etc/apparmor.d/tunables/global")
|
|
return err == nil
|
|
}
|
|
|
|
// check if abstractions/base exist
|
|
func abstractionsExists() bool {
|
|
_, err := os.Stat("/etc/apparmor.d/abstractions/base")
|
|
return err == nil
|
|
}
|
|
|
|
func installAppArmorProfile() error {
|
|
if !apparmor.IsEnabled() {
|
|
return nil
|
|
}
|
|
|
|
// Make sure /etc/apparmor.d exists
|
|
if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.OpenFile(apparmorProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := generateProfile(f); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
f.Close()
|
|
|
|
cmd := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker")
|
|
// to use the parser directly we have to make sure we are in the correct
|
|
// dir with the profile
|
|
cmd.Dir = "/etc/apparmor.d"
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("Error loading docker apparmor profile: %s (%s)", err, output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasAppArmorProfileLoaded(profile string) error {
|
|
file, err := os.Open("/sys/kernel/security/apparmor/profiles")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r := bufio.NewReader(file)
|
|
for {
|
|
p, err := r.ReadString('\n')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.HasPrefix(p, profile+" ") {
|
|
return nil
|
|
}
|
|
}
|
|
}
|