Merged by openshift-bot
OpenShift Bot authored on 2016/01/16 16:52:44... | ... |
@@ -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() |