1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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

Refactor sec profiles their own packages
This commit is contained in:
Arnaud Porterie 2016-01-25 22:18:56 -08:00
commit c5380f9118
10 changed files with 222 additions and 171 deletions

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}
}
}

View file

@ -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}}
}
`

View file

@ -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": []
}
]
}

View file

@ -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)

View file

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

View file

@ -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)
}
}

View file

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