summaryrefslogtreecommitdiff
path: root/server/apparmor
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@gmail.com>2017-11-01 11:24:59 -0400
committerMatthew Heon <matthew.heon@gmail.com>2017-11-01 11:24:59 -0400
commita031b83a09a8628435317a03f199cdc18b78262f (patch)
treebc017a96769ce6de33745b8b0b1304ccf38e9df0 /server/apparmor
parent2b74391cd5281f6fdf391ff8ad50fd1490f6bf89 (diff)
downloadpodman-a031b83a09a8628435317a03f199cdc18b78262f.tar.gz
podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.bz2
podman-a031b83a09a8628435317a03f199cdc18b78262f.zip
Initial checkin from CRI-O repo
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Diffstat (limited to 'server/apparmor')
-rw-r--r--server/apparmor/aaparser.go89
-rw-r--r--server/apparmor/apparmor_common.go14
-rw-r--r--server/apparmor/apparmor_supported.go145
-rw-r--r--server/apparmor/apparmor_unsupported.go18
-rw-r--r--server/apparmor/template.go45
5 files changed, 311 insertions, 0 deletions
diff --git a/server/apparmor/aaparser.go b/server/apparmor/aaparser.go
new file mode 100644
index 000000000..7f0f02ac5
--- /dev/null
+++ b/server/apparmor/aaparser.go
@@ -0,0 +1,89 @@
+// +build apparmor
+
+package apparmor
+
+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)
+}
+
+// LoadProfile runs `apparmor_parser -r` on a specified apparmor profile to
+// replace the profile.
+func LoadProfile(profilePath string) error {
+ _, err := cmd("", "-r", profilePath)
+ return err
+}
+
+// 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]
+
+ // 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/server/apparmor/apparmor_common.go b/server/apparmor/apparmor_common.go
new file mode 100644
index 000000000..6366a66e6
--- /dev/null
+++ b/server/apparmor/apparmor_common.go
@@ -0,0 +1,14 @@
+package apparmor
+
+const (
+ // DefaultApparmorProfile is the name of default apparmor profile name.
+ DefaultApparmorProfile = "crio-default"
+
+ // ContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container profile.
+ ContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
+
+ // ProfileRuntimeDefault is he profile specifying the runtime default.
+ ProfileRuntimeDefault = "runtime/default"
+ // ProfileNamePrefix is the prefix for specifying profiles loaded on the node.
+ ProfileNamePrefix = "localhost/"
+)
diff --git a/server/apparmor/apparmor_supported.go b/server/apparmor/apparmor_supported.go
new file mode 100644
index 000000000..d765c9de9
--- /dev/null
+++ b/server/apparmor/apparmor_supported.go
@@ -0,0 +1,145 @@
+// +build apparmor
+
+package apparmor
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/docker/docker/utils/templates"
+ "github.com/opencontainers/runc/libcontainer/apparmor"
+)
+
+const (
+ // 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 {
+ // Name is profile name.
+ Name string
+ // Imports defines the apparmor functions to import, before defining the profile.
+ 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
+}
+
+// EnsureDefaultApparmorProfile loads default apparmor profile, if it is not loaded.
+func EnsureDefaultApparmorProfile() error {
+ if apparmor.IsEnabled() {
+ loaded, err := IsLoaded(DefaultApparmorProfile)
+ if err != nil {
+ return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", DefaultApparmorProfile, err)
+ }
+
+ // Nothing to do.
+ if loaded {
+ return nil
+ }
+
+ // Load the profile.
+ if err := InstallDefault(DefaultApparmorProfile); err != nil {
+ return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", DefaultApparmorProfile)
+ }
+ }
+
+ return nil
+}
+
+// IsEnabled returns true if apparmor is enabled for the host.
+func IsEnabled() bool {
+ return apparmor.IsEnabled()
+}
+
+// GetProfileNameFromPodAnnotations gets the name of the profile to use with container from
+// pod annotations
+func GetProfileNameFromPodAnnotations(annotations map[string]string, containerName string) string {
+ return annotations[ContainerAnnotationKeyPrefix+containerName]
+}
+
+// InstallDefault generates a default profile in a temp directory determined by
+// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'.
+func InstallDefault(name string) error {
+ p := profileData{
+ Name: name,
+ }
+
+ // Install to a temporary directory.
+ f, err := ioutil.TempFile("", name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if err := p.generateDefault(f); err != nil {
+ return err
+ }
+
+ return LoadProfile(f.Name())
+}
+
+// IsLoaded checks if a profile with the given name has been loaded into the
+// kernel.
+func IsLoaded(name string) (bool, error) {
+ file, err := os.Open("/sys/kernel/security/apparmor/profiles")
+ if err != nil {
+ return false, err
+ }
+ defer file.Close()
+
+ r := bufio.NewReader(file)
+ for {
+ p, err := r.ReadString('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return false, err
+ }
+ if strings.HasPrefix(p, name+" ") {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+// generateDefault creates an apparmor profile from ProfileData.
+func (p *profileData) generateDefault(out io.Writer) error {
+ compiled, err := templates.NewParse("apparmor_profile", baseTemplate)
+ if err != nil {
+ return err
+ }
+
+ if macroExists("tunables/global") {
+ p.Imports = append(p.Imports, "#include <tunables/global>")
+ } else {
+ p.Imports = append(p.Imports, "@{PROC}=/proc/")
+ }
+
+ if macroExists("abstractions/base") {
+ p.InnerImports = append(p.InnerImports, "#include <abstractions/base>")
+ }
+
+ ver, err := GetVersion()
+ if err != nil {
+ return err
+ }
+ p.Version = ver
+
+ return compiled.Execute(out, p)
+}
+
+// macrosExists checks if the passed macro exists.
+func macroExists(m string) bool {
+ _, err := os.Stat(path.Join(profileDirectory, m))
+ return err == nil
+}
diff --git a/server/apparmor/apparmor_unsupported.go b/server/apparmor/apparmor_unsupported.go
new file mode 100644
index 000000000..fbd1d87a0
--- /dev/null
+++ b/server/apparmor/apparmor_unsupported.go
@@ -0,0 +1,18 @@
+// +build !apparmor
+
+package apparmor
+
+// IsEnabled returns false, when build without apparmor build tag.
+func IsEnabled() bool {
+ return false
+}
+
+// EnsureDefaultApparmorProfile dose nothing, when build without apparmor build tag.
+func EnsureDefaultApparmorProfile() error {
+ return nil
+}
+
+// GetProfileNameFromPodAnnotations dose nothing, when build without apparmor build tag.
+func GetProfileNameFromPodAnnotations(annotations map[string]string, containerName string) string {
+ return ""
+}
diff --git a/server/apparmor/template.go b/server/apparmor/template.go
new file mode 100644
index 000000000..6656ff61c
--- /dev/null
+++ b/server/apparmor/template.go
@@ -0,0 +1,45 @@
+// +build apparmor
+
+package apparmor
+
+// baseTemplate defines the default apparmor profile for containers.
+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/** rwklx,
+ deny /sys/kernel/security/** rwklx,
+
+{{if ge .Version 208095}}
+ ptrace (trace,read) peer={{.Name}},
+{{end}}
+}
+`