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:
commit
c5380f9118
10 changed files with 222 additions and 171 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
derr "github.com/docker/docker/errors"
|
derr "github.com/docker/docker/errors"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
|
"github.com/docker/docker/profiles/seccomp"
|
||||||
|
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||||
|
@ -71,7 +72,7 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SeccompProfile == "" {
|
if c.SeccompProfile == "" {
|
||||||
container.Seccomp = getDefaultSeccompProfile()
|
container.Seccomp = seccomp.GetDefaultProfile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add CAP_ prefix to all caps for new libcontainer update to match
|
// 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" {
|
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
|
||||||
container.Seccomp, err = loadSeccompProfile(c.SeccompProfile)
|
container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
sysinfo "github.com/docker/docker/pkg/system"
|
sysinfo "github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
aaprofile "github.com/docker/docker/profiles/apparmor"
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||||
|
@ -33,6 +34,8 @@ import (
|
||||||
const (
|
const (
|
||||||
DriverName = "native"
|
DriverName = "native"
|
||||||
Version = "0.2"
|
Version = "0.2"
|
||||||
|
|
||||||
|
defaultApparmorProfile = "docker-default"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver contains all information for native driver,
|
// Driver contains all information for native driver,
|
||||||
|
@ -57,13 +60,13 @@ func NewDriver(root string, options []string) (*Driver, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if apparmor.IsEnabled() {
|
if apparmor.IsEnabled() {
|
||||||
if err := installAppArmorProfile(); err != nil {
|
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
|
||||||
apparmorProfiles := []string{"docker-default"}
|
apparmorProfiles := []string{defaultApparmorProfile}
|
||||||
|
|
||||||
// Allow daemon to run if loading failed, but are active
|
// Allow daemon to run if loading failed, but are active
|
||||||
// (possibly through another run, manually, or via system startup)
|
// (possibly through another run, manually, or via system startup)
|
||||||
for _, policy := range apparmorProfiles {
|
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)
|
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
profiles/apparmor/apparmor.go
Normal file
110
profiles/apparmor/apparmor.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
profiles/apparmor/template.go
Normal file
50
profiles/apparmor/template.go
Normal 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}}
|
||||||
|
}
|
||||||
|
`
|
27
profiles/seccomp/fixtures/example.json
Executable file
27
profiles/seccomp/fixtures/example.json
Executable 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package native
|
package seccomp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -11,11 +11,13 @@ import (
|
||||||
"github.com/opencontainers/runc/libcontainer/seccomp"
|
"github.com/opencontainers/runc/libcontainer/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getDefaultSeccompProfile() *configs.Seccomp {
|
// GetDefaultProfile returns the default seccomp profile.
|
||||||
|
func GetDefaultProfile() *configs.Seccomp {
|
||||||
return defaultSeccompProfile
|
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
|
var config types.Seccomp
|
||||||
if err := json.Unmarshal([]byte(body), &config); err != nil {
|
if err := json.Unmarshal([]byte(body), &config); err != nil {
|
||||||
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
|
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
|
|
@ -1,6 +1,6 @@
|
||||||
// +build linux,seccomp
|
// +build linux,seccomp
|
||||||
|
|
||||||
package native
|
package seccomp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
19
profiles/seccomp/seccomp_test.go
Normal file
19
profiles/seccomp/seccomp_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// +build linux,!seccomp
|
// +build linux,!seccomp
|
||||||
|
|
||||||
package native
|
package seccomp
|
||||||
|
|
||||||
import "github.com/opencontainers/runc/libcontainer/configs"
|
import "github.com/opencontainers/runc/libcontainer/configs"
|
||||||
|
|
Loading…
Reference in a new issue