From b237189e6c8a4f97be59f08c63cdcb1f2f4680a8 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 2 Sep 2016 15:20:54 +0200 Subject: [PATCH] daemon: add a flag to override the default seccomp profile Signed-off-by: Antonio Murdaca --- api/server/router/system/system_routes.go | 14 ++++++-- api/types/types.go | 17 ++++++++-- cli/command/system/info.go | 17 ++++++++-- client/info_test.go | 6 ++-- daemon/config_unix.go | 2 ++ daemon/daemon.go | 7 ++++ daemon/daemon_solaris.go | 4 +++ daemon/daemon_unix.go | 20 +++++++++++- daemon/daemon_windows.go | 4 +++ daemon/info.go | 27 +++++++++++----- daemon/seccomp_linux.go | 13 ++++++-- docs/reference/commandline/dockerd.md | 2 ++ integration-cli/docker_cli_info_unix_test.go | 2 +- integration-cli/docker_cli_run_unix_test.go | 34 ++++++++++++++++++++ man/dockerd.8.md | 4 +++ 15 files changed, 150 insertions(+), 23 deletions(-) diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index 89ff1a79b4..6e16b708fb 100644 --- a/api/server/router/system/system_routes.go +++ b/api/server/router/system/system_routes.go @@ -42,10 +42,20 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht if versions.LessThan(httputils.VersionFromContext(ctx), "1.25") { // TODO: handle this conversion in engine-api type oldInfo struct { - *types.Info + *types.InfoBase ExecutionDriver string + SecurityOptions []string } - return httputils.WriteJSON(w, http.StatusOK, &oldInfo{Info: info, ExecutionDriver: ""}) + old := &oldInfo{ + InfoBase: info.InfoBase, + ExecutionDriver: "", + } + for _, s := range info.SecurityOptions { + if s.Key == "Name" { + old.SecurityOptions = append(old.SecurityOptions, s.Value) + } + } + return httputils.WriteJSON(w, http.StatusOK, old) } return httputils.WriteJSON(w, http.StatusOK, info) } diff --git a/api/types/types.go b/api/types/types.go index bd9bf7bf6a..4dbc6b92c0 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -142,9 +142,9 @@ type Version struct { BuildTime string `json:",omitempty"` } -// Info contains response of Remote API: +// InfoBase contains the base response of Remote API: // GET "/info" -type Info struct { +type InfoBase struct { ID string Containers int ContainersRunning int @@ -191,7 +191,6 @@ type Info struct { ServerVersion string ClusterStore string ClusterAdvertise string - SecurityOptions []string Runtimes map[string]Runtime DefaultRuntime string Swarm swarm.Info @@ -202,6 +201,18 @@ type Info struct { Isolation container.Isolation } +// SecurityOpt holds key/value pair about a security option +type SecurityOpt struct { + Key, Value string +} + +// Info contains response of Remote API: +// GET "/info" +type Info struct { + *InfoBase + SecurityOptions []SecurityOpt +} + // PluginsInfo is a temp struct holding Plugins name // registered with docker daemon. It is used by Info struct type PluginsInfo struct { diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 0bfd9986d2..dfbc83d90a 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -140,9 +140,20 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { } if info.OSType == "linux" { - fmt.Fprintf(dockerCli.Out(), "Security Options:") - ioutils.FprintfIfNotEmpty(dockerCli.Out(), " %s", strings.Join(info.SecurityOptions, " ")) - fmt.Fprintf(dockerCli.Out(), "\n") + if len(info.SecurityOptions) != 0 { + fmt.Fprintf(dockerCli.Out(), "Security Options:\n") + for _, o := range info.SecurityOptions { + switch o.Key { + case "Name": + fmt.Fprintf(dockerCli.Out(), " %s\n", o.Value) + case "Profile": + if o.Key != "default" { + fmt.Fprintf(dockerCli.Err(), " WARNING: You're not using the Docker's default seccomp profile\n") + } + fmt.Fprintf(dockerCli.Out(), " %s: %s\n", o.Key, o.Value) + } + } + } } // Isolation only has meaning on a Windows daemon. diff --git a/client/info_test.go b/client/info_test.go index 79f23c8af2..7af82a8a31 100644 --- a/client/info_test.go +++ b/client/info_test.go @@ -46,8 +46,10 @@ func TestInfo(t *testing.T) { return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) } info := &types.Info{ - ID: "daemonID", - Containers: 3, + InfoBase: &types.InfoBase{ + ID: "daemonID", + Containers: 3, + }, } b, err := json.Marshal(info) if err != nil { diff --git a/daemon/config_unix.go b/daemon/config_unix.go index caca8f97d5..277cb66c24 100644 --- a/daemon/config_unix.go +++ b/daemon/config_unix.go @@ -39,6 +39,7 @@ type Config struct { OOMScoreAdjust int `json:"oom-score-adjust,omitempty"` Init bool `json:"init,omitempty"` InitPath string `json:"init-path,omitempty"` + SeccompProfile string `json:"seccomp-profile,omitempty"` } // bridgeConfig stores all the bridge driver specific @@ -101,6 +102,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) { flags.StringVar(&config.InitPath, "init-path", "", "Path to the docker-init binary") flags.Int64Var(&config.CPURealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds") flags.Int64Var(&config.CPURealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds") + flags.StringVar(&config.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile") config.attachExperimentalFlags(flags) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 97c81a5e9f..86efb851fc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -104,6 +104,9 @@ type Daemon struct { defaultIsolation containertypes.Isolation // Default isolation mode on Windows clusterProvider cluster.Provider cluster Cluster + + seccompProfile []byte + seccompProfilePath string } // HasExperimental returns whether the experimental features of the daemon are enabled or not @@ -530,6 +533,10 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot } }() + if err := d.setupSeccompProfile(); err != nil { + return nil, err + } + // Set the default isolation mode (only applicable on Windows) if err := d.setDefaultIsolation(); err != nil { return nil, fmt.Errorf("error setting default isolation mode: %v", err) diff --git a/daemon/daemon_solaris.go b/daemon/daemon_solaris.go index f555156cfd..9e3dd53147 100644 --- a/daemon/daemon_solaris.go +++ b/daemon/daemon_solaris.go @@ -177,3 +177,7 @@ func setupDaemonProcess(config *Config) error { func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error { return nil } + +func (daemon *Daemon) setupSeccompProfile() error { + return nil +} diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 1e606f7208..245ed44f10 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -4,6 +4,7 @@ package daemon import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "net" @@ -1242,6 +1243,23 @@ func (daemon *Daemon) initCgroupsPath(path string) error { return err } } - + return nil +} + +func (daemon *Daemon) setupSeccompProfile() error { + if daemon.configStore.SeccompProfile != "" { + daemon.seccompProfilePath = daemon.configStore.SeccompProfile + b, err := ioutil.ReadFile(daemon.configStore.SeccompProfile) + if err != nil { + return fmt.Errorf("opening seccomp profile (%s) failed: %v", daemon.configStore.SeccompProfile, err) + } + daemon.seccompProfile = b + p := struct { + DefaultAction string `json:"defaultAction"` + }{} + if err := json.Unmarshal(daemon.seccompProfile, &p); err != nil { + return err + } + } return nil } diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 880c5c4e4c..0402c8eb93 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -532,3 +532,7 @@ func setupDaemonProcess(config *Config) error { func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error { return nil } + +func (daemon *Daemon) setupSeccompProfile() error { + return nil +} diff --git a/daemon/info.go b/daemon/info.go index 00ecebf438..f20fba6578 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -68,22 +68,29 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { } }) - var securityOptions []string + securityOptions := []types.SecurityOpt{} if sysInfo.AppArmor { - securityOptions = append(securityOptions, "apparmor") + securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "apparmor"}) } if sysInfo.Seccomp && supportsSeccomp { - securityOptions = append(securityOptions, "seccomp") + profile := daemon.seccompProfilePath + if profile == "" { + profile = "default" + } + securityOptions = append(securityOptions, + types.SecurityOpt{Key: "Name", Value: "seccomp"}, + types.SecurityOpt{Key: "Profile", Value: profile}, + ) } if selinuxEnabled() { - securityOptions = append(securityOptions, "selinux") + securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "selinux"}) } uid, gid := daemon.GetRemappedUIDGID() if uid != 0 || gid != 0 { - securityOptions = append(securityOptions, "userns") + securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "userns"}) } - v := &types.Info{ + v := &types.InfoBase{ ID: daemon.ID, Containers: int(cRunning + cPaused + cStopped), ContainersRunning: int(cRunning), @@ -120,7 +127,6 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { HTTPProxy: sockets.GetProxyEnv("http_proxy"), HTTPSProxy: sockets.GetProxyEnv("https_proxy"), NoProxy: sockets.GetProxyEnv("no_proxy"), - SecurityOptions: securityOptions, LiveRestoreEnabled: daemon.configStore.LiveRestoreEnabled, Isolation: daemon.defaultIsolation, } @@ -150,7 +156,12 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { } v.Name = hostname - return v, nil + i := &types.Info{ + InfoBase: v, + SecurityOptions: securityOptions, + } + + return i, nil } // SystemVersion returns version information about the daemon. diff --git a/daemon/seccomp_linux.go b/daemon/seccomp_linux.go index 89cbc444ca..7f16733d95 100644 --- a/daemon/seccomp_linux.go +++ b/daemon/seccomp_linux.go @@ -37,9 +37,16 @@ func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error { return err } } else { - profile, err = seccomp.GetDefaultProfile(rs) - if err != nil { - return err + if daemon.seccompProfile != nil { + profile, err = seccomp.LoadProfile(string(daemon.seccompProfile), rs) + if err != nil { + return err + } + } else { + profile, err = seccomp.GetDefaultProfile(rs) + if err != nil { + return err + } } } diff --git a/docs/reference/commandline/dockerd.md b/docs/reference/commandline/dockerd.md index 8522a0624c..a34e021348 100644 --- a/docs/reference/commandline/dockerd.md +++ b/docs/reference/commandline/dockerd.md @@ -74,6 +74,7 @@ Options: -p, --pidfile string Path to use for daemon PID file (default "/var/run/docker.pid") --raw-logs Full timestamps without ANSI coloring --registry-mirror value Preferred Docker registry mirror (default []) + --seccomp-profile value Path to seccomp profile --selinux-enabled Enable selinux support --shutdown-timeout=15 Set the shutdown timeout value in seconds -s, --storage-driver string Storage driver to use @@ -1195,6 +1196,7 @@ This is a full example of the allowed configuration options on Linux: "icc": false, "raw-logs": false, "registry-mirrors": [], + "seccomp-profile": "", "insecure-registries": [], "disable-legacy-registry": false, "default-runtime": "runc", diff --git a/integration-cli/docker_cli_info_unix_test.go b/integration-cli/docker_cli_info_unix_test.go index 900534d68d..b9323060dd 100644 --- a/integration-cli/docker_cli_info_unix_test.go +++ b/integration-cli/docker_cli_info_unix_test.go @@ -11,5 +11,5 @@ func (s *DockerSuite) TestInfoSecurityOptions(c *check.C) { testRequires(c, SameHostDaemon, seccompEnabled, Apparmor, DaemonIsLinux) out, _ := dockerCmd(c, "info") - c.Assert(out, checker.Contains, "Security Options: apparmor seccomp") + c.Assert(out, checker.Contains, "Security Options:\n apparmor\n seccomp\n Profile: default\n") } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 46f2d0677f..29d0462de2 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -1375,3 +1375,37 @@ func (s *DockerDaemonSuite) TestRunSeccompJSONNoArchAndArchMap(c *check.C) { c.Assert(err, check.NotNil) c.Assert(out, checker.Contains, "'architectures' and 'archMap' were specified in the seccomp profile, use either 'architectures' or 'archMap'") } + +func (s *DockerDaemonSuite) TestRunWithDaemonDefaultSeccompProfile(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled) + + err := s.d.StartWithBusybox() + c.Assert(err, check.IsNil) + + // 1) verify I can run containers with the Docker default shipped profile which allows chmod + _, err = s.d.Cmd("run", "busybox", "chmod", "777", ".") + c.Assert(err, check.IsNil) + + jsonData := `{ + "defaultAction": "SCMP_ACT_ALLOW", + "syscalls": [ + { + "name": "chmod", + "action": "SCMP_ACT_ERRNO" + } + ] +}` + tmpFile, err := ioutil.TempFile("", "profile.json") + c.Assert(err, check.IsNil) + defer tmpFile.Close() + _, err = tmpFile.Write([]byte(jsonData)) + c.Assert(err, check.IsNil) + + // 2) restart the daemon and add a custom seccomp profile in which we deny chmod + err = s.d.Restart("--seccomp-profile=" + tmpFile.Name()) + c.Assert(err, check.IsNil) + + out, err := s.d.Cmd("run", "busybox", "chmod", "777", ".") + c.Assert(err, check.NotNil) + c.Assert(out, checker.Contains, "Operation not permitted") +} diff --git a/man/dockerd.8.md b/man/dockerd.8.md index c20f0bb140..f7fd231149 100644 --- a/man/dockerd.8.md +++ b/man/dockerd.8.md @@ -56,6 +56,7 @@ dockerd - Enable daemon mode [**--raw-logs**] [**--registry-mirror**[=*[]*]] [**-s**|**--storage-driver**[=*STORAGE-DRIVER*]] +[**--seccomp-profile**[=*SECCOMP-PROFILE-PATH*]] [**--selinux-enabled**] [**--shutdown-timeout**[=*15*]] [**--storage-opt**[=*[]*]] @@ -248,6 +249,9 @@ output otherwise. **-s**, **--storage-driver**="" Force the Docker runtime to use a specific storage driver. +**--seccomp-profile**="" + Path to seccomp profile. + **--selinux-enabled**=*true*|*false* Enable selinux support. Default is false.