瀏覽代碼

Merge pull request #19468 from jfrazelle/refactor-sec-profiles-into-own-pkg

Refactor sec profiles their own packages
Arnaud Porterie 9 年之前
父節點
當前提交
c5380f9118

+ 0 - 161
daemon/execdriver/native/apparmor.go

@@ -1,161 +0,0 @@
-// +build linux
-
-package native
-
-import (
-	"bufio"
-	"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()
-
-	if err := aaparser.LoadProfile(apparmorProfilePath); err != nil {
-		return err
-	}
-
-	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
-		}
-	}
-}

+ 3 - 2
daemon/execdriver/native/create.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/daemon/execdriver"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/mount"
+	"github.com/docker/docker/profiles/seccomp"
 
 	"github.com/docker/docker/volume"
 	"github.com/opencontainers/runc/libcontainer/apparmor"
@@ -71,7 +72,7 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
 		}
 
 		if c.SeccompProfile == "" {
-			container.Seccomp = getDefaultSeccompProfile()
+			container.Seccomp = seccomp.GetDefaultProfile()
 		}
 	}
 	// add CAP_ prefix to all caps for new libcontainer update to match
@@ -88,7 +89,7 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
 	}
 
 	if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
-		container.Seccomp, err = loadSeccompProfile(c.SeccompProfile)
+		container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile)
 		if err != nil {
 			return nil, err
 		}

+ 6 - 3
daemon/execdriver/native/driver.go

@@ -21,6 +21,7 @@ import (
 	"github.com/docker/docker/pkg/reexec"
 	sysinfo "github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/term"
+	aaprofile "github.com/docker/docker/profiles/apparmor"
 	"github.com/opencontainers/runc/libcontainer"
 	"github.com/opencontainers/runc/libcontainer/apparmor"
 	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
@@ -33,6 +34,8 @@ import (
 const (
 	DriverName = "native"
 	Version    = "0.2"
+
+	defaultApparmorProfile = "docker-default"
 )
 
 // Driver contains all information for native driver,
@@ -57,13 +60,13 @@ func NewDriver(root string, options []string) (*Driver, error) {
 	}
 
 	if apparmor.IsEnabled() {
-		if err := installAppArmorProfile(); err != nil {
-			apparmorProfiles := []string{"docker-default"}
+		if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
+			apparmorProfiles := []string{defaultApparmorProfile}
 
 			// Allow daemon to run if loading failed, but are active
 			// (possibly through another run, manually, or via system startup)
 			for _, policy := range apparmorProfiles {
-				if err := hasAppArmorProfileLoaded(policy); err != nil {
+				if err := aaprofile.IsLoaded(policy); err != nil {
 					return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
 				}
 			}

+ 110 - 0
profiles/apparmor/apparmor.go

@@ -0,0 +1,110 @@
+// +build linux
+
+package apparmor
+
+import (
+	"bufio"
+	"io"
+	"os"
+	"path"
+	"strings"
+	"text/template"
+
+	"github.com/docker/docker/pkg/aaparser"
+)
+
+var (
+	// profileDirectory is the file store for apparmor profiles and macros.
+	profileDirectory = "/etc/apparmor.d"
+	// defaultProfilePath is the default path for the apparmor profile to be saved.
+	defaultProfilePath = path.Join(profileDirectory, "docker")
+)
+
+// profileData holds information about the given profile for generation.
+type profileData struct {
+	// Name is profile name.
+	Name string
+	// ExecPath is the path to the docker binary.
+	ExecPath 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
+	// MajorVersion is the apparmor_parser major version.
+	MajorVersion int
+	// MinorVersion is the apparmor_parser minor version.
+	MinorVersion int
+}
+
+// generateDefault creates an apparmor profile from ProfileData.
+func (p *profileData) generateDefault(out io.Writer) error {
+	compiled, err := template.New("apparmor_profile").Parse(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>")
+	}
+	if err := compiled.Execute(out, p); err != nil {
+		return err
+	}
+	return nil
+}
+
+// macrosExists checks if the passed macro exists.
+func macroExists(m string) bool {
+	_, err := os.Stat(path.Join(profileDirectory, m))
+	return err == nil
+}
+
+// InstallDefault generates a default profile and installs it in the
+// ProfileDirectory with `apparmor_parser`.
+func InstallDefault(name string) error {
+	// Make sure the path where they want to save the profile exists
+	if err := os.MkdirAll(profileDirectory, 0755); err != nil {
+		return err
+	}
+
+	p := profileData{
+		Name: name,
+	}
+
+	f, err := os.OpenFile(defaultProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		return err
+	}
+	if err := p.generateDefault(f); err != nil {
+		f.Close()
+		return err
+	}
+	f.Close()
+
+	if err := aaparser.LoadProfile(defaultProfilePath); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// IsLoaded checks if a passed profile as been loaded into the kernel.
+func IsLoaded(name 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, name+" ") {
+			return nil
+		}
+	}
+}

+ 50 - 0
profiles/apparmor/template.go

@@ -0,0 +1,50 @@
+// +build linux
+
+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/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}}
+}
+`

+ 27 - 0
profiles/seccomp/fixtures/example.json

@@ -0,0 +1,27 @@
+{
+    "defaultAction": "SCMP_ACT_ERRNO",
+    "syscalls": [
+        {
+            "name": "clone",
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+                {
+                    "index": 0,
+                    "value": 2080505856,
+                    "valueTwo": 0,
+                    "op": "SCMP_CMP_MASKED_EQ"
+                }
+            ]
+        },
+        {
+            "name": "open",
+            "action": "SCMP_ACT_ALLOW",
+            "args": []
+        },
+        {
+            "name": "close",
+            "action": "SCMP_ACT_ALLOW",
+            "args": []
+        }
+    ]
+}

+ 5 - 3
daemon/execdriver/native/seccomp.go → profiles/seccomp/seccomp.go

@@ -1,6 +1,6 @@
 // +build linux
 
-package native
+package seccomp
 
 import (
 	"encoding/json"
@@ -11,11 +11,13 @@ import (
 	"github.com/opencontainers/runc/libcontainer/seccomp"
 )
 
-func getDefaultSeccompProfile() *configs.Seccomp {
+// GetDefaultProfile returns the default seccomp profile.
+func GetDefaultProfile() *configs.Seccomp {
 	return defaultSeccompProfile
 }
 
-func loadSeccompProfile(body string) (*configs.Seccomp, error) {
+// LoadProfile takes a file path a decodes the seccomp profile.
+func LoadProfile(body string) (*configs.Seccomp, error) {
 	var config types.Seccomp
 	if err := json.Unmarshal([]byte(body), &config); err != nil {
 		return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)

+ 1 - 1
daemon/execdriver/native/seccomp_default.go → profiles/seccomp/seccomp_default.go

@@ -1,6 +1,6 @@
 // +build linux,seccomp
 
-package native
+package seccomp
 
 import (
 	"syscall"

+ 19 - 0
profiles/seccomp/seccomp_test.go

@@ -0,0 +1,19 @@
+// +build linux
+
+package seccomp
+
+import (
+	"io/ioutil"
+	"testing"
+)
+
+func TestLoadProfile(t *testing.T) {
+	f, err := ioutil.ReadFile("fixtures/example.json")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := LoadProfile(string(f)); err != nil {
+		t.Fatal(err)
+	}
+}

+ 1 - 1
daemon/execdriver/native/seccomp_unsupported.go → profiles/seccomp/seccomp_unsupported.go

@@ -1,6 +1,6 @@
 // +build linux,!seccomp
 
-package native
+package seccomp
 
 import "github.com/opencontainers/runc/libcontainer/configs"