| // +build linux |
| |
| package native |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "os" |
| "os/exec" |
| "path" |
| "strings" |
| "text/template" |
| |
| "github.com/docker/docker/pkg/aaparser" |
| "github.com/opencontainers/runc/libcontainer/apparmor" |
| ) |
| |
| const ( |
| apparmorProfilePath = "/etc/apparmor.d/docker" |
| ) |
| |
| type data struct { |
| Name string |
| ExecPath string |
| Imports []string |
| InnerImports []string |
| MajorVersion int |
| MinorVersion int |
| } |
| |
| const baseTemplate = ` |
| {{range $value := .Imports}} |
| {{$value}} |
| {{end}} |
| |
| profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { |
| {{range $value := .InnerImports}} |
| {{$value}} |
| {{end}} |
| |
| network, |
| capability, |
| file, |
| umount, |
| |
| deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) |
| # deny write to files not in /proc/<number>/** or /proc/sys/** |
| deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w, |
| deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) |
| deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ |
| deny @{PROC}/sysrq-trigger rwklx, |
| deny @{PROC}/mem rwklx, |
| deny @{PROC}/kmem rwklx, |
| deny @{PROC}/kcore rwklx, |
| |
| deny mount, |
| |
| 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, |
| |
| {{if ge .MajorVersion 2}}{{if ge .MinorVersion 8}} |
| # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container |
| ptrace (trace,read) peer=docker-default, |
| {{end}}{{end}} |
| {{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}} |
| # docker daemon confinement requires explict allow rule for signal |
| signal (receive) set=(kill,term) peer={{.ExecPath}}, |
| {{end}}{{end}} |
| } |
| ` |
| |
| 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>") |
| } |
| data.MajorVersion, data.MinorVersion, err = aaparser.GetVersion() |
| if err != nil { |
| return err |
| } |
| data.ExecPath, err = exec.LookPath("docker") |
| if err != nil { |
| return err |
| } |
| 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 |
| } |
| } |
| } |