Browse code

Merge pull request #6470 from pweil-/scc-caps

Merged by openshift-bot

OpenShift Bot authored on 2016/01/16 16:52:44
Showing 17 changed files
... ...
@@ -2113,6 +2113,22 @@ func deepCopy_api_SecurityContextConstraints(in SecurityContextConstraints, out
2113 2113
 	if err := deepCopy_api_SupplementalGroupsStrategyOptions(in.SupplementalGroups, &out.SupplementalGroups, c); err != nil {
2114 2114
 		return err
2115 2115
 	}
2116
+	if in.DefaultAddCapabilities != nil {
2117
+		out.DefaultAddCapabilities = make([]Capability, len(in.DefaultAddCapabilities))
2118
+		for i := range in.DefaultAddCapabilities {
2119
+			out.DefaultAddCapabilities[i] = in.DefaultAddCapabilities[i]
2120
+		}
2121
+	} else {
2122
+		out.DefaultAddCapabilities = nil
2123
+	}
2124
+	if in.RequiredDropCapabilities != nil {
2125
+		out.RequiredDropCapabilities = make([]Capability, len(in.RequiredDropCapabilities))
2126
+		for i := range in.RequiredDropCapabilities {
2127
+			out.RequiredDropCapabilities[i] = in.RequiredDropCapabilities[i]
2128
+		}
2129
+	} else {
2130
+		out.RequiredDropCapabilities = nil
2131
+	}
2116 2132
 	if in.Users != nil {
2117 2133
 		out.Users = make([]string, len(in.Users))
2118 2134
 		for i := range in.Users {
... ...
@@ -2143,7 +2143,16 @@ type SecurityContextConstraints struct {
2143 2143
 
2144 2144
 	// AllowPrivilegedContainer determines if a container can request to be run as privileged.
2145 2145
 	AllowPrivilegedContainer bool
2146
+	// DefaultAddCapabilities is the default set of capabilities that will be added to the container
2147
+	// unless the pod spec specifically drops the capability.  You may not list a capabiility in both
2148
+	// DefaultAddCapabilities and RequiredDropCapabilities.
2149
+	DefaultAddCapabilities []Capability
2150
+	// RequiredDropCapabilities are the capabilities that will be dropped from the container.  These
2151
+	// are required to be dropped and cannot be added.
2152
+	RequiredDropCapabilities []Capability
2146 2153
 	// AllowedCapabilities is a list of capabilities that can be requested to add to the container.
2154
+	// Capabilities in this field maybe added at the pod author's discretion.
2155
+	// You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities.
2147 2156
 	AllowedCapabilities []Capability
2148 2157
 	// AllowHostDirVolumePlugin determines if the policy allow containers to use the HostDir volume plugin
2149 2158
 	AllowHostDirVolumePlugin bool
... ...
@@ -2760,6 +2760,22 @@ func convert_api_SecurityContextConstraints_To_v1_SecurityContextConstraints(in
2760 2760
 	if err := convert_api_SupplementalGroupsStrategyOptions_To_v1_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil {
2761 2761
 		return err
2762 2762
 	}
2763
+	if in.DefaultAddCapabilities != nil {
2764
+		out.DefaultAddCapabilities = make([]Capability, len(in.DefaultAddCapabilities))
2765
+		for i := range in.DefaultAddCapabilities {
2766
+			out.DefaultAddCapabilities[i] = Capability(in.DefaultAddCapabilities[i])
2767
+		}
2768
+	} else {
2769
+		out.DefaultAddCapabilities = nil
2770
+	}
2771
+	if in.RequiredDropCapabilities != nil {
2772
+		out.RequiredDropCapabilities = make([]Capability, len(in.RequiredDropCapabilities))
2773
+		for i := range in.RequiredDropCapabilities {
2774
+			out.RequiredDropCapabilities[i] = Capability(in.RequiredDropCapabilities[i])
2775
+		}
2776
+	} else {
2777
+		out.RequiredDropCapabilities = nil
2778
+	}
2763 2779
 	if in.Users != nil {
2764 2780
 		out.Users = make([]string, len(in.Users))
2765 2781
 		for i := range in.Users {
... ...
@@ -5768,6 +5784,22 @@ func convert_v1_SecurityContextConstraints_To_api_SecurityContextConstraints(in
5768 5768
 	if err := convert_v1_SupplementalGroupsStrategyOptions_To_api_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil {
5769 5769
 		return err
5770 5770
 	}
5771
+	if in.DefaultAddCapabilities != nil {
5772
+		out.DefaultAddCapabilities = make([]api.Capability, len(in.DefaultAddCapabilities))
5773
+		for i := range in.DefaultAddCapabilities {
5774
+			out.DefaultAddCapabilities[i] = api.Capability(in.DefaultAddCapabilities[i])
5775
+		}
5776
+	} else {
5777
+		out.DefaultAddCapabilities = nil
5778
+	}
5779
+	if in.RequiredDropCapabilities != nil {
5780
+		out.RequiredDropCapabilities = make([]api.Capability, len(in.RequiredDropCapabilities))
5781
+		for i := range in.RequiredDropCapabilities {
5782
+			out.RequiredDropCapabilities[i] = api.Capability(in.RequiredDropCapabilities[i])
5783
+		}
5784
+	} else {
5785
+		out.RequiredDropCapabilities = nil
5786
+	}
5771 5787
 	if in.Users != nil {
5772 5788
 		out.Users = make([]string, len(in.Users))
5773 5789
 		for i := range in.Users {
... ...
@@ -2162,6 +2162,22 @@ func deepCopy_v1_SecurityContextConstraints(in SecurityContextConstraints, out *
2162 2162
 	if err := deepCopy_v1_SupplementalGroupsStrategyOptions(in.SupplementalGroups, &out.SupplementalGroups, c); err != nil {
2163 2163
 		return err
2164 2164
 	}
2165
+	if in.DefaultAddCapabilities != nil {
2166
+		out.DefaultAddCapabilities = make([]Capability, len(in.DefaultAddCapabilities))
2167
+		for i := range in.DefaultAddCapabilities {
2168
+			out.DefaultAddCapabilities[i] = in.DefaultAddCapabilities[i]
2169
+		}
2170
+	} else {
2171
+		out.DefaultAddCapabilities = nil
2172
+	}
2173
+	if in.RequiredDropCapabilities != nil {
2174
+		out.RequiredDropCapabilities = make([]Capability, len(in.RequiredDropCapabilities))
2175
+		for i := range in.RequiredDropCapabilities {
2176
+			out.RequiredDropCapabilities[i] = in.RequiredDropCapabilities[i]
2177
+		}
2178
+	} else {
2179
+		out.RequiredDropCapabilities = nil
2180
+	}
2165 2181
 	if in.Users != nil {
2166 2182
 		out.Users = make([]string, len(in.Users))
2167 2183
 		for i := range in.Users {
... ...
@@ -2597,7 +2597,16 @@ type SecurityContextConstraints struct {
2597 2597
 
2598 2598
 	// AllowPrivilegedContainer determines if a container can request to be run as privileged.
2599 2599
 	AllowPrivilegedContainer bool `json:"allowPrivilegedContainer" description:"allow containers to run as privileged"`
2600
+	// DefaultAddCapabilities is the default set of capabilities that will be added to the container
2601
+	// unless the pod spec specifically drops the capability.  You may not list a capabiility in both
2602
+	// DefaultAddCapabilities and RequiredDropCapabilities.
2603
+	DefaultAddCapabilities []Capability `json:"defaultAddCapabilities" description:"capabilities that are added by default but may be dropped"`
2604
+	// RequiredDropCapabilities are the capabilities that will be dropped from the container.  These
2605
+	// are required to be dropped and cannot be added.
2606
+	RequiredDropCapabilities []Capability `json:"requiredDropCapabilities" description:"capabilities that will be dropped by default and may not be added"`
2600 2607
 	// AllowedCapabilities is a list of capabilities that can be requested to add to the container.
2608
+	// Capabilities in this field maybe added at the pod author's discretion.
2609
+	// You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities.
2601 2610
 	AllowedCapabilities []Capability `json:"allowedCapabilities" description:"capabilities that are allowed to be added"`
2602 2611
 	// AllowHostDirVolumePlugin determines if the policy allow containers to use the HostDir volume plugin
2603 2612
 	AllowHostDirVolumePlugin bool `json:"allowHostDirVolumePlugin" description:"allow the use of the host dir volume plugin"`
... ...
@@ -2144,6 +2144,22 @@ func convert_api_SecurityContextConstraints_To_v1beta3_SecurityContextConstraint
2144 2144
 	if err := convert_api_SupplementalGroupsStrategyOptions_To_v1beta3_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil {
2145 2145
 		return err
2146 2146
 	}
2147
+	if in.DefaultAddCapabilities != nil {
2148
+		out.DefaultAddCapabilities = make([]Capability, len(in.DefaultAddCapabilities))
2149
+		for i := range in.DefaultAddCapabilities {
2150
+			out.DefaultAddCapabilities[i] = Capability(in.DefaultAddCapabilities[i])
2151
+		}
2152
+	} else {
2153
+		out.DefaultAddCapabilities = nil
2154
+	}
2155
+	if in.RequiredDropCapabilities != nil {
2156
+		out.RequiredDropCapabilities = make([]Capability, len(in.RequiredDropCapabilities))
2157
+		for i := range in.RequiredDropCapabilities {
2158
+			out.RequiredDropCapabilities[i] = Capability(in.RequiredDropCapabilities[i])
2159
+		}
2160
+	} else {
2161
+		out.RequiredDropCapabilities = nil
2162
+	}
2147 2163
 	if in.Users != nil {
2148 2164
 		out.Users = make([]string, len(in.Users))
2149 2165
 		for i := range in.Users {
... ...
@@ -4478,6 +4494,22 @@ func convert_v1beta3_SecurityContextConstraints_To_api_SecurityContextConstraint
4478 4478
 	if err := convert_v1beta3_SupplementalGroupsStrategyOptions_To_api_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil {
4479 4479
 		return err
4480 4480
 	}
4481
+	if in.DefaultAddCapabilities != nil {
4482
+		out.DefaultAddCapabilities = make([]api.Capability, len(in.DefaultAddCapabilities))
4483
+		for i := range in.DefaultAddCapabilities {
4484
+			out.DefaultAddCapabilities[i] = api.Capability(in.DefaultAddCapabilities[i])
4485
+		}
4486
+	} else {
4487
+		out.DefaultAddCapabilities = nil
4488
+	}
4489
+	if in.RequiredDropCapabilities != nil {
4490
+		out.RequiredDropCapabilities = make([]api.Capability, len(in.RequiredDropCapabilities))
4491
+		for i := range in.RequiredDropCapabilities {
4492
+			out.RequiredDropCapabilities[i] = api.Capability(in.RequiredDropCapabilities[i])
4493
+		}
4494
+	} else {
4495
+		out.RequiredDropCapabilities = nil
4496
+	}
4481 4497
 	if in.Users != nil {
4482 4498
 		out.Users = make([]string, len(in.Users))
4483 4499
 		for i := range in.Users {
... ...
@@ -2143,6 +2143,22 @@ func deepCopy_v1beta3_SecurityContextConstraints(in SecurityContextConstraints,
2143 2143
 	if err := deepCopy_v1beta3_SupplementalGroupsStrategyOptions(in.SupplementalGroups, &out.SupplementalGroups, c); err != nil {
2144 2144
 		return err
2145 2145
 	}
2146
+	if in.DefaultAddCapabilities != nil {
2147
+		out.DefaultAddCapabilities = make([]Capability, len(in.DefaultAddCapabilities))
2148
+		for i := range in.DefaultAddCapabilities {
2149
+			out.DefaultAddCapabilities[i] = in.DefaultAddCapabilities[i]
2150
+		}
2151
+	} else {
2152
+		out.DefaultAddCapabilities = nil
2153
+	}
2154
+	if in.RequiredDropCapabilities != nil {
2155
+		out.RequiredDropCapabilities = make([]Capability, len(in.RequiredDropCapabilities))
2156
+		for i := range in.RequiredDropCapabilities {
2157
+			out.RequiredDropCapabilities[i] = in.RequiredDropCapabilities[i]
2158
+		}
2159
+	} else {
2160
+		out.RequiredDropCapabilities = nil
2161
+	}
2146 2162
 	if in.Users != nil {
2147 2163
 		out.Users = make([]string, len(in.Users))
2148 2164
 		for i := range in.Users {
... ...
@@ -2092,7 +2092,16 @@ type SecurityContextConstraints struct {
2092 2092
 
2093 2093
 	// AllowPrivilegedContainer determines if a container can request to be run as privileged.
2094 2094
 	AllowPrivilegedContainer bool `json:"allowPrivilegedContainer" description:"allow containers to run as privileged"`
2095
+	// DefaultAddCapabilities is the default set of capabilities that will be added to the container
2096
+	// unless the pod spec specifically drops the capability.  You may not list a capabiility in both
2097
+	// DefaultAddCapabilities and RequiredDropCapabilities.
2098
+	DefaultAddCapabilities []Capability `json:"defaultAddCapabilities" description:"capabilities that are added by default but may be dropped"`
2099
+	// RequiredDropCapabilities are the capabilities that will be dropped from the container.  These
2100
+	// are required to be dropped and cannot be added.
2101
+	RequiredDropCapabilities []Capability `json:"requiredDropCapabilities" description:"capabilities that will be dropped by default and may not be added"`
2095 2102
 	// AllowedCapabilities is a list of capabilities that can be requested to add to the container.
2103
+	// Capabilities in this field maybe added at the pod author's discretion.
2104
+	// You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities.
2096 2105
 	AllowedCapabilities []Capability `json:"allowedCapabilities" description:"capabilities that are allowed to be added"`
2097 2106
 	// AllowHostDirVolumePlugin determines if the policy allow containers to use the HostDir volume plugin
2098 2107
 	AllowHostDirVolumePlugin bool `json:"allowHostDirVolumePlugin" description:"allow the use of the host dir volume plugin"`
... ...
@@ -2105,9 +2105,38 @@ func ValidateSecurityContextConstraints(scc *api.SecurityContextConstraints) err
2105 2105
 	}
2106 2106
 	allErrs = append(allErrs, validateIDRanges(scc.SupplementalGroups.Ranges).Prefix("supplementalGroups")...)
2107 2107
 
2108
+	// validate capabilities
2109
+	allErrs = append(allErrs, validateSCCCapsAgainstDrops(scc.RequiredDropCapabilities, scc.DefaultAddCapabilities, "defaultAddCapabilities")...)
2110
+	allErrs = append(allErrs, validateSCCCapsAgainstDrops(scc.RequiredDropCapabilities, scc.AllowedCapabilities, "allowedCapabilities")...)
2111
+
2108 2112
 	return allErrs
2109 2113
 }
2110 2114
 
2115
+// validateSCCCapsAgainstDrops ensures an allowed cap is not listed in the required drops.
2116
+func validateSCCCapsAgainstDrops(requiredDrops []api.Capability, capsToCheck []api.Capability, fieldName string) errs.ValidationErrorList {
2117
+	allErrs := errs.ValidationErrorList{}
2118
+	if requiredDrops == nil {
2119
+		return allErrs
2120
+	}
2121
+	for _, cap := range capsToCheck {
2122
+		if hasCap(cap, requiredDrops) {
2123
+			allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, cap,
2124
+				fmt.Sprintf("capability is listed in %s and requiredDropCapabilities", fieldName)))
2125
+		}
2126
+	}
2127
+	return allErrs
2128
+}
2129
+
2130
+// hasCap checks for needle in haystack.
2131
+func hasCap(needle api.Capability, haystack []api.Capability) bool {
2132
+	for _, c := range haystack {
2133
+		if needle == c {
2134
+			return true
2135
+		}
2136
+	}
2137
+	return false
2138
+}
2139
+
2111 2140
 // validateIDRanges ensures the range is valid.
2112 2141
 func validateIDRanges(rng []api.IDRange) errs.ValidationErrorList {
2113 2142
 	allErrs := errs.ValidationErrorList{}
... ...
@@ -4171,6 +4171,14 @@ func TestValidateSecurityContextConstraints(t *testing.T) {
4171 4171
 	negativePriority := validSCC()
4172 4172
 	negativePriority.Priority = &invalidPriority
4173 4173
 
4174
+	requiredCapAddAndDrop := validSCC()
4175
+	requiredCapAddAndDrop.DefaultAddCapabilities = []api.Capability{"foo"}
4176
+	requiredCapAddAndDrop.RequiredDropCapabilities = []api.Capability{"foo"}
4177
+
4178
+	allowedCapListedInRequiredDrop := validSCC()
4179
+	allowedCapListedInRequiredDrop.RequiredDropCapabilities = []api.Capability{"foo"}
4180
+	allowedCapListedInRequiredDrop.AllowedCapabilities = []api.Capability{"foo"}
4181
+
4174 4182
 	errorCases := map[string]struct {
4175 4183
 		scc         *api.SecurityContextConstraints
4176 4184
 		errorType   fielderrors.ValidationErrorType
... ...
@@ -4246,6 +4254,16 @@ func TestValidateSecurityContextConstraints(t *testing.T) {
4246 4246
 			errorType:   errors.ValidationErrorTypeInvalid,
4247 4247
 			errorDetail: "priority cannot be negative",
4248 4248
 		},
4249
+		"invalid required caps": {
4250
+			scc:         requiredCapAddAndDrop,
4251
+			errorType:   errors.ValidationErrorTypeInvalid,
4252
+			errorDetail: "capability is listed in defaultAddCapabilities and requiredDropCapabilities",
4253
+		},
4254
+		"allowed cap listed in required drops": {
4255
+			scc:         allowedCapListedInRequiredDrop,
4256
+			errorType:   errors.ValidationErrorTypeInvalid,
4257
+			errorDetail: "capability is listed in allowedCapabilities and requiredDropCapabilities",
4258
+		},
4249 4259
 	}
4250 4260
 
4251 4261
 	for k, v := range errorCases {
... ...
@@ -4266,6 +4284,14 @@ func TestValidateSecurityContextConstraints(t *testing.T) {
4266 4266
 	runAsNonRoot := validSCC()
4267 4267
 	runAsNonRoot.RunAsUser.Type = api.RunAsUserStrategyMustRunAsNonRoot
4268 4268
 
4269
+	caseInsensitiveAddDrop := validSCC()
4270
+	caseInsensitiveAddDrop.DefaultAddCapabilities = []api.Capability{"foo"}
4271
+	caseInsensitiveAddDrop.RequiredDropCapabilities = []api.Capability{"FOO"}
4272
+
4273
+	caseInsensitiveAllowedDrop := validSCC()
4274
+	caseInsensitiveAllowedDrop.RequiredDropCapabilities = []api.Capability{"FOO"}
4275
+	caseInsensitiveAllowedDrop.AllowedCapabilities = []api.Capability{"foo"}
4276
+
4269 4277
 	successCases := map[string]struct {
4270 4278
 		scc *api.SecurityContextConstraints
4271 4279
 	}{
... ...
@@ -4278,6 +4304,12 @@ func TestValidateSecurityContextConstraints(t *testing.T) {
4278 4278
 		"run as non-root (user only)": {
4279 4279
 			scc: runAsNonRoot,
4280 4280
 		},
4281
+		"comparison for add -> drop is case sensitive": {
4282
+			scc: caseInsensitiveAddDrop,
4283
+		},
4284
+		"comparison for allowed -> drop is case sensitive": {
4285
+			scc: caseInsensitiveAllowedDrop,
4286
+		},
4281 4287
 	}
4282 4288
 
4283 4289
 	for k, v := range successCases {
4284 4290
new file mode 100644
... ...
@@ -0,0 +1,149 @@
0
+/*
1
+Copyright 2015 The Kubernetes Authors All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package capabilities
17
+
18
+import (
19
+	"fmt"
20
+
21
+	"k8s.io/kubernetes/pkg/api"
22
+	"k8s.io/kubernetes/pkg/util/fielderrors"
23
+	"k8s.io/kubernetes/pkg/util/sets"
24
+)
25
+
26
+// defaultCapabilities implements the CapabilitiesSecurityContextConstraintsStrategy interface
27
+type defaultCapabilities struct {
28
+	defaultAddCapabilities   []api.Capability
29
+	requiredDropCapabilities []api.Capability
30
+	allowedCaps              []api.Capability
31
+}
32
+
33
+var _ CapabilitiesSecurityContextConstraintsStrategy = &defaultCapabilities{}
34
+
35
+// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation
36
+// based on the configured initial caps and allowed caps.
37
+func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []api.Capability) (CapabilitiesSecurityContextConstraintsStrategy, error) {
38
+	return &defaultCapabilities{
39
+		defaultAddCapabilities:   defaultAddCapabilities,
40
+		requiredDropCapabilities: requiredDropCapabilities,
41
+		allowedCaps:              allowedCaps,
42
+	}, nil
43
+}
44
+
45
+// Generate creates the capabilities based on policy rules.  Generate will produce the following:
46
+// 1.  a capabilities.Add set containing all the required adds (unless the
47
+// 		container specifically is dropping the cap) and container requested adds
48
+// 2.  a capabilities.Drop set containing all the required drops and container requested drops
49
+func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) {
50
+	defaultAdd := makeCapSet(s.defaultAddCapabilities)
51
+	requiredDrop := makeCapSet(s.requiredDropCapabilities)
52
+	containerAdd := sets.NewString()
53
+	containerDrop := sets.NewString()
54
+
55
+	if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil {
56
+		containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add)
57
+		containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop)
58
+	}
59
+
60
+	// remove any default adds that the container is specifically dropping
61
+	defaultAdd = defaultAdd.Difference(containerDrop)
62
+
63
+	combinedAdd := defaultAdd.Union(containerAdd).List()
64
+	combinedDrop := requiredDrop.Union(containerDrop).List()
65
+
66
+	// nothing generated?  return nil
67
+	if len(combinedAdd) == 0 && len(combinedDrop) == 0 {
68
+		return nil, nil
69
+	}
70
+
71
+	return &api.Capabilities{
72
+		Add:  capabilityFromStringSlice(combinedAdd),
73
+		Drop: capabilityFromStringSlice(combinedDrop),
74
+	}, nil
75
+}
76
+
77
+// Validate ensures that the specified values fall within the range of the strategy.
78
+func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container) fielderrors.ValidationErrorList {
79
+	allErrs := fielderrors.ValidationErrorList{}
80
+
81
+	// if the security context isn't set then we haven't generated correctly.  Shouldn't get here
82
+	// if using the provider correctly
83
+	if container.SecurityContext == nil {
84
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid("securityContext", container.SecurityContext, "no security context is set"))
85
+		return allErrs
86
+	}
87
+
88
+	if container.SecurityContext.Capabilities == nil {
89
+		// if container.SC.Caps is nil then nothing was defaulted by the strat or requested by the pod author
90
+		// if there are no required caps on the strategy and nothing is requested on the pod
91
+		// then we can safely return here without further validation.
92
+		if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 {
93
+			return allErrs
94
+		}
95
+
96
+		// container has no requested caps but we have required caps.  We should have something in
97
+		// at least the drops on the container.
98
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid("capabilities", container.SecurityContext.Capabilities,
99
+			"required capabilities are not set on the securityContext"))
100
+		return allErrs
101
+	}
102
+
103
+	// validate that anything being added is in the default or allowed sets
104
+	defaultAdd := makeCapSet(s.defaultAddCapabilities)
105
+	allowedAdd := makeCapSet(s.allowedCaps)
106
+
107
+	for _, cap := range container.SecurityContext.Capabilities.Add {
108
+		sCap := string(cap)
109
+		if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) {
110
+			allErrs = append(allErrs, fielderrors.NewFieldInvalid("capabilities.add", sCap, "capability may not be added"))
111
+		}
112
+	}
113
+
114
+	// validate that anything that is required to be dropped is in the drop set
115
+	containerDrops := makeCapSet(container.SecurityContext.Capabilities.Drop)
116
+
117
+	for _, requiredDrop := range s.requiredDropCapabilities {
118
+		sDrop := string(requiredDrop)
119
+		if !containerDrops.Has(sDrop) {
120
+			allErrs = append(allErrs, fielderrors.NewFieldInvalid("capabilities.drop", container.SecurityContext.Capabilities.Drop,
121
+				fmt.Sprintf("%s is required to be dropped but was not found", sDrop)))
122
+		}
123
+	}
124
+
125
+	return allErrs
126
+}
127
+
128
+// capabilityFromStringSlice creates a capability slice from a string slice.
129
+func capabilityFromStringSlice(slice []string) []api.Capability {
130
+	if len(slice) == 0 {
131
+		return nil
132
+	}
133
+	caps := []api.Capability{}
134
+	for _, c := range slice {
135
+		caps = append(caps, api.Capability(c))
136
+	}
137
+	return caps
138
+}
139
+
140
+// makeCapSet makes a string set from capabilities and normalizes them to be all lower case to help
141
+// with comparisons.
142
+func makeCapSet(caps []api.Capability) sets.String {
143
+	s := sets.NewString()
144
+	for _, c := range caps {
145
+		s.Insert(string(c))
146
+	}
147
+	return s
148
+}
0 149
new file mode 100644
... ...
@@ -0,0 +1,387 @@
0
+/*
1
+Copyright 2015 The Kubernetes Authors All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package capabilities
17
+
18
+import (
19
+	"k8s.io/kubernetes/pkg/api"
20
+	"reflect"
21
+	"testing"
22
+)
23
+
24
+func TestGenerateAdds(t *testing.T) {
25
+	tests := map[string]struct {
26
+		defaultAddCaps   []api.Capability
27
+		requiredDropCaps []api.Capability
28
+		containerCaps    *api.Capabilities
29
+		expectedCaps     *api.Capabilities
30
+	}{
31
+		"no required, no container requests": {
32
+			expectedCaps: nil,
33
+		},
34
+		"required, no container requests": {
35
+			defaultAddCaps: []api.Capability{"foo"},
36
+			expectedCaps: &api.Capabilities{
37
+				Add: []api.Capability{"foo"},
38
+			},
39
+		},
40
+		"required, container requests add required": {
41
+			defaultAddCaps: []api.Capability{"foo"},
42
+			containerCaps: &api.Capabilities{
43
+				Add: []api.Capability{"foo"},
44
+			},
45
+			expectedCaps: &api.Capabilities{
46
+				Add: []api.Capability{"foo"},
47
+			},
48
+		},
49
+		"multiple required, container requests add required": {
50
+			defaultAddCaps: []api.Capability{"foo", "bar", "baz"},
51
+			containerCaps: &api.Capabilities{
52
+				Add: []api.Capability{"foo"},
53
+			},
54
+			expectedCaps: &api.Capabilities{
55
+				Add: []api.Capability{"bar", "baz", "foo"},
56
+			},
57
+		},
58
+		"required, container requests add non-required": {
59
+			defaultAddCaps: []api.Capability{"foo"},
60
+			containerCaps: &api.Capabilities{
61
+				Add: []api.Capability{"bar"},
62
+			},
63
+			expectedCaps: &api.Capabilities{
64
+				Add: []api.Capability{"bar", "foo"},
65
+			},
66
+		},
67
+		"generation dedupes": {
68
+			defaultAddCaps: []api.Capability{"foo", "foo", "foo", "foo"},
69
+			containerCaps: &api.Capabilities{
70
+				Add: []api.Capability{"foo", "foo", "foo"},
71
+			},
72
+			expectedCaps: &api.Capabilities{
73
+				Add: []api.Capability{"foo"},
74
+			},
75
+		},
76
+		"generation is case sensitive - will not dedupe": {
77
+			defaultAddCaps: []api.Capability{"foo"},
78
+			containerCaps: &api.Capabilities{
79
+				Add: []api.Capability{"FOO"},
80
+			},
81
+			expectedCaps: &api.Capabilities{
82
+				Add: []api.Capability{"FOO", "foo"},
83
+			},
84
+		},
85
+	}
86
+
87
+	for k, v := range tests {
88
+		container := &api.Container{
89
+			SecurityContext: &api.SecurityContext{
90
+				Capabilities: v.containerCaps,
91
+			},
92
+		}
93
+
94
+		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil)
95
+		if err != nil {
96
+			t.Errorf("%s failed: %v", k, err)
97
+			continue
98
+		}
99
+		generatedCaps, err := strategy.Generate(nil, container)
100
+		if err != nil {
101
+			t.Errorf("%s failed generating: %v", k, err)
102
+			continue
103
+		}
104
+		if v.expectedCaps == nil && generatedCaps != nil {
105
+			t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps)
106
+			continue
107
+		}
108
+		if !reflect.DeepEqual(v.expectedCaps, generatedCaps) {
109
+			t.Errorf("%s did not generate correctly.  Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps)
110
+		}
111
+	}
112
+}
113
+
114
+func TestGenerateDrops(t *testing.T) {
115
+	tests := map[string]struct {
116
+		defaultAddCaps   []api.Capability
117
+		requiredDropCaps []api.Capability
118
+		containerCaps    *api.Capabilities
119
+		expectedCaps     *api.Capabilities
120
+	}{
121
+		"no required, no container requests": {
122
+			expectedCaps: nil,
123
+		},
124
+		"required drops are defaulted": {
125
+			requiredDropCaps: []api.Capability{"foo"},
126
+			expectedCaps: &api.Capabilities{
127
+				Drop: []api.Capability{"foo"},
128
+			},
129
+		},
130
+		"required drops are defaulted when making container requests": {
131
+			requiredDropCaps: []api.Capability{"foo"},
132
+			containerCaps: &api.Capabilities{
133
+				Drop: []api.Capability{"foo", "bar"},
134
+			},
135
+			expectedCaps: &api.Capabilities{
136
+				Drop: []api.Capability{"bar", "foo"},
137
+			},
138
+		},
139
+		"can drop a required add": {
140
+			defaultAddCaps: []api.Capability{"foo"},
141
+			containerCaps: &api.Capabilities{
142
+				Drop: []api.Capability{"foo"},
143
+			},
144
+			expectedCaps: &api.Capabilities{
145
+				Drop: []api.Capability{"foo"},
146
+			},
147
+		},
148
+		"can drop non-required add": {
149
+			defaultAddCaps: []api.Capability{"foo"},
150
+			containerCaps: &api.Capabilities{
151
+				Drop: []api.Capability{"bar"},
152
+			},
153
+			expectedCaps: &api.Capabilities{
154
+				Add:  []api.Capability{"foo"},
155
+				Drop: []api.Capability{"bar"},
156
+			},
157
+		},
158
+		"defaulting adds and drops, dropping a required add": {
159
+			defaultAddCaps:   []api.Capability{"foo", "bar", "baz"},
160
+			requiredDropCaps: []api.Capability{"abc"},
161
+			containerCaps: &api.Capabilities{
162
+				Drop: []api.Capability{"foo"},
163
+			},
164
+			expectedCaps: &api.Capabilities{
165
+				Add:  []api.Capability{"bar", "baz"},
166
+				Drop: []api.Capability{"abc", "foo"},
167
+			},
168
+		},
169
+		"generation dedupes": {
170
+			requiredDropCaps: []api.Capability{"bar", "bar", "bar", "bar"},
171
+			containerCaps: &api.Capabilities{
172
+				Drop: []api.Capability{"bar", "bar", "bar"},
173
+			},
174
+			expectedCaps: &api.Capabilities{
175
+				Drop: []api.Capability{"bar"},
176
+			},
177
+		},
178
+		"generation is case sensitive - will not dedupe": {
179
+			requiredDropCaps: []api.Capability{"bar"},
180
+			containerCaps: &api.Capabilities{
181
+				Drop: []api.Capability{"BAR"},
182
+			},
183
+			expectedCaps: &api.Capabilities{
184
+				Drop: []api.Capability{"BAR", "bar"},
185
+			},
186
+		},
187
+	}
188
+	for k, v := range tests {
189
+		container := &api.Container{
190
+			SecurityContext: &api.SecurityContext{
191
+				Capabilities: v.containerCaps,
192
+			},
193
+		}
194
+
195
+		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil)
196
+		if err != nil {
197
+			t.Errorf("%s failed: %v", k, err)
198
+			continue
199
+		}
200
+		generatedCaps, err := strategy.Generate(nil, container)
201
+		if err != nil {
202
+			t.Errorf("%s failed generating: %v", k, err)
203
+			continue
204
+		}
205
+		if v.expectedCaps == nil && generatedCaps != nil {
206
+			t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps)
207
+			continue
208
+		}
209
+		if !reflect.DeepEqual(v.expectedCaps, generatedCaps) {
210
+			t.Errorf("%s did not generate correctly.  Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps)
211
+		}
212
+	}
213
+}
214
+
215
+func TestValidateAdds(t *testing.T) {
216
+	tests := map[string]struct {
217
+		defaultAddCaps   []api.Capability
218
+		requiredDropCaps []api.Capability
219
+		allowedCaps      []api.Capability
220
+		containerCaps    *api.Capabilities
221
+		shouldPass       bool
222
+	}{
223
+		// no container requests
224
+		"no required, no allowed, no container requests": {
225
+			shouldPass: true,
226
+		},
227
+		"no required, allowed, no container requests": {
228
+			allowedCaps: []api.Capability{"foo"},
229
+			shouldPass:  true,
230
+		},
231
+		"required, no allowed, no container requests": {
232
+			defaultAddCaps: []api.Capability{"foo"},
233
+			shouldPass:     false,
234
+		},
235
+
236
+		// container requests match required
237
+		"required, no allowed, container requests valid": {
238
+			defaultAddCaps: []api.Capability{"foo"},
239
+			containerCaps: &api.Capabilities{
240
+				Add: []api.Capability{"foo"},
241
+			},
242
+			shouldPass: true,
243
+		},
244
+		"required, no allowed, container requests invalid": {
245
+			defaultAddCaps: []api.Capability{"foo"},
246
+			containerCaps: &api.Capabilities{
247
+				Add: []api.Capability{"bar"},
248
+			},
249
+			shouldPass: false,
250
+		},
251
+
252
+		// container requests match allowed
253
+		"no required, allowed, container requests valid": {
254
+			allowedCaps: []api.Capability{"foo"},
255
+			containerCaps: &api.Capabilities{
256
+				Add: []api.Capability{"foo"},
257
+			},
258
+			shouldPass: true,
259
+		},
260
+		"no required, allowed, container requests invalid": {
261
+			allowedCaps: []api.Capability{"foo"},
262
+			containerCaps: &api.Capabilities{
263
+				Add: []api.Capability{"bar"},
264
+			},
265
+			shouldPass: false,
266
+		},
267
+
268
+		// required and allowed
269
+		"required, allowed, container requests valid required": {
270
+			defaultAddCaps: []api.Capability{"foo"},
271
+			allowedCaps:    []api.Capability{"bar"},
272
+			containerCaps: &api.Capabilities{
273
+				Add: []api.Capability{"foo"},
274
+			},
275
+			shouldPass: true,
276
+		},
277
+		"required, allowed, container requests valid allowed": {
278
+			defaultAddCaps: []api.Capability{"foo"},
279
+			allowedCaps:    []api.Capability{"bar"},
280
+			containerCaps: &api.Capabilities{
281
+				Add: []api.Capability{"bar"},
282
+			},
283
+			shouldPass: true,
284
+		},
285
+		"required, allowed, container requests invalid": {
286
+			defaultAddCaps: []api.Capability{"foo"},
287
+			allowedCaps:    []api.Capability{"bar"},
288
+			containerCaps: &api.Capabilities{
289
+				Add: []api.Capability{"baz"},
290
+			},
291
+			shouldPass: false,
292
+		},
293
+		"validation is case sensitive": {
294
+			defaultAddCaps: []api.Capability{"foo"},
295
+			containerCaps: &api.Capabilities{
296
+				Add: []api.Capability{"FOO"},
297
+			},
298
+			shouldPass: false,
299
+		},
300
+	}
301
+
302
+	for k, v := range tests {
303
+		container := &api.Container{
304
+			SecurityContext: &api.SecurityContext{
305
+				Capabilities: v.containerCaps,
306
+			},
307
+		}
308
+
309
+		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, v.allowedCaps)
310
+		if err != nil {
311
+			t.Errorf("%s failed: %v", k, err)
312
+			continue
313
+		}
314
+		errs := strategy.Validate(nil, container)
315
+		if v.shouldPass && len(errs) > 0 {
316
+			t.Errorf("%s should have passed but had errors %v", k, errs)
317
+			continue
318
+		}
319
+		if !v.shouldPass && len(errs) == 0 {
320
+			t.Errorf("%s should have failed but recieved no errors", k)
321
+		}
322
+	}
323
+}
324
+
325
+func TestValidateDrops(t *testing.T) {
326
+	tests := map[string]struct {
327
+		defaultAddCaps   []api.Capability
328
+		requiredDropCaps []api.Capability
329
+		containerCaps    *api.Capabilities
330
+		shouldPass       bool
331
+	}{
332
+		// no container requests
333
+		"no required, no container requests": {
334
+			shouldPass: true,
335
+		},
336
+		"required, no container requests": {
337
+			requiredDropCaps: []api.Capability{"foo"},
338
+			shouldPass:       false,
339
+		},
340
+
341
+		// container requests match required
342
+		"required, container requests valid": {
343
+			requiredDropCaps: []api.Capability{"foo"},
344
+			containerCaps: &api.Capabilities{
345
+				Drop: []api.Capability{"foo"},
346
+			},
347
+			shouldPass: true,
348
+		},
349
+		"required, container requests invalid": {
350
+			requiredDropCaps: []api.Capability{"foo"},
351
+			containerCaps: &api.Capabilities{
352
+				Drop: []api.Capability{"bar"},
353
+			},
354
+			shouldPass: false,
355
+		},
356
+		"validation is case sensitive": {
357
+			requiredDropCaps: []api.Capability{"foo"},
358
+			containerCaps: &api.Capabilities{
359
+				Drop: []api.Capability{"FOO"},
360
+			},
361
+			shouldPass: false,
362
+		},
363
+	}
364
+
365
+	for k, v := range tests {
366
+		container := &api.Container{
367
+			SecurityContext: &api.SecurityContext{
368
+				Capabilities: v.containerCaps,
369
+			},
370
+		}
371
+
372
+		strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil)
373
+		if err != nil {
374
+			t.Errorf("%s failed: %v", k, err)
375
+			continue
376
+		}
377
+		errs := strategy.Validate(nil, container)
378
+		if v.shouldPass && len(errs) > 0 {
379
+			t.Errorf("%s should have passed but had errors %v", k, errs)
380
+			continue
381
+		}
382
+		if !v.shouldPass && len(errs) == 0 {
383
+			t.Errorf("%s should have failed but recieved no errors", k)
384
+		}
385
+	}
386
+}
0 387
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+/*
1
+Copyright 2015 The Kubernetes Authors All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package capabilities
17
+
18
+import (
19
+	"k8s.io/kubernetes/pkg/api"
20
+	"k8s.io/kubernetes/pkg/util/fielderrors"
21
+)
22
+
23
+// CapabilitiesSecurityContextConstraintsStrategy defines the interface for all cap constraint strategies.
24
+type CapabilitiesSecurityContextConstraintsStrategy interface {
25
+	// Generate creates the capabilities based on policy rules.
26
+	Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error)
27
+	// Validate ensures that the specified values fall within the range of the strategy.
28
+	Validate(pod *api.Pod, container *api.Container) fielderrors.ValidationErrorList
29
+}
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"fmt"
21 21
 
22 22
 	"k8s.io/kubernetes/pkg/api"
23
+	"k8s.io/kubernetes/pkg/securitycontextconstraints/capabilities"
23 24
 	"k8s.io/kubernetes/pkg/securitycontextconstraints/group"
24 25
 	"k8s.io/kubernetes/pkg/securitycontextconstraints/selinux"
25 26
 	"k8s.io/kubernetes/pkg/securitycontextconstraints/user"
... ...
@@ -29,7 +30,7 @@ import (
29 29
 // used to pass in the field being validated for reusable group strategies so they
30 30
 // can create informative error messages.
31 31
 const (
32
-	fsGroupField = "fsGroup"
32
+	fsGroupField            = "fsGroup"
33 33
 	supplementalGroupsField = "supplementalGroups"
34 34
 )
35 35
 
... ...
@@ -40,6 +41,7 @@ type simpleProvider struct {
40 40
 	seLinuxStrategy           selinux.SELinuxSecurityContextConstraintsStrategy
41 41
 	fsGroupStrategy           group.GroupSecurityContextConstraintsStrategy
42 42
 	supplementalGroupStrategy group.GroupSecurityContextConstraintsStrategy
43
+	capabilitiesStrategy      capabilities.CapabilitiesSecurityContextConstraintsStrategy
43 44
 }
44 45
 
45 46
 // ensure we implement the interface correctly.
... ...
@@ -71,12 +73,18 @@ func NewSimpleProvider(scc *api.SecurityContextConstraints) (SecurityContextCons
71 71
 		return nil, err
72 72
 	}
73 73
 
74
+	capStrat, err := createCapabilitiesStrategy(scc.DefaultAddCapabilities, scc.RequiredDropCapabilities, scc.AllowedCapabilities)
75
+	if err != nil {
76
+		return nil, err
77
+	}
78
+
74 79
 	return &simpleProvider{
75 80
 		scc:                       scc,
76 81
 		runAsUserStrategy:         userStrat,
77 82
 		seLinuxStrategy:           seLinuxStrat,
78 83
 		fsGroupStrategy:           fsGroupStrat,
79 84
 		supplementalGroupStrategy: supGroupStrat,
85
+		capabilitiesStrategy:      capStrat,
80 86
 	}, nil
81 87
 }
82 88
 
... ...
@@ -167,7 +175,12 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
167 167
 		sc.RunAsNonRoot = &b
168 168
 	}
169 169
 
170
-	// No need to touch capabilities, they will validate or not.
170
+	caps, err := s.capabilitiesStrategy.Generate(pod, container)
171
+	if err != nil {
172
+		return nil, err
173
+	}
174
+	sc.Capabilities = caps
175
+
171 176
 	return sc, nil
172 177
 }
173 178
 
... ...
@@ -228,20 +241,7 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
228 228
 		allErrs = append(allErrs, fielderrors.NewFieldInvalid("privileged", *sc.Privileged, "Privileged containers are not allowed"))
229 229
 	}
230 230
 
231
-	if sc.Capabilities != nil && len(sc.Capabilities.Add) > 0 {
232
-		for _, cap := range sc.Capabilities.Add {
233
-			found := false
234
-			for _, allowedCap := range s.scc.AllowedCapabilities {
235
-				if cap == allowedCap {
236
-					found = true
237
-					break
238
-				}
239
-			}
240
-			if !found {
241
-				allErrs = append(allErrs, fielderrors.NewFieldInvalid("capabilities.add", cap, "Capability is not allowed to be added"))
242
-			}
243
-		}
244
-	}
231
+	allErrs = append(allErrs, s.capabilitiesStrategy.Validate(pod, container)...)
245 232
 
246 233
 	if !s.scc.AllowHostDirVolumePlugin {
247 234
 		for _, v := range pod.Spec.Volumes {
... ...
@@ -327,3 +327,8 @@ func createSupplementalGroupStrategy(opts *api.SupplementalGroupsStrategyOptions
327 327
 		return nil, fmt.Errorf("Unrecognized SupplementalGroups strategy type %s", opts.Type)
328 328
 	}
329 329
 }
330
+
331
+// createCapabilitiesStrategy creates a new capabilities strategy.
332
+func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []api.Capability) (capabilities.CapabilitiesSecurityContextConstraintsStrategy, error) {
333
+	return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
334
+}
... ...
@@ -41,11 +41,13 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
41 41
 			ObjectMeta: api.ObjectMeta{
42 42
 				Name: "scc-sa",
43 43
 			},
44
+			DefaultAddCapabilities:   []api.Capability{"foo"},
45
+			RequiredDropCapabilities: []api.Capability{"bar"},
44 46
 			RunAsUser: api.RunAsUserStrategyOptions{
45 47
 				Type: api.RunAsUserStrategyRunAsAny,
46 48
 			},
47 49
 			SELinuxContext: api.SELinuxContextStrategyOptions{
48
-				Type:           api.SELinuxStrategyRunAsAny,
50
+				Type: api.SELinuxStrategyRunAsAny,
49 51
 			},
50 52
 			// these are pod mutating strategies that are tested above
51 53
 			FSGroup: api.FSGroupStrategyOptions{
... ...
@@ -109,6 +111,8 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
109 109
 			ObjectMeta: api.ObjectMeta{
110 110
 				Name: "scc-sa",
111 111
 			},
112
+			DefaultAddCapabilities:   []api.Capability{"foo"},
113
+			RequiredDropCapabilities: []api.Capability{"bar"},
112 114
 			RunAsUser: api.RunAsUserStrategyOptions{
113 115
 				Type: api.RunAsUserStrategyMustRunAs,
114 116
 				UID:  &uid,
... ...
@@ -167,7 +171,7 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
167 167
 	failSupplementalGroupPod := defaultPod()
168 168
 	failSupplementalGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{999}
169 169
 	failSupplementalGroupSCC := defaultSCC()
170
-	failSupplementalGroupSCC.SupplementalGroups = api.SupplementalGroupsStrategyOptions {
170
+	failSupplementalGroupSCC.SupplementalGroups = api.SupplementalGroupsStrategyOptions{
171 171
 		Type: api.SupplementalGroupsStrategyMustRunAs,
172 172
 		Ranges: []api.IDRange{
173 173
 			{Min: 1, Max: 1},
... ...
@@ -178,7 +182,7 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
178 178
 	fsGroup := int64(999)
179 179
 	failFSGroupPod.Spec.SecurityContext.FSGroup = &fsGroup
180 180
 	failFSGroupSCC := defaultSCC()
181
-	failFSGroupSCC.FSGroup = api.FSGroupStrategyOptions {
181
+	failFSGroupSCC.FSGroup = api.FSGroupStrategyOptions{
182 182
 		Type: api.FSGroupStrategyMustRunAs,
183 183
 		Ranges: []api.IDRange{
184 184
 			{Min: 1, Max: 1},
... ...
@@ -218,33 +222,33 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
218 218
 			expectedError: "Host IPC is not allowed to be used",
219 219
 		},
220 220
 		"failSupplementalGroupOutOfRange": {
221
-			pod: failSupplementalGroupPod,
222
-			scc: failSupplementalGroupSCC,
221
+			pod:           failSupplementalGroupPod,
222
+			scc:           failSupplementalGroupSCC,
223 223
 			expectedError: "999 is not an allowed group",
224 224
 		},
225 225
 		"failSupplementalGroupEmpty": {
226
-			pod: defaultPod(),
227
-			scc: failSupplementalGroupSCC,
226
+			pod:           defaultPod(),
227
+			scc:           failSupplementalGroupSCC,
228 228
 			expectedError: "unable to validate empty groups against required ranges",
229 229
 		},
230 230
 		"failFSGroupOutOfRange": {
231
-			pod: failFSGroupPod,
232
-			scc: failFSGroupSCC,
231
+			pod:           failFSGroupPod,
232
+			scc:           failFSGroupSCC,
233 233
 			expectedError: "999 is not an allowed group",
234 234
 		},
235 235
 		"failFSGroupEmpty": {
236
-			pod: defaultPod(),
237
-			scc: failFSGroupSCC,
236
+			pod:           defaultPod(),
237
+			scc:           failFSGroupSCC,
238 238
 			expectedError: "unable to validate empty groups against required ranges",
239 239
 		},
240 240
 		"failNilSELinux": {
241
-			pod: failNilSELinuxPod,
242
-			scc: failSELinuxSCC,
241
+			pod:           failNilSELinuxPod,
242
+			scc:           failSELinuxSCC,
243 243
 			expectedError: "unable to validate nil seLinuxOptions",
244 244
 		},
245 245
 		"failInvalidSELinux": {
246
-			pod: failInvalidSELinuxPod,
247
-			scc: failSELinuxSCC,
246
+			pod:           failInvalidSELinuxPod,
247
+			scc:           failSELinuxSCC,
248 248
 			expectedError: "does not match required level.  Found bar, wanted foo",
249 249
 		},
250 250
 	}
... ...
@@ -334,7 +338,7 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
334 334
 		"failCapsSCC": {
335 335
 			pod:           failCapsPod,
336 336
 			scc:           defaultSCC(),
337
-			expectedError: "Capability is not allowed to be added",
337
+			expectedError: "capability may not be added",
338 338
 		},
339 339
 		"failHostDirSCC": {
340 340
 			pod:           failHostDirPod,
... ...
@@ -403,17 +407,17 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) {
403 403
 
404 404
 	seLinuxPod := defaultPod()
405 405
 	seLinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{
406
-		User: "user",
407
-		Role: "role",
408
-		Type: "type",
406
+		User:  "user",
407
+		Role:  "role",
408
+		Type:  "type",
409 409
 		Level: "level",
410 410
 	}
411 411
 	seLinuxSCC := defaultSCC()
412 412
 	seLinuxSCC.SELinuxContext.Type = api.SELinuxStrategyMustRunAs
413 413
 	seLinuxSCC.SELinuxContext.SELinuxOptions = &api.SELinuxOptions{
414
-		User: "user",
415
-		Role: "role",
416
-		Type: "type",
414
+		User:  "user",
415
+		Role:  "role",
416
+		Type:  "type",
417 417
 		Level: "level",
418 418
 	}
419 419
 
... ...
@@ -478,7 +482,7 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
478 478
 			},
479 479
 		}
480 480
 	}
481
-	
481
+
482 482
 	// fail user strat
483 483
 	userSCC := defaultSCC()
484 484
 	var uid int64 = 999
... ...
@@ -515,6 +519,14 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
515 515
 		Add: []api.Capability{"foo"},
516 516
 	}
517 517
 
518
+	// pod should be able to request caps that are in the required set even if not specified in the allowed set
519
+	requiredCapsSCC := defaultSCC()
520
+	requiredCapsSCC.DefaultAddCapabilities = []api.Capability{"foo"}
521
+	requiredCapsPod := defaultPod()
522
+	requiredCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{
523
+		Add: []api.Capability{"foo"},
524
+	}
525
+
518 526
 	hostDirSCC := defaultSCC()
519 527
 	hostDirSCC.AllowHostDirVolumePlugin = true
520 528
 	hostDirPod := defaultPod()
... ...
@@ -548,10 +560,14 @@ func TestValidateContainerSecurityContextSuccess(t *testing.T) {
548 548
 			pod: privPod,
549 549
 			scc: privSCC,
550 550
 		},
551
-		"pass caps validating SCC": {
551
+		"pass allowed caps validating SCC": {
552 552
 			pod: capsPod,
553 553
 			scc: capsSCC,
554 554
 		},
555
+		"pass required caps validating SCC": {
556
+			pod: requiredCapsPod,
557
+			scc: requiredCapsSCC,
558
+		},
555 559
 		"pass hostDir validating SCC": {
556 560
 			pod: hostDirPod,
557 561
 			scc: hostDirSCC,
... ...
@@ -600,7 +616,7 @@ func defaultPod() *api.Pod {
600 600
 	return &api.Pod{
601 601
 		Spec: api.PodSpec{
602 602
 			SecurityContext: &api.PodSecurityContext{
603
-				// fill in for test cases
603
+			// fill in for test cases
604 604
 			},
605 605
 			Containers: []api.Container{
606 606
 				{
... ...
@@ -14743,6 +14743,8 @@
14743 14743
     "required": [
14744 14744
      "priority",
14745 14745
      "allowPrivilegedContainer",
14746
+     "defaultAddCapabilities",
14747
+     "requiredDropCapabilities",
14746 14748
      "allowedCapabilities",
14747 14749
      "allowHostDirVolumePlugin",
14748 14750
      "allowHostNetwork",
... ...
@@ -14771,6 +14773,20 @@
14771 14771
       "type": "boolean",
14772 14772
       "description": "allow containers to run as privileged"
14773 14773
      },
14774
+     "defaultAddCapabilities": {
14775
+      "type": "array",
14776
+      "items": {
14777
+       "$ref": "v1.Capability"
14778
+      },
14779
+      "description": "capabilities that are added by default but may be dropped"
14780
+     },
14781
+     "requiredDropCapabilities": {
14782
+      "type": "array",
14783
+      "items": {
14784
+       "$ref": "v1.Capability"
14785
+      },
14786
+      "description": "capabilities that will be dropped by default and may not be added"
14787
+     },
14774 14788
      "allowedCapabilities": {
14775 14789
       "type": "array",
14776 14790
       "items": {
... ...
@@ -27,6 +27,122 @@ func NewTestAdmission(store cache.Store, kclient client.Interface) kadmission.In
27 27
 	}
28 28
 }
29 29
 
30
+func TestAdmitCaps(t *testing.T) {
31
+	createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod {
32
+		pod := goodPod()
33
+		pod.Spec.Containers[0].SecurityContext.Capabilities = caps
34
+		return pod
35
+	}
36
+
37
+	restricted := restrictiveSCC()
38
+
39
+	allowsFooInAllowed := restrictiveSCC()
40
+	allowsFooInAllowed.Name = "allowCapInAllowed"
41
+	allowsFooInAllowed.AllowedCapabilities = []kapi.Capability{"foo"}
42
+
43
+	allowsFooInRequired := restrictiveSCC()
44
+	allowsFooInRequired.Name = "allowCapInRequired"
45
+	allowsFooInRequired.DefaultAddCapabilities = []kapi.Capability{"foo"}
46
+
47
+	requiresFooToBeDropped := restrictiveSCC()
48
+	requiresFooToBeDropped.Name = "requireDrop"
49
+	requiresFooToBeDropped.RequiredDropCapabilities = []kapi.Capability{"foo"}
50
+
51
+	tc := map[string]struct {
52
+		pod                  *kapi.Pod
53
+		sccs                 []*kapi.SecurityContextConstraints
54
+		shouldPass           bool
55
+		expectedCapabilities *kapi.Capabilities
56
+	}{
57
+		// UC 1: if an SCC does not define allowed or required caps then a pod requesting a cap
58
+		// should be rejected.
59
+		"should reject cap add when not allowed or required": {
60
+			pod:        createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}),
61
+			sccs:       []*kapi.SecurityContextConstraints{restricted},
62
+			shouldPass: false,
63
+		},
64
+		// UC 2: if an SCC allows a cap in the allowed field it should accept the pod request
65
+		// to add the cap.
66
+		"should accept cap add when in allowed": {
67
+			pod:        createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}),
68
+			sccs:       []*kapi.SecurityContextConstraints{restricted, allowsFooInAllowed},
69
+			shouldPass: true,
70
+		},
71
+		// UC 3: if an SCC requires a cap then it should accept the pod request
72
+		// to add the cap.
73
+		"should accept cap add when in required": {
74
+			pod:        createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}),
75
+			sccs:       []*kapi.SecurityContextConstraints{restricted, allowsFooInRequired},
76
+			shouldPass: true,
77
+		},
78
+		// UC 4: if an SCC requires a cap to be dropped then it should fail both
79
+		// in the verification of adds and verification of drops
80
+		"should reject cap add when requested cap is required to be dropped": {
81
+			pod:        createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}),
82
+			sccs:       []*kapi.SecurityContextConstraints{restricted, requiresFooToBeDropped},
83
+			shouldPass: false,
84
+		},
85
+		// UC 5: if an SCC requires a cap to be dropped it should accept
86
+		// a manual request to drop the cap.
87
+		"should accept cap drop when cap is required to be dropped": {
88
+			pod:        createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}),
89
+			sccs:       []*kapi.SecurityContextConstraints{restricted, requiresFooToBeDropped},
90
+			shouldPass: true,
91
+		},
92
+		// UC 6: required add is defaulted
93
+		"required add is defaulted": {
94
+			pod:        goodPod(),
95
+			sccs:       []*kapi.SecurityContextConstraints{allowsFooInRequired},
96
+			shouldPass: true,
97
+			expectedCapabilities: &kapi.Capabilities{
98
+				Add: []kapi.Capability{"foo"},
99
+			},
100
+		},
101
+		// UC 7: required drop is defaulted
102
+		"required drop is defaulted": {
103
+			pod:        goodPod(),
104
+			sccs:       []*kapi.SecurityContextConstraints{requiresFooToBeDropped},
105
+			shouldPass: true,
106
+			expectedCapabilities: &kapi.Capabilities{
107
+				Drop: []kapi.Capability{"foo"},
108
+			},
109
+		},
110
+	}
111
+
112
+	for k, v := range tc {
113
+		testSCCAdmit(k, v.sccs, v.pod, v.shouldPass, t)
114
+
115
+		if v.expectedCapabilities != nil {
116
+			if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) {
117
+				t.Errorf("%s resulted in caps that were not expected - expected: %v, received: %v", k, v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities)
118
+			}
119
+		}
120
+	}
121
+}
122
+
123
+func testSCCAdmit(testCaseName string, sccs []*kapi.SecurityContextConstraints, pod *kapi.Pod, shouldPass bool, t *testing.T) {
124
+	namespace := createNamespaceForTest()
125
+	serviceAccount := createSAForTest()
126
+	tc := testclient.NewSimpleFake(namespace, serviceAccount)
127
+	store := cache.NewStore(cache.MetaNamespaceKeyFunc)
128
+
129
+	for _, scc := range sccs {
130
+		store.Add(scc)
131
+	}
132
+
133
+	plugin := NewTestAdmission(store, tc)
134
+
135
+	attrs := kadmission.NewAttributesRecord(pod, "Pod", "namespace", "", string(kapi.ResourcePods), "", kadmission.Create, &user.DefaultInfo{})
136
+	err := plugin.Admit(attrs)
137
+
138
+	if shouldPass && err != nil {
139
+		t.Errorf("%s expected no errors but received %v", testCaseName, err)
140
+	}
141
+	if !shouldPass && err == nil {
142
+		t.Errorf("%s expected errors but received none", testCaseName)
143
+	}
144
+}
145
+
30 146
 func TestAdmit(t *testing.T) {
31 147
 	// create the annotated namespace and add it to the fake client
32 148
 	namespace := createNamespaceForTest()