daemon: add a flag to override the default seccomp profile
| ... | ... |
@@ -42,10 +42,20 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht |
| 42 | 42 |
if versions.LessThan(httputils.VersionFromContext(ctx), "1.25") {
|
| 43 | 43 |
// TODO: handle this conversion in engine-api |
| 44 | 44 |
type oldInfo struct {
|
| 45 |
- *types.Info |
|
| 45 |
+ *types.InfoBase |
|
| 46 | 46 |
ExecutionDriver string |
| 47 |
+ SecurityOptions []string |
|
| 47 | 48 |
} |
| 48 |
- return httputils.WriteJSON(w, http.StatusOK, &oldInfo{Info: info, ExecutionDriver: "<not supported>"})
|
|
| 49 |
+ old := &oldInfo{
|
|
| 50 |
+ InfoBase: info.InfoBase, |
|
| 51 |
+ ExecutionDriver: "<not supported>", |
|
| 52 |
+ } |
|
| 53 |
+ for _, s := range info.SecurityOptions {
|
|
| 54 |
+ if s.Key == "Name" {
|
|
| 55 |
+ old.SecurityOptions = append(old.SecurityOptions, s.Value) |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ return httputils.WriteJSON(w, http.StatusOK, old) |
|
| 49 | 59 |
} |
| 50 | 60 |
return httputils.WriteJSON(w, http.StatusOK, info) |
| 51 | 61 |
} |
| ... | ... |
@@ -142,9 +142,9 @@ type Version struct {
|
| 142 | 142 |
BuildTime string `json:",omitempty"` |
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-// Info contains response of Remote API: |
|
| 145 |
+// InfoBase contains the base response of Remote API: |
|
| 146 | 146 |
// GET "/info" |
| 147 |
-type Info struct {
|
|
| 147 |
+type InfoBase struct {
|
|
| 148 | 148 |
ID string |
| 149 | 149 |
Containers int |
| 150 | 150 |
ContainersRunning int |
| ... | ... |
@@ -191,7 +191,6 @@ type Info struct {
|
| 191 | 191 |
ServerVersion string |
| 192 | 192 |
ClusterStore string |
| 193 | 193 |
ClusterAdvertise string |
| 194 |
- SecurityOptions []string |
|
| 195 | 194 |
Runtimes map[string]Runtime |
| 196 | 195 |
DefaultRuntime string |
| 197 | 196 |
Swarm swarm.Info |
| ... | ... |
@@ -202,6 +201,18 @@ type Info struct {
|
| 202 | 202 |
Isolation container.Isolation |
| 203 | 203 |
} |
| 204 | 204 |
|
| 205 |
+// SecurityOpt holds key/value pair about a security option |
|
| 206 |
+type SecurityOpt struct {
|
|
| 207 |
+ Key, Value string |
|
| 208 |
+} |
|
| 209 |
+ |
|
| 210 |
+// Info contains response of Remote API: |
|
| 211 |
+// GET "/info" |
|
| 212 |
+type Info struct {
|
|
| 213 |
+ *InfoBase |
|
| 214 |
+ SecurityOptions []SecurityOpt |
|
| 215 |
+} |
|
| 216 |
+ |
|
| 205 | 217 |
// PluginsInfo is a temp struct holding Plugins name |
| 206 | 218 |
// registered with docker daemon. It is used by Info struct |
| 207 | 219 |
type PluginsInfo struct {
|
| ... | ... |
@@ -140,9 +140,20 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
|
| 140 | 140 |
} |
| 141 | 141 |
|
| 142 | 142 |
if info.OSType == "linux" {
|
| 143 |
- fmt.Fprintf(dockerCli.Out(), "Security Options:") |
|
| 144 |
- ioutils.FprintfIfNotEmpty(dockerCli.Out(), " %s", strings.Join(info.SecurityOptions, " ")) |
|
| 145 |
- fmt.Fprintf(dockerCli.Out(), "\n") |
|
| 143 |
+ if len(info.SecurityOptions) != 0 {
|
|
| 144 |
+ fmt.Fprintf(dockerCli.Out(), "Security Options:\n") |
|
| 145 |
+ for _, o := range info.SecurityOptions {
|
|
| 146 |
+ switch o.Key {
|
|
| 147 |
+ case "Name": |
|
| 148 |
+ fmt.Fprintf(dockerCli.Out(), " %s\n", o.Value) |
|
| 149 |
+ case "Profile": |
|
| 150 |
+ if o.Key != "default" {
|
|
| 151 |
+ fmt.Fprintf(dockerCli.Err(), " WARNING: You're not using the Docker's default seccomp profile\n") |
|
| 152 |
+ } |
|
| 153 |
+ fmt.Fprintf(dockerCli.Out(), " %s: %s\n", o.Key, o.Value) |
|
| 154 |
+ } |
|
| 155 |
+ } |
|
| 156 |
+ } |
|
| 146 | 157 |
} |
| 147 | 158 |
|
| 148 | 159 |
// Isolation only has meaning on a Windows daemon. |
| ... | ... |
@@ -46,8 +46,10 @@ func TestInfo(t *testing.T) {
|
| 46 | 46 |
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
| 47 | 47 |
} |
| 48 | 48 |
info := &types.Info{
|
| 49 |
- ID: "daemonID", |
|
| 50 |
- Containers: 3, |
|
| 49 |
+ InfoBase: &types.InfoBase{
|
|
| 50 |
+ ID: "daemonID", |
|
| 51 |
+ Containers: 3, |
|
| 52 |
+ }, |
|
| 51 | 53 |
} |
| 52 | 54 |
b, err := json.Marshal(info) |
| 53 | 55 |
if err != nil {
|
| ... | ... |
@@ -39,6 +39,7 @@ type Config struct {
|
| 39 | 39 |
OOMScoreAdjust int `json:"oom-score-adjust,omitempty"` |
| 40 | 40 |
Init bool `json:"init,omitempty"` |
| 41 | 41 |
InitPath string `json:"init-path,omitempty"` |
| 42 |
+ SeccompProfile string `json:"seccomp-profile,omitempty"` |
|
| 42 | 43 |
} |
| 43 | 44 |
|
| 44 | 45 |
// bridgeConfig stores all the bridge driver specific |
| ... | ... |
@@ -101,6 +102,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
|
| 101 | 101 |
flags.StringVar(&config.InitPath, "init-path", "", "Path to the docker-init binary") |
| 102 | 102 |
flags.Int64Var(&config.CPURealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds") |
| 103 | 103 |
flags.Int64Var(&config.CPURealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds") |
| 104 |
+ flags.StringVar(&config.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile") |
|
| 104 | 105 |
|
| 105 | 106 |
config.attachExperimentalFlags(flags) |
| 106 | 107 |
} |
| ... | ... |
@@ -104,6 +104,9 @@ type Daemon struct {
|
| 104 | 104 |
defaultIsolation containertypes.Isolation // Default isolation mode on Windows |
| 105 | 105 |
clusterProvider cluster.Provider |
| 106 | 106 |
cluster Cluster |
| 107 |
+ |
|
| 108 |
+ seccompProfile []byte |
|
| 109 |
+ seccompProfilePath string |
|
| 107 | 110 |
} |
| 108 | 111 |
|
| 109 | 112 |
// HasExperimental returns whether the experimental features of the daemon are enabled or not |
| ... | ... |
@@ -526,6 +529,10 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot |
| 526 | 526 |
} |
| 527 | 527 |
}() |
| 528 | 528 |
|
| 529 |
+ if err := d.setupSeccompProfile(); err != nil {
|
|
| 530 |
+ return nil, err |
|
| 531 |
+ } |
|
| 532 |
+ |
|
| 529 | 533 |
// Set the default isolation mode (only applicable on Windows) |
| 530 | 534 |
if err := d.setDefaultIsolation(); err != nil {
|
| 531 | 535 |
return nil, fmt.Errorf("error setting default isolation mode: %v", err)
|
| ... | ... |
@@ -4,6 +4,7 @@ package daemon |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 | 6 |
"bytes" |
| 7 |
+ "encoding/json" |
|
| 7 | 8 |
"fmt" |
| 8 | 9 |
"io/ioutil" |
| 9 | 10 |
"net" |
| ... | ... |
@@ -1240,6 +1241,23 @@ func (daemon *Daemon) initCgroupsPath(path string) error {
|
| 1240 | 1240 |
return err |
| 1241 | 1241 |
} |
| 1242 | 1242 |
} |
| 1243 |
+ return nil |
|
| 1244 |
+} |
|
| 1243 | 1245 |
|
| 1246 |
+func (daemon *Daemon) setupSeccompProfile() error {
|
|
| 1247 |
+ if daemon.configStore.SeccompProfile != "" {
|
|
| 1248 |
+ daemon.seccompProfilePath = daemon.configStore.SeccompProfile |
|
| 1249 |
+ b, err := ioutil.ReadFile(daemon.configStore.SeccompProfile) |
|
| 1250 |
+ if err != nil {
|
|
| 1251 |
+ return fmt.Errorf("opening seccomp profile (%s) failed: %v", daemon.configStore.SeccompProfile, err)
|
|
| 1252 |
+ } |
|
| 1253 |
+ daemon.seccompProfile = b |
|
| 1254 |
+ p := struct {
|
|
| 1255 |
+ DefaultAction string `json:"defaultAction"` |
|
| 1256 |
+ }{}
|
|
| 1257 |
+ if err := json.Unmarshal(daemon.seccompProfile, &p); err != nil {
|
|
| 1258 |
+ return err |
|
| 1259 |
+ } |
|
| 1260 |
+ } |
|
| 1244 | 1261 |
return nil |
| 1245 | 1262 |
} |
| ... | ... |
@@ -68,22 +68,29 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 68 | 68 |
} |
| 69 | 69 |
}) |
| 70 | 70 |
|
| 71 |
- var securityOptions []string |
|
| 71 |
+ securityOptions := []types.SecurityOpt{}
|
|
| 72 | 72 |
if sysInfo.AppArmor {
|
| 73 |
- securityOptions = append(securityOptions, "apparmor") |
|
| 73 |
+ securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "apparmor"})
|
|
| 74 | 74 |
} |
| 75 | 75 |
if sysInfo.Seccomp && supportsSeccomp {
|
| 76 |
- securityOptions = append(securityOptions, "seccomp") |
|
| 76 |
+ profile := daemon.seccompProfilePath |
|
| 77 |
+ if profile == "" {
|
|
| 78 |
+ profile = "default" |
|
| 79 |
+ } |
|
| 80 |
+ securityOptions = append(securityOptions, |
|
| 81 |
+ types.SecurityOpt{Key: "Name", Value: "seccomp"},
|
|
| 82 |
+ types.SecurityOpt{Key: "Profile", Value: profile},
|
|
| 83 |
+ ) |
|
| 77 | 84 |
} |
| 78 | 85 |
if selinuxEnabled() {
|
| 79 |
- securityOptions = append(securityOptions, "selinux") |
|
| 86 |
+ securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "selinux"})
|
|
| 80 | 87 |
} |
| 81 | 88 |
uid, gid := daemon.GetRemappedUIDGID() |
| 82 | 89 |
if uid != 0 || gid != 0 {
|
| 83 |
- securityOptions = append(securityOptions, "userns") |
|
| 90 |
+ securityOptions = append(securityOptions, types.SecurityOpt{Key: "Name", Value: "userns"})
|
|
| 84 | 91 |
} |
| 85 | 92 |
|
| 86 |
- v := &types.Info{
|
|
| 93 |
+ v := &types.InfoBase{
|
|
| 87 | 94 |
ID: daemon.ID, |
| 88 | 95 |
Containers: int(cRunning + cPaused + cStopped), |
| 89 | 96 |
ContainersRunning: int(cRunning), |
| ... | ... |
@@ -120,7 +127,6 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 120 | 120 |
HTTPProxy: sockets.GetProxyEnv("http_proxy"),
|
| 121 | 121 |
HTTPSProxy: sockets.GetProxyEnv("https_proxy"),
|
| 122 | 122 |
NoProxy: sockets.GetProxyEnv("no_proxy"),
|
| 123 |
- SecurityOptions: securityOptions, |
|
| 124 | 123 |
LiveRestoreEnabled: daemon.configStore.LiveRestoreEnabled, |
| 125 | 124 |
Isolation: daemon.defaultIsolation, |
| 126 | 125 |
} |
| ... | ... |
@@ -150,7 +156,12 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 150 | 150 |
} |
| 151 | 151 |
v.Name = hostname |
| 152 | 152 |
|
| 153 |
- return v, nil |
|
| 153 |
+ i := &types.Info{
|
|
| 154 |
+ InfoBase: v, |
|
| 155 |
+ SecurityOptions: securityOptions, |
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ return i, nil |
|
| 154 | 159 |
} |
| 155 | 160 |
|
| 156 | 161 |
// SystemVersion returns version information about the daemon. |
| ... | ... |
@@ -37,9 +37,16 @@ func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error {
|
| 37 | 37 |
return err |
| 38 | 38 |
} |
| 39 | 39 |
} else {
|
| 40 |
- profile, err = seccomp.GetDefaultProfile(rs) |
|
| 41 |
- if err != nil {
|
|
| 42 |
- return err |
|
| 40 |
+ if daemon.seccompProfile != nil {
|
|
| 41 |
+ profile, err = seccomp.LoadProfile(string(daemon.seccompProfile), rs) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ } else {
|
|
| 46 |
+ profile, err = seccomp.GetDefaultProfile(rs) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 43 | 50 |
} |
| 44 | 51 |
} |
| 45 | 52 |
|
| ... | ... |
@@ -74,6 +74,7 @@ Options: |
| 74 | 74 |
-p, --pidfile string Path to use for daemon PID file (default "/var/run/docker.pid") |
| 75 | 75 |
--raw-logs Full timestamps without ANSI coloring |
| 76 | 76 |
--registry-mirror value Preferred Docker registry mirror (default []) |
| 77 |
+ --seccomp-profile value Path to seccomp profile |
|
| 77 | 78 |
--selinux-enabled Enable selinux support |
| 78 | 79 |
--shutdown-timeout=15 Set the shutdown timeout value in seconds |
| 79 | 80 |
-s, --storage-driver string Storage driver to use |
| ... | ... |
@@ -1195,6 +1196,7 @@ This is a full example of the allowed configuration options on Linux: |
| 1195 | 1195 |
"icc": false, |
| 1196 | 1196 |
"raw-logs": false, |
| 1197 | 1197 |
"registry-mirrors": [], |
| 1198 |
+ "seccomp-profile": "", |
|
| 1198 | 1199 |
"insecure-registries": [], |
| 1199 | 1200 |
"disable-legacy-registry": false, |
| 1200 | 1201 |
"default-runtime": "runc", |
| ... | ... |
@@ -11,5 +11,5 @@ func (s *DockerSuite) TestInfoSecurityOptions(c *check.C) {
|
| 11 | 11 |
testRequires(c, SameHostDaemon, seccompEnabled, Apparmor, DaemonIsLinux) |
| 12 | 12 |
|
| 13 | 13 |
out, _ := dockerCmd(c, "info") |
| 14 |
- c.Assert(out, checker.Contains, "Security Options: apparmor seccomp") |
|
| 14 |
+ c.Assert(out, checker.Contains, "Security Options:\n apparmor\n seccomp\n Profile: default\n") |
|
| 15 | 15 |
} |
| ... | ... |
@@ -1375,3 +1375,37 @@ func (s *DockerDaemonSuite) TestRunSeccompJSONNoArchAndArchMap(c *check.C) {
|
| 1375 | 1375 |
c.Assert(err, check.NotNil) |
| 1376 | 1376 |
c.Assert(out, checker.Contains, "'architectures' and 'archMap' were specified in the seccomp profile, use either 'architectures' or 'archMap'") |
| 1377 | 1377 |
} |
| 1378 |
+ |
|
| 1379 |
+func (s *DockerDaemonSuite) TestRunWithDaemonDefaultSeccompProfile(c *check.C) {
|
|
| 1380 |
+ testRequires(c, SameHostDaemon, seccompEnabled) |
|
| 1381 |
+ |
|
| 1382 |
+ err := s.d.StartWithBusybox() |
|
| 1383 |
+ c.Assert(err, check.IsNil) |
|
| 1384 |
+ |
|
| 1385 |
+ // 1) verify I can run containers with the Docker default shipped profile which allows chmod |
|
| 1386 |
+ _, err = s.d.Cmd("run", "busybox", "chmod", "777", ".")
|
|
| 1387 |
+ c.Assert(err, check.IsNil) |
|
| 1388 |
+ |
|
| 1389 |
+ jsonData := `{
|
|
| 1390 |
+ "defaultAction": "SCMP_ACT_ALLOW", |
|
| 1391 |
+ "syscalls": [ |
|
| 1392 |
+ {
|
|
| 1393 |
+ "name": "chmod", |
|
| 1394 |
+ "action": "SCMP_ACT_ERRNO" |
|
| 1395 |
+ } |
|
| 1396 |
+ ] |
|
| 1397 |
+}` |
|
| 1398 |
+ tmpFile, err := ioutil.TempFile("", "profile.json")
|
|
| 1399 |
+ c.Assert(err, check.IsNil) |
|
| 1400 |
+ defer tmpFile.Close() |
|
| 1401 |
+ _, err = tmpFile.Write([]byte(jsonData)) |
|
| 1402 |
+ c.Assert(err, check.IsNil) |
|
| 1403 |
+ |
|
| 1404 |
+ // 2) restart the daemon and add a custom seccomp profile in which we deny chmod |
|
| 1405 |
+ err = s.d.Restart("--seccomp-profile=" + tmpFile.Name())
|
|
| 1406 |
+ c.Assert(err, check.IsNil) |
|
| 1407 |
+ |
|
| 1408 |
+ out, err := s.d.Cmd("run", "busybox", "chmod", "777", ".")
|
|
| 1409 |
+ c.Assert(err, check.NotNil) |
|
| 1410 |
+ c.Assert(out, checker.Contains, "Operation not permitted") |
|
| 1411 |
+} |
| ... | ... |
@@ -56,6 +56,7 @@ dockerd - Enable daemon mode |
| 56 | 56 |
[**--raw-logs**] |
| 57 | 57 |
[**--registry-mirror**[=*[]*]] |
| 58 | 58 |
[**-s**|**--storage-driver**[=*STORAGE-DRIVER*]] |
| 59 |
+[**--seccomp-profile**[=*SECCOMP-PROFILE-PATH*]] |
|
| 59 | 60 |
[**--selinux-enabled**] |
| 60 | 61 |
[**--shutdown-timeout**[=*15*]] |
| 61 | 62 |
[**--storage-opt**[=*[]*]] |
| ... | ... |
@@ -248,6 +249,9 @@ output otherwise. |
| 248 | 248 |
**-s**, **--storage-driver**="" |
| 249 | 249 |
Force the Docker runtime to use a specific storage driver. |
| 250 | 250 |
|
| 251 |
+**--seccomp-profile**="" |
|
| 252 |
+ Path to seccomp profile. |
|
| 253 |
+ |
|
| 251 | 254 |
**--selinux-enabled**=*true*|*false* |
| 252 | 255 |
Enable selinux support. Default is false. |
| 253 | 256 |
|