Browse code

Merge pull request #26276 from runcom/seccomp-conf

daemon: add a flag to override the default seccomp profile

Justin Cormack authored on 2016/11/05 00:45:30
Showing 15 changed files
... ...
@@ -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)
... ...
@@ -177,3 +177,7 @@ func setupDaemonProcess(config *Config) error {
177 177
 func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
178 178
 	return nil
179 179
 }
180
+
181
+func (daemon *Daemon) setupSeccompProfile() error {
182
+	return nil
183
+}
... ...
@@ -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
 }
... ...
@@ -532,3 +532,7 @@ func setupDaemonProcess(config *Config) error {
532 532
 func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error {
533 533
 	return nil
534 534
 }
535
+
536
+func (daemon *Daemon) setupSeccompProfile() error {
537
+	return nil
538
+}
... ...
@@ -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