2015-07-28 14:48:18 -04:00
|
|
|
// +build linux
|
|
|
|
|
|
|
|
package native
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
|
2015-11-23 18:41:18 -05:00
|
|
|
"github.com/docker/docker/pkg/aaparser"
|
2015-07-28 14:48:18 -04:00
|
|
|
"github.com/opencontainers/runc/libcontainer/apparmor"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
apparmorProfilePath = "/etc/apparmor.d/docker"
|
|
|
|
)
|
|
|
|
|
|
|
|
type data struct {
|
|
|
|
Name string
|
2015-11-23 18:41:18 -05:00
|
|
|
ExecPath string
|
2015-07-28 14:48:18 -04:00
|
|
|
Imports []string
|
|
|
|
InnerImports []string
|
2015-11-23 18:41:18 -05:00
|
|
|
MajorVersion int
|
|
|
|
MinorVersion int
|
2015-07-28 14:48:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const baseTemplate = `
|
|
|
|
{{range $value := .Imports}}
|
|
|
|
{{$value}}
|
|
|
|
{{end}}
|
|
|
|
|
|
|
|
profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
|
|
|
|
{{range $value := .InnerImports}}
|
|
|
|
{{$value}}
|
|
|
|
{{end}}
|
|
|
|
|
|
|
|
network,
|
|
|
|
capability,
|
|
|
|
file,
|
|
|
|
umount,
|
|
|
|
|
2015-11-25 13:38:18 -05:00
|
|
|
deny @{PROC}/{*,**^[0-9]*,sys/kernel/shm*} wkx,
|
2015-07-28 14:48:18 -04:00
|
|
|
deny @{PROC}/sysrq-trigger rwklx,
|
|
|
|
deny @{PROC}/mem rwklx,
|
|
|
|
deny @{PROC}/kmem rwklx,
|
2015-08-05 23:08:00 -04:00
|
|
|
deny @{PROC}/kcore rwklx,
|
2015-07-28 14:48:18 -04:00
|
|
|
|
|
|
|
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,
|
2015-10-12 10:41:18 -04:00
|
|
|
|
2015-11-23 18:41:18 -05:00
|
|
|
{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
|
2015-10-12 10:41:18 -04:00
|
|
|
# docker daemon confinement requires explict allow rule for signal
|
2015-11-23 18:41:18 -05:00
|
|
|
signal (receive) set=(kill,term) peer={{.ExecPath}},
|
2015-10-12 10:41:18 -04:00
|
|
|
|
|
|
|
# suppress ptrace denails when using 'docker ps'
|
|
|
|
ptrace (trace,read) peer=docker-default,
|
2015-11-23 18:41:18 -05:00
|
|
|
{{end}}{{end}}
|
2015-07-28 14:48:18 -04:00
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
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>")
|
|
|
|
}
|
2015-11-23 18:41:18 -05:00
|
|
|
data.MajorVersion, data.MinorVersion, err = aaparser.GetVersion()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data.ExecPath, err = exec.LookPath("docker")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-07-28 14:48:18 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|