Browse code

Merge pull request #37294 from jstarks/lcow_caps

lcow: Allow the client to adjust capabilities and device cgroup rules

Sebastiaan van Stijn authored on 2018/06/20 14:14:07
Showing 5 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+package caps // import "github.com/docker/docker/daemon/caps"
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/syndtr/gocapability/capability"
7
+)
8
+
9
+var capabilityList Capabilities
10
+
11
+func init() {
12
+	last := capability.CAP_LAST_CAP
13
+	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
14
+	if last == capability.Cap(63) {
15
+		last = capability.CAP_BLOCK_SUSPEND
16
+	}
17
+	for _, cap := range capability.List() {
18
+		if cap > last {
19
+			continue
20
+		}
21
+		capabilityList = append(capabilityList,
22
+			&CapabilityMapping{
23
+				Key:   "CAP_" + strings.ToUpper(cap.String()),
24
+				Value: cap,
25
+			},
26
+		)
27
+	}
28
+}
29
+
30
+type (
31
+	// CapabilityMapping maps linux capability name to its value of capability.Cap type
32
+	// Capabilities is one of the security systems in Linux Security Module (LSM)
33
+	// framework provided by the kernel.
34
+	// For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html
35
+	CapabilityMapping struct {
36
+		Key   string         `json:"key,omitempty"`
37
+		Value capability.Cap `json:"value,omitempty"`
38
+	}
39
+	// Capabilities contains all CapabilityMapping
40
+	Capabilities []*CapabilityMapping
41
+)
42
+
43
+// String returns <key> of CapabilityMapping
44
+func (c *CapabilityMapping) String() string {
45
+	return c.Key
46
+}
47
+
48
+// GetCapability returns CapabilityMapping which contains specific key
49
+func GetCapability(key string) *CapabilityMapping {
50
+	for _, capp := range capabilityList {
51
+		if capp.Key == key {
52
+			cpy := *capp
53
+			return &cpy
54
+		}
55
+	}
56
+	return nil
57
+}
58
+
59
+// GetAllCapabilities returns all of the capabilities
60
+func GetAllCapabilities() []string {
61
+	output := make([]string, len(capabilityList))
62
+	for i, capability := range capabilityList {
63
+		output[i] = capability.String()
64
+	}
65
+	return output
66
+}
67
+
68
+// inSlice tests whether a string is contained in a slice of strings or not.
69
+// Comparison is case insensitive
70
+func inSlice(slice []string, s string) bool {
71
+	for _, ss := range slice {
72
+		if strings.ToLower(s) == strings.ToLower(ss) {
73
+			return true
74
+		}
75
+	}
76
+	return false
77
+}
78
+
79
+// TweakCapabilities can tweak capabilities by adding or dropping capabilities
80
+// based on the basics capabilities.
81
+func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
82
+	var (
83
+		newCaps []string
84
+		allCaps = GetAllCapabilities()
85
+	)
86
+
87
+	// FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix
88
+	// Currently they are mixed in here. We should do conversion in one place.
89
+
90
+	// look for invalid cap in the drop list
91
+	for _, cap := range drops {
92
+		if strings.ToLower(cap) == "all" {
93
+			continue
94
+		}
95
+
96
+		if !inSlice(allCaps, "CAP_"+cap) {
97
+			return nil, fmt.Errorf("Unknown capability drop: %q", cap)
98
+		}
99
+	}
100
+
101
+	// handle --cap-add=all
102
+	if inSlice(adds, "all") {
103
+		basics = allCaps
104
+	}
105
+
106
+	if !inSlice(drops, "all") {
107
+		for _, cap := range basics {
108
+			// skip `all` already handled above
109
+			if strings.ToLower(cap) == "all" {
110
+				continue
111
+			}
112
+
113
+			// if we don't drop `all`, add back all the non-dropped caps
114
+			if !inSlice(drops, cap[4:]) {
115
+				newCaps = append(newCaps, strings.ToUpper(cap))
116
+			}
117
+		}
118
+	}
119
+
120
+	for _, cap := range adds {
121
+		// skip `all` already handled above
122
+		if strings.ToLower(cap) == "all" {
123
+			continue
124
+		}
125
+
126
+		cap = "CAP_" + cap
127
+
128
+		if !inSlice(allCaps, cap) {
129
+			return nil, fmt.Errorf("Unknown capability to add: %q", cap)
130
+		}
131
+
132
+		// add cap if not already in the list
133
+		if !inSlice(newCaps, cap) {
134
+			newCaps = append(newCaps, strings.ToUpper(cap))
135
+		}
136
+	}
137
+	return newCaps, nil
138
+}
0 139
deleted file mode 100644
... ...
@@ -1,141 +0,0 @@
1
-// +build !windows
2
-
3
-package caps // import "github.com/docker/docker/daemon/caps"
4
-
5
-import (
6
-	"fmt"
7
-	"strings"
8
-
9
-	"github.com/syndtr/gocapability/capability"
10
-)
11
-
12
-var capabilityList Capabilities
13
-
14
-func init() {
15
-	last := capability.CAP_LAST_CAP
16
-	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
17
-	if last == capability.Cap(63) {
18
-		last = capability.CAP_BLOCK_SUSPEND
19
-	}
20
-	for _, cap := range capability.List() {
21
-		if cap > last {
22
-			continue
23
-		}
24
-		capabilityList = append(capabilityList,
25
-			&CapabilityMapping{
26
-				Key:   "CAP_" + strings.ToUpper(cap.String()),
27
-				Value: cap,
28
-			},
29
-		)
30
-	}
31
-}
32
-
33
-type (
34
-	// CapabilityMapping maps linux capability name to its value of capability.Cap type
35
-	// Capabilities is one of the security systems in Linux Security Module (LSM)
36
-	// framework provided by the kernel.
37
-	// For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html
38
-	CapabilityMapping struct {
39
-		Key   string         `json:"key,omitempty"`
40
-		Value capability.Cap `json:"value,omitempty"`
41
-	}
42
-	// Capabilities contains all CapabilityMapping
43
-	Capabilities []*CapabilityMapping
44
-)
45
-
46
-// String returns <key> of CapabilityMapping
47
-func (c *CapabilityMapping) String() string {
48
-	return c.Key
49
-}
50
-
51
-// GetCapability returns CapabilityMapping which contains specific key
52
-func GetCapability(key string) *CapabilityMapping {
53
-	for _, capp := range capabilityList {
54
-		if capp.Key == key {
55
-			cpy := *capp
56
-			return &cpy
57
-		}
58
-	}
59
-	return nil
60
-}
61
-
62
-// GetAllCapabilities returns all of the capabilities
63
-func GetAllCapabilities() []string {
64
-	output := make([]string, len(capabilityList))
65
-	for i, capability := range capabilityList {
66
-		output[i] = capability.String()
67
-	}
68
-	return output
69
-}
70
-
71
-// inSlice tests whether a string is contained in a slice of strings or not.
72
-// Comparison is case insensitive
73
-func inSlice(slice []string, s string) bool {
74
-	for _, ss := range slice {
75
-		if strings.ToLower(s) == strings.ToLower(ss) {
76
-			return true
77
-		}
78
-	}
79
-	return false
80
-}
81
-
82
-// TweakCapabilities can tweak capabilities by adding or dropping capabilities
83
-// based on the basics capabilities.
84
-func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
85
-	var (
86
-		newCaps []string
87
-		allCaps = GetAllCapabilities()
88
-	)
89
-
90
-	// FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix
91
-	// Currently they are mixed in here. We should do conversion in one place.
92
-
93
-	// look for invalid cap in the drop list
94
-	for _, cap := range drops {
95
-		if strings.ToLower(cap) == "all" {
96
-			continue
97
-		}
98
-
99
-		if !inSlice(allCaps, "CAP_"+cap) {
100
-			return nil, fmt.Errorf("Unknown capability drop: %q", cap)
101
-		}
102
-	}
103
-
104
-	// handle --cap-add=all
105
-	if inSlice(adds, "all") {
106
-		basics = allCaps
107
-	}
108
-
109
-	if !inSlice(drops, "all") {
110
-		for _, cap := range basics {
111
-			// skip `all` already handled above
112
-			if strings.ToLower(cap) == "all" {
113
-				continue
114
-			}
115
-
116
-			// if we don't drop `all`, add back all the non-dropped caps
117
-			if !inSlice(drops, cap[4:]) {
118
-				newCaps = append(newCaps, strings.ToUpper(cap))
119
-			}
120
-		}
121
-	}
122
-
123
-	for _, cap := range adds {
124
-		// skip `all` already handled above
125
-		if strings.ToLower(cap) == "all" {
126
-			continue
127
-		}
128
-
129
-		cap = "CAP_" + cap
130
-
131
-		if !inSlice(allCaps, cap) {
132
-			return nil, fmt.Errorf("Unknown capability to add: %q", cap)
133
-		}
134
-
135
-		// add cap if not already in the list
136
-		if !inSlice(newCaps, cap) {
137
-			newCaps = append(newCaps, strings.ToUpper(cap))
138
-		}
139
-	}
140
-	return newCaps, nil
141
-}
142 1
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package daemon // import "github.com/docker/docker/daemon"
1
+
2
+import (
3
+	"fmt"
4
+	"regexp"
5
+	"strconv"
6
+
7
+	"github.com/docker/docker/container"
8
+	"github.com/docker/docker/daemon/caps"
9
+	specs "github.com/opencontainers/runtime-spec/specs-go"
10
+)
11
+
12
+// nolint: gosimple
13
+var (
14
+	deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\*) ([rwm]{1,3})$")
15
+)
16
+
17
+func setCapabilities(s *specs.Spec, c *container.Container) error {
18
+	var caplist []string
19
+	var err error
20
+	if c.HostConfig.Privileged {
21
+		caplist = caps.GetAllCapabilities()
22
+	} else {
23
+		caplist, err = caps.TweakCapabilities(s.Process.Capabilities.Bounding, c.HostConfig.CapAdd, c.HostConfig.CapDrop)
24
+		if err != nil {
25
+			return err
26
+		}
27
+	}
28
+	s.Process.Capabilities.Effective = caplist
29
+	s.Process.Capabilities.Bounding = caplist
30
+	s.Process.Capabilities.Permitted = caplist
31
+	s.Process.Capabilities.Inheritable = caplist
32
+	// setUser has already been executed here
33
+	// if non root drop capabilities in the way execve does
34
+	if s.Process.User.UID != 0 {
35
+		s.Process.Capabilities.Effective = []string{}
36
+		s.Process.Capabilities.Permitted = []string{}
37
+	}
38
+	return nil
39
+}
40
+
41
+func appendDevicePermissionsFromCgroupRules(devPermissions []specs.LinuxDeviceCgroup, rules []string) ([]specs.LinuxDeviceCgroup, error) {
42
+	for _, deviceCgroupRule := range rules {
43
+		ss := deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1)
44
+		if len(ss[0]) != 5 {
45
+			return nil, fmt.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
46
+		}
47
+		matches := ss[0]
48
+
49
+		dPermissions := specs.LinuxDeviceCgroup{
50
+			Allow:  true,
51
+			Type:   matches[1],
52
+			Access: matches[4],
53
+		}
54
+		if matches[2] == "*" {
55
+			major := int64(-1)
56
+			dPermissions.Major = &major
57
+		} else {
58
+			major, err := strconv.ParseInt(matches[2], 10, 64)
59
+			if err != nil {
60
+				return nil, fmt.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
61
+			}
62
+			dPermissions.Major = &major
63
+		}
64
+		if matches[3] == "*" {
65
+			minor := int64(-1)
66
+			dPermissions.Minor = &minor
67
+		} else {
68
+			minor, err := strconv.ParseInt(matches[3], 10, 64)
69
+			if err != nil {
70
+				return nil, fmt.Errorf("invalid minor value in device cgroup rule format: '%s'", deviceCgroupRule)
71
+			}
72
+			dPermissions.Minor = &minor
73
+		}
74
+		devPermissions = append(devPermissions, dPermissions)
75
+	}
76
+	return devPermissions, nil
77
+}
... ...
@@ -6,14 +6,12 @@ import (
6 6
 	"os"
7 7
 	"os/exec"
8 8
 	"path/filepath"
9
-	"regexp"
10 9
 	"sort"
11 10
 	"strconv"
12 11
 	"strings"
13 12
 
14 13
 	containertypes "github.com/docker/docker/api/types/container"
15 14
 	"github.com/docker/docker/container"
16
-	"github.com/docker/docker/daemon/caps"
17 15
 	daemonconfig "github.com/docker/docker/daemon/config"
18 16
 	"github.com/docker/docker/oci"
19 17
 	"github.com/docker/docker/pkg/idtools"
... ...
@@ -29,11 +27,6 @@ import (
29 29
 	"golang.org/x/sys/unix"
30 30
 )
31 31
 
32
-// nolint: gosimple
33
-var (
34
-	deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\*) ([rwm]{1,3})$")
35
-)
36
-
37 32
 func setResources(s *specs.Spec, r containertypes.Resources) error {
38 33
 	weightDevices, err := getBlkioWeightDevices(r)
39 34
 	if err != nil {
... ...
@@ -115,39 +108,10 @@ func setDevices(s *specs.Spec, c *container.Container) error {
115 115
 			devPermissions = append(devPermissions, dPermissions...)
116 116
 		}
117 117
 
118
-		for _, deviceCgroupRule := range c.HostConfig.DeviceCgroupRules {
119
-			ss := deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1)
120
-			if len(ss[0]) != 5 {
121
-				return fmt.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
122
-			}
123
-			matches := ss[0]
124
-
125
-			dPermissions := specs.LinuxDeviceCgroup{
126
-				Allow:  true,
127
-				Type:   matches[1],
128
-				Access: matches[4],
129
-			}
130
-			if matches[2] == "*" {
131
-				major := int64(-1)
132
-				dPermissions.Major = &major
133
-			} else {
134
-				major, err := strconv.ParseInt(matches[2], 10, 64)
135
-				if err != nil {
136
-					return fmt.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
137
-				}
138
-				dPermissions.Major = &major
139
-			}
140
-			if matches[3] == "*" {
141
-				minor := int64(-1)
142
-				dPermissions.Minor = &minor
143
-			} else {
144
-				minor, err := strconv.ParseInt(matches[3], 10, 64)
145
-				if err != nil {
146
-					return fmt.Errorf("invalid minor value in device cgroup rule format: '%s'", deviceCgroupRule)
147
-				}
148
-				dPermissions.Minor = &minor
149
-			}
150
-			devPermissions = append(devPermissions, dPermissions)
118
+		var err error
119
+		devPermissions, err = appendDevicePermissionsFromCgroupRules(devPermissions, c.HostConfig.DeviceCgroupRules)
120
+		if err != nil {
121
+			return err
151 122
 		}
152 123
 	}
153 124
 
... ...
@@ -249,30 +213,6 @@ func setNamespace(s *specs.Spec, ns specs.LinuxNamespace) {
249 249
 	s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
250 250
 }
251 251
 
252
-func setCapabilities(s *specs.Spec, c *container.Container) error {
253
-	var caplist []string
254
-	var err error
255
-	if c.HostConfig.Privileged {
256
-		caplist = caps.GetAllCapabilities()
257
-	} else {
258
-		caplist, err = caps.TweakCapabilities(s.Process.Capabilities.Bounding, c.HostConfig.CapAdd, c.HostConfig.CapDrop)
259
-		if err != nil {
260
-			return err
261
-		}
262
-	}
263
-	s.Process.Capabilities.Effective = caplist
264
-	s.Process.Capabilities.Bounding = caplist
265
-	s.Process.Capabilities.Permitted = caplist
266
-	s.Process.Capabilities.Inheritable = caplist
267
-	// setUser has already been executed here
268
-	// if non root drop capabilities in the way execve does
269
-	if s.Process.User.UID != 0 {
270
-		s.Process.Capabilities.Effective = []string{}
271
-		s.Process.Capabilities.Permitted = []string{}
272
-	}
273
-	return nil
274
-}
275
-
276 252
 func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
277 253
 	userNS := false
278 254
 	// user
... ...
@@ -211,7 +211,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
211 211
 		if !system.LCOWSupported() {
212 212
 			return nil, fmt.Errorf("Linux containers on Windows are not supported")
213 213
 		}
214
-		daemon.createSpecLinuxFields(c, &s)
214
+		if err := daemon.createSpecLinuxFields(c, &s); err != nil {
215
+			return nil, err
216
+		}
215 217
 	default:
216 218
 		return nil, fmt.Errorf("Unsupported platform %q", img.OS)
217 219
 	}
... ...
@@ -336,12 +338,21 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
336 336
 // Sets the Linux-specific fields of the OCI spec
337 337
 // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
338 338
 // be pulled in from oci_linux.go.
339
-func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) {
339
+func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) error {
340 340
 	if len(s.Process.Cwd) == 0 {
341 341
 		s.Process.Cwd = `/`
342 342
 	}
343 343
 	s.Root.Path = "rootfs"
344 344
 	s.Root.Readonly = c.HostConfig.ReadonlyRootfs
345
+	if err := setCapabilities(s, c); err != nil {
346
+		return fmt.Errorf("linux spec capabilities: %v", err)
347
+	}
348
+	devPermissions, err := appendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)
349
+	if err != nil {
350
+		return fmt.Errorf("linux runtime spec devices: %v", err)
351
+	}
352
+	s.Linux.Resources.Devices = devPermissions
353
+	return nil
345 354
 }
346 355
 
347 356
 func escapeArgs(args []string) []string {