| ... | ... |
@@ -32,8 +32,26 @@ At that time, the openshift docker registry image must be upgraded in order to c |
| 32 | 32 |
|
| 33 | 33 |
1. The `volume.metadata` field is deprecated as of Origin 1.0.6 in favor of `volume.downwardAPI`. |
| 34 | 34 |
|
| 35 |
-1. New fields (`allowHostPID` and `allowHostIPC`) have been added to the default SCCs in Origin 1.0.7. |
|
| 36 |
-You may set these fields manually or [reset your default SCCs](https://docs.openshift.org/latest/admin_guide/manage_scc.html#updating-the-default-security-context-constraints). |
|
| 35 |
+1. New fields (`fsGroup`, `supplementalGroups`, `allowHostPID` and `allowHostIPC`) have been added |
|
| 36 |
+to the default SCCs in Origin 1.0.7. These allow you to control groups for persistent volumes, |
|
| 37 |
+supplemental groups for the container, and usage of the host PID/IPC namespaces. The fields will |
|
| 38 |
+default as follows for existing SCCs: |
|
| 39 |
+ |
|
| 40 |
+ 1. allowHostPID - defaults to false. You may wish to change this to true on any privileged SCCs or |
|
| 41 |
+ [reset your default SCCs](https://docs.openshift.org/latest/admin_guide/manage_scc.html#updating-the-default-security-context-constraints) |
|
| 42 |
+ which will set this field to true for the privileged SCC and false for the restricted SCC. |
|
| 43 |
+ 1. allowHostIPC - defaults to false. You may wish to change this to true on any privileged SCCs or |
|
| 44 |
+ [reset your default SCCs](https://docs.openshift.org/latest/admin_guide/manage_scc.html#updating-the-default-security-context-constraints) |
|
| 45 |
+ which will set this field to true for the privileged SCC and false for the restricted SCC. |
|
| 46 |
+ 1. fsGroup - if the strategy type is unset this field will default based on the runAsUser strategy. |
|
| 47 |
+ If runAsUser is set to RunAsAny this field will also be set to RunAsAny. If the strategy type is |
|
| 48 |
+ any other value this field will default to MustRunAs and look to the namespace for [annotation |
|
| 49 |
+ configuration](https://docs.openshift.org/latest/architecture/additional_concepts/authorization.html#understanding-pre-allocated-values-and-security-context-constraints). |
|
| 50 |
+ 1. supplementalGroups - if the strategy type is unset this field will default based on the runAsUser strategy. |
|
| 51 |
+ If runAsUser is set to RunAsAny this field will also be set to RunAsAny. If the strategy type is |
|
| 52 |
+ any other value this field will default to MustRunAs and look to the namespace for [annotation |
|
| 53 |
+ configuration](https://docs.openshift.org/latest/architecture/additional_concepts/authorization.html#understanding-pre-allocated-values-and-security-context-constraints). |
|
| 54 |
+ |
|
| 37 | 55 |
|
| 38 | 56 |
1. The `v1beta3` API version is being removed in Origin 1.1 (OSE 3.1). |
| 39 | 57 |
Existing `v1beta3` resources stored in etcd will still be readable and |
| ... | ... |
@@ -31,6 +31,12 @@ func GetBootstrapSecurityContextConstraints(buildControllerUsername string) []ka |
| 31 | 31 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 32 | 32 |
Type: kapi.RunAsUserStrategyRunAsAny, |
| 33 | 33 |
}, |
| 34 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 35 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 36 |
+ }, |
|
| 37 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 38 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 39 |
+ }, |
|
| 34 | 40 |
Users: []string{buildControllerUsername},
|
| 35 | 41 |
Groups: []string{ClusterAdminGroup, NodesGroup},
|
| 36 | 42 |
}, |
| ... | ... |
@@ -50,6 +56,22 @@ func GetBootstrapSecurityContextConstraints(buildControllerUsername string) []ka |
| 50 | 50 |
// will fail. |
| 51 | 51 |
Type: kapi.RunAsUserStrategyMustRunAsRange, |
| 52 | 52 |
}, |
| 53 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 54 |
+ // This strategy requires that annotations on the namespace which will be populated |
|
| 55 |
+ // by the admission controller. Admission will first look for the SupplementalGroupsAnnotation |
|
| 56 |
+ // on the namespace and if it is unable to find that annotation it will attempt |
|
| 57 |
+ // to use the UIDRangeAnnotation. If neither annotation exists then creation |
|
| 58 |
+ // of the SCC will fail. |
|
| 59 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 60 |
+ }, |
|
| 61 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 62 |
+ // This strategy requires that annotations on the namespace which will be populated |
|
| 63 |
+ // by the admission controller. Admission will first look for the SupplementalGroupsAnnotation |
|
| 64 |
+ // on the namespace and if it is unable to find that annotation it will attempt |
|
| 65 |
+ // to use the UIDRangeAnnotation. If neither annotation exists then creation |
|
| 66 |
+ // of the SCC will fail. |
|
| 67 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 68 |
+ }, |
|
| 53 | 69 |
Groups: []string{AuthenticatedGroup},
|
| 54 | 70 |
}, |
| 55 | 71 |
} |
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"k8s.io/kubernetes/pkg/fields" |
| 16 | 16 |
"k8s.io/kubernetes/pkg/labels" |
| 17 | 17 |
"k8s.io/kubernetes/pkg/runtime" |
| 18 |
+ sc "k8s.io/kubernetes/pkg/securitycontext" |
|
| 18 | 19 |
scc "k8s.io/kubernetes/pkg/securitycontextconstraints" |
| 19 | 20 |
"k8s.io/kubernetes/pkg/util/sets" |
| 20 | 21 |
"k8s.io/kubernetes/pkg/watch" |
| ... | ... |
@@ -164,24 +165,46 @@ func assignSecurityContext(provider scc.SecurityContextConstraintsProvider, pod |
| 164 | 164 |
|
| 165 | 165 |
errs := fielderrors.ValidationErrorList{}
|
| 166 | 166 |
|
| 167 |
- for i, c := range pod.Spec.Containers {
|
|
| 168 |
- sc, err := provider.CreateSecurityContext(pod, &c) |
|
| 167 |
+ psc, err := provider.CreatePodSecurityContext(pod) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ errs = append(errs, fielderrors.NewFieldInvalid("spec.securityContext", pod.Spec.SecurityContext, err.Error()))
|
|
| 170 |
+ } |
|
| 171 |
+ |
|
| 172 |
+ // save the original PSC and validate the generated PSC. Leave the generated PSC |
|
| 173 |
+ // set for container generation/validation. We will reset to original post container |
|
| 174 |
+ // validation. |
|
| 175 |
+ originalPSC := pod.Spec.SecurityContext |
|
| 176 |
+ pod.Spec.SecurityContext = psc |
|
| 177 |
+ errs = append(errs, provider.ValidatePodSecurityContext(pod).Prefix("spec.securityContext")...)
|
|
| 178 |
+ |
|
| 179 |
+ // Note: this is not changing the original container, we will set container SCs later so long |
|
| 180 |
+ // as all containers validated under the same SCC. |
|
| 181 |
+ for i, containerCopy := range pod.Spec.Containers {
|
|
| 182 |
+ // We will determine the effective security context for the container and validate against that |
|
| 183 |
+ // since that is how the sc provider will eventually apply settings in the runtime. |
|
| 184 |
+ // This results in an SC that is based on the Pod's PSC with the set fields from the container |
|
| 185 |
+ // overriding pod level settings. |
|
| 186 |
+ containerCopy.SecurityContext = sc.DetermineEffectiveSecurityContext(pod, &containerCopy) |
|
| 187 |
+ |
|
| 188 |
+ sc, err := provider.CreateContainerSecurityContext(pod, &containerCopy) |
|
| 169 | 189 |
if err != nil {
|
| 170 | 190 |
errs = append(errs, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.containers[%d].securityContext", i), "", err.Error()))
|
| 171 | 191 |
continue |
| 172 | 192 |
} |
| 173 | 193 |
generatedSCs[i] = sc |
| 174 | 194 |
|
| 175 |
- c.SecurityContext = sc |
|
| 176 |
- errs = append(errs, provider.ValidateSecurityContext(pod, &c).Prefix(fmt.Sprintf("spec.containers[%d].securityContext", i))...)
|
|
| 195 |
+ containerCopy.SecurityContext = sc |
|
| 196 |
+ errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy).Prefix(fmt.Sprintf("spec.containers[%d].securityContext", i))...)
|
|
| 177 | 197 |
} |
| 178 | 198 |
|
| 179 | 199 |
if len(errs) > 0 {
|
| 200 |
+ // ensure psc is not mutated if there are errors |
|
| 201 |
+ pod.Spec.SecurityContext = originalPSC |
|
| 180 | 202 |
return errs |
| 181 | 203 |
} |
| 182 | 204 |
|
| 183 | 205 |
// if we've reached this code then we've generated and validated an SC for every container in the |
| 184 |
- // pod so let's apply what we generated |
|
| 206 |
+ // pod so let's apply what we generated. Note: the psc is already applied. |
|
| 185 | 207 |
for i, sc := range generatedSCs {
|
| 186 | 208 |
pod.Spec.Containers[i].SecurityContext = sc |
| 187 | 209 |
} |
| ... | ... |
@@ -205,51 +228,63 @@ func (c *constraint) createProvidersFromConstraints(ns string, sccs []*kapi.Secu |
| 205 | 205 |
var err error |
| 206 | 206 |
resolveUIDRange := requiresPreAllocatedUIDRange(constraint) |
| 207 | 207 |
resolveSELinuxLevel := requiresPreAllocatedSELinuxLevel(constraint) |
| 208 |
+ resolveFSGroup := requiresPreallocatedFSGroup(constraint) |
|
| 209 |
+ resolveSupplementalGroups := requiresPreallocatedSupplementalGroups(constraint) |
|
| 210 |
+ requiresNamespaceAllocations := resolveUIDRange || resolveSELinuxLevel || resolveFSGroup || resolveSupplementalGroups |
|
| 208 | 211 |
|
| 209 |
- if resolveUIDRange || resolveSELinuxLevel {
|
|
| 210 |
- var min, max *int64 |
|
| 211 |
- var level string |
|
| 212 |
- |
|
| 212 |
+ if requiresNamespaceAllocations {
|
|
| 213 | 213 |
// Ensure we have the namespace |
| 214 |
- if namespace, err = c.getNamespace(ns, namespace); err != nil {
|
|
| 214 |
+ namespace, err = c.getNamespace(ns, namespace) |
|
| 215 |
+ if err != nil {
|
|
| 215 | 216 |
errs = append(errs, fmt.Errorf("error fetching namespace %s required to preallocate values for %s: %v", ns, constraint.Name, err))
|
| 216 | 217 |
continue |
| 217 | 218 |
} |
| 219 |
+ } |
|
| 218 | 220 |
|
| 219 |
- // Resolve the values from the namespace |
|
| 220 |
- if resolveUIDRange {
|
|
| 221 |
- if min, max, err = getPreallocatedUIDRange(namespace); err != nil {
|
|
| 222 |
- errs = append(errs, fmt.Errorf("unable to find pre-allocated uid annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 223 |
- continue |
|
| 224 |
- } |
|
| 221 |
+ // Make a copy of the constraint so we don't mutate the store's cache |
|
| 222 |
+ var constraintCopy kapi.SecurityContextConstraints = *constraint |
|
| 223 |
+ constraint = &constraintCopy |
|
| 224 |
+ |
|
| 225 |
+ // Resolve the values from the namespace |
|
| 226 |
+ if resolveUIDRange {
|
|
| 227 |
+ constraint.RunAsUser.UIDRangeMin, constraint.RunAsUser.UIDRangeMax, err = getPreallocatedUIDRange(namespace) |
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ errs = append(errs, fmt.Errorf("unable to find pre-allocated uid annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 230 |
+ continue |
|
| 225 | 231 |
} |
| 226 |
- if resolveSELinuxLevel {
|
|
| 227 |
- if level, err = getPreallocatedLevel(namespace); err != nil {
|
|
| 228 |
- errs = append(errs, fmt.Errorf("unable to find pre-allocated mcs annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 229 |
- continue |
|
| 230 |
- } |
|
| 232 |
+ } |
|
| 233 |
+ if resolveSELinuxLevel {
|
|
| 234 |
+ var level string |
|
| 235 |
+ if level, err = getPreallocatedLevel(namespace); err != nil {
|
|
| 236 |
+ errs = append(errs, fmt.Errorf("unable to find pre-allocated mcs annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 237 |
+ continue |
|
| 231 | 238 |
} |
| 232 | 239 |
|
| 233 |
- // Make a copy of the constraint so we don't mutate the store's cache |
|
| 234 |
- var constraintCopy kapi.SecurityContextConstraints = *constraint |
|
| 235 |
- constraint = &constraintCopy |
|
| 236 |
- if resolveSELinuxLevel && constraint.SELinuxContext.SELinuxOptions != nil {
|
|
| 237 |
- // Make a copy of the SELinuxOptions so we don't mutate the store's cache |
|
| 240 |
+ // SELinuxOptions is a pointer, if we are resolving and it is already initialized |
|
| 241 |
+ // we need to make a copy of it so we don't manipulate the store's cache. |
|
| 242 |
+ if constraint.SELinuxContext.SELinuxOptions != nil {
|
|
| 238 | 243 |
var seLinuxOptionsCopy kapi.SELinuxOptions = *constraint.SELinuxContext.SELinuxOptions |
| 239 | 244 |
constraint.SELinuxContext.SELinuxOptions = &seLinuxOptionsCopy |
| 245 |
+ } else {
|
|
| 246 |
+ constraint.SELinuxContext.SELinuxOptions = &kapi.SELinuxOptions{}
|
|
| 240 | 247 |
} |
| 241 |
- |
|
| 242 |
- // Set the resolved values |
|
| 243 |
- if resolveUIDRange {
|
|
| 244 |
- constraint.RunAsUser.UIDRangeMin = min |
|
| 245 |
- constraint.RunAsUser.UIDRangeMax = max |
|
| 248 |
+ constraint.SELinuxContext.SELinuxOptions.Level = level |
|
| 249 |
+ } |
|
| 250 |
+ if resolveFSGroup {
|
|
| 251 |
+ fsGroup, err := getPreallocatedFSGroup(namespace) |
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ errs = append(errs, fmt.Errorf("unable to find pre-allocated group annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 254 |
+ continue |
|
| 246 | 255 |
} |
| 247 |
- if resolveSELinuxLevel {
|
|
| 248 |
- if constraint.SELinuxContext.SELinuxOptions == nil {
|
|
| 249 |
- constraint.SELinuxContext.SELinuxOptions = &kapi.SELinuxOptions{}
|
|
| 250 |
- } |
|
| 251 |
- constraint.SELinuxContext.SELinuxOptions.Level = level |
|
| 256 |
+ constraint.FSGroup.Ranges = fsGroup |
|
| 257 |
+ } |
|
| 258 |
+ if resolveSupplementalGroups {
|
|
| 259 |
+ supplementalGroups, err := getPreallocatedSupplementalGroups(namespace) |
|
| 260 |
+ if err != nil {
|
|
| 261 |
+ errs = append(errs, fmt.Errorf("unable to find pre-allocated group annotation for namespace %s while trying to configure SCC %s: %v", namespace.Name, constraint.Name, err))
|
|
| 262 |
+ continue |
|
| 252 | 263 |
} |
| 264 |
+ constraint.SupplementalGroups.Ranges = supplementalGroups |
|
| 253 | 265 |
} |
| 254 | 266 |
|
| 255 | 267 |
// Create the provider |
| ... | ... |
@@ -315,7 +350,7 @@ func constraintSupportsGroup(group string, constraintGroups []string) bool {
|
| 315 | 315 |
return false |
| 316 | 316 |
} |
| 317 | 317 |
|
| 318 |
-// getPreallocatedUIDRange retrieves the annotated value from the service account, splits it to make |
|
| 318 |
+// getPreallocatedUIDRange retrieves the annotated value from the namespace, splits it to make |
|
| 319 | 319 |
// the min/max and formats the data into the necessary types for the strategy options. |
| 320 | 320 |
func getPreallocatedUIDRange(ns *kapi.Namespace) (*int64, *int64, error) {
|
| 321 | 321 |
annotationVal, ok := ns.Annotations[allocator.UIDRangeAnnotation] |
| ... | ... |
@@ -336,7 +371,7 @@ func getPreallocatedUIDRange(ns *kapi.Namespace) (*int64, *int64, error) {
|
| 336 | 336 |
return &min, &max, nil |
| 337 | 337 |
} |
| 338 | 338 |
|
| 339 |
-// getPreallocatedLevel gets the annotated value from the service account. |
|
| 339 |
+// getPreallocatedLevel gets the annotated value from the namespace. |
|
| 340 | 340 |
func getPreallocatedLevel(ns *kapi.Namespace) (string, error) {
|
| 341 | 341 |
level, ok := ns.Annotations[allocator.MCSAnnotation] |
| 342 | 342 |
if !ok {
|
| ... | ... |
@@ -349,6 +384,87 @@ func getPreallocatedLevel(ns *kapi.Namespace) (string, error) {
|
| 349 | 349 |
return level, nil |
| 350 | 350 |
} |
| 351 | 351 |
|
| 352 |
+// getSupplementalGroupsAnnotation provides a backwards compatible way to get supplemental groups |
|
| 353 |
+// annotations from a namespace by looking for SupplementalGroupsAnnotation and falling back to |
|
| 354 |
+// UIDRangeAnnotation if it is not found. |
|
| 355 |
+func getSupplementalGroupsAnnotation(ns *kapi.Namespace) (string, error) {
|
|
| 356 |
+ groups, ok := ns.Annotations[allocator.SupplementalGroupsAnnotation] |
|
| 357 |
+ if !ok {
|
|
| 358 |
+ glog.V(4).Infof("unable to find supplemental group annotation %s falling back to %s", allocator.SupplementalGroupsAnnotation, allocator.UIDRangeAnnotation)
|
|
| 359 |
+ |
|
| 360 |
+ groups, ok = ns.Annotations[allocator.UIDRangeAnnotation] |
|
| 361 |
+ if !ok {
|
|
| 362 |
+ return "", fmt.Errorf("unable to find supplemental group or uid annotation for namespace %s", ns.Name)
|
|
| 363 |
+ } |
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ if len(groups) == 0 {
|
|
| 367 |
+ return "", fmt.Errorf("unable to find groups using %s and %s annotations", allocator.SupplementalGroupsAnnotation, allocator.UIDRangeAnnotation)
|
|
| 368 |
+ } |
|
| 369 |
+ return groups, nil |
|
| 370 |
+} |
|
| 371 |
+ |
|
| 372 |
+// getPreallocatedFSGroup gets the annotated value from the namespace. |
|
| 373 |
+func getPreallocatedFSGroup(ns *kapi.Namespace) ([]kapi.IDRange, error) {
|
|
| 374 |
+ groups, err := getSupplementalGroupsAnnotation(ns) |
|
| 375 |
+ if err != nil {
|
|
| 376 |
+ return nil, err |
|
| 377 |
+ } |
|
| 378 |
+ glog.V(4).Infof("got preallocated value for groups: %s in namespace %s", groups, ns.Name)
|
|
| 379 |
+ |
|
| 380 |
+ blocks, err := parseSupplementalGroupAnnotation(groups) |
|
| 381 |
+ if err != nil {
|
|
| 382 |
+ return nil, err |
|
| 383 |
+ } |
|
| 384 |
+ return []kapi.IDRange{
|
|
| 385 |
+ {
|
|
| 386 |
+ Min: int64(blocks[0].Start), |
|
| 387 |
+ Max: int64(blocks[0].Start), |
|
| 388 |
+ }, |
|
| 389 |
+ }, nil |
|
| 390 |
+} |
|
| 391 |
+ |
|
| 392 |
+// getPreallocatedSupplementalGroups gets the annotated value from the namespace. |
|
| 393 |
+func getPreallocatedSupplementalGroups(ns *kapi.Namespace) ([]kapi.IDRange, error) {
|
|
| 394 |
+ groups, err := getSupplementalGroupsAnnotation(ns) |
|
| 395 |
+ if err != nil {
|
|
| 396 |
+ return nil, err |
|
| 397 |
+ } |
|
| 398 |
+ glog.V(4).Infof("got preallocated value for groups: %s in namespace %s", groups, ns.Name)
|
|
| 399 |
+ |
|
| 400 |
+ blocks, err := parseSupplementalGroupAnnotation(groups) |
|
| 401 |
+ if err != nil {
|
|
| 402 |
+ return nil, err |
|
| 403 |
+ } |
|
| 404 |
+ |
|
| 405 |
+ idRanges := []kapi.IDRange{}
|
|
| 406 |
+ for _, block := range blocks {
|
|
| 407 |
+ rng := kapi.IDRange{
|
|
| 408 |
+ Min: int64(block.Start), |
|
| 409 |
+ Max: int64(block.End), |
|
| 410 |
+ } |
|
| 411 |
+ idRanges = append(idRanges, rng) |
|
| 412 |
+ } |
|
| 413 |
+ return idRanges, nil |
|
| 414 |
+} |
|
| 415 |
+ |
|
| 416 |
+// parseSupplementalGroupAnnotation parses the group annotation into blocks. |
|
| 417 |
+func parseSupplementalGroupAnnotation(groups string) ([]uid.Block, error) {
|
|
| 418 |
+ blocks := []uid.Block{}
|
|
| 419 |
+ segments := strings.Split(groups, ",") |
|
| 420 |
+ for _, segment := range segments {
|
|
| 421 |
+ block, err := uid.ParseBlock(segment) |
|
| 422 |
+ if err != nil {
|
|
| 423 |
+ return nil, err |
|
| 424 |
+ } |
|
| 425 |
+ blocks = append(blocks, block) |
|
| 426 |
+ } |
|
| 427 |
+ if len(blocks) == 0 {
|
|
| 428 |
+ return nil, fmt.Errorf("no blocks parsed from annotation %s", groups)
|
|
| 429 |
+ } |
|
| 430 |
+ return blocks, nil |
|
| 431 |
+} |
|
| 432 |
+ |
|
| 352 | 433 |
// requiresPreAllocatedUIDRange returns true if the strategy is must run in range and the min or max |
| 353 | 434 |
// is not set. |
| 354 | 435 |
func requiresPreAllocatedUIDRange(constraint *kapi.SecurityContextConstraints) bool {
|
| ... | ... |
@@ -369,6 +485,24 @@ func requiresPreAllocatedSELinuxLevel(constraint *kapi.SecurityContextConstraint |
| 369 | 369 |
return constraint.SELinuxContext.SELinuxOptions.Level == "" |
| 370 | 370 |
} |
| 371 | 371 |
|
| 372 |
+// requiresPreAllocatedSELinuxLevel returns true if the strategy is must run as and there is no |
|
| 373 |
+// range specified. |
|
| 374 |
+func requiresPreallocatedSupplementalGroups(constraint *kapi.SecurityContextConstraints) bool {
|
|
| 375 |
+ if constraint.SupplementalGroups.Type != kapi.SupplementalGroupsStrategyMustRunAs {
|
|
| 376 |
+ return false |
|
| 377 |
+ } |
|
| 378 |
+ return len(constraint.SupplementalGroups.Ranges) == 0 |
|
| 379 |
+} |
|
| 380 |
+ |
|
| 381 |
+// requiresPreallocatedFSGroup returns true if the strategy is must run as and there is no |
|
| 382 |
+// range specified. |
|
| 383 |
+func requiresPreallocatedFSGroup(constraint *kapi.SecurityContextConstraints) bool {
|
|
| 384 |
+ if constraint.FSGroup.Type != kapi.FSGroupStrategyMustRunAs {
|
|
| 385 |
+ return false |
|
| 386 |
+ } |
|
| 387 |
+ return len(constraint.FSGroup.Ranges) == 0 |
|
| 388 |
+} |
|
| 389 |
+ |
|
| 372 | 390 |
// deduplicateSecurityContextConstraints ensures we have a unique slice of constraints. |
| 373 | 391 |
func deduplicateSecurityContextConstraints(sccs []*kapi.SecurityContextConstraints) []*kapi.SecurityContextConstraints {
|
| 374 | 392 |
deDuped := []*kapi.SecurityContextConstraints{}
|
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"k8s.io/kubernetes/pkg/util" |
| 16 | 16 |
|
| 17 | 17 |
allocator "github.com/openshift/origin/pkg/security" |
| 18 |
+ "github.com/openshift/origin/pkg/security/uid" |
|
| 18 | 19 |
) |
| 19 | 20 |
|
| 20 | 21 |
func NewTestAdmission(store cache.Store, kclient client.Interface) kadmission.Interface {
|
| ... | ... |
@@ -31,11 +32,16 @@ func TestAdmit(t *testing.T) {
|
| 31 | 31 |
ObjectMeta: kapi.ObjectMeta{
|
| 32 | 32 |
Name: "default", |
| 33 | 33 |
Annotations: map[string]string{
|
| 34 |
- allocator.UIDRangeAnnotation: "1/3", |
|
| 35 |
- allocator.MCSAnnotation: "s0:c1,c0", |
|
| 34 |
+ allocator.UIDRangeAnnotation: "1/3", |
|
| 35 |
+ allocator.MCSAnnotation: "s0:c1,c0", |
|
| 36 |
+ allocator.SupplementalGroupsAnnotation: "2/3", |
|
| 36 | 37 |
}, |
| 37 | 38 |
}, |
| 38 | 39 |
} |
| 40 |
+ |
|
| 41 |
+ // used for cases where things are preallocated |
|
| 42 |
+ defaultGroup := int64(2) |
|
| 43 |
+ |
|
| 39 | 44 |
serviceAccount := &kapi.ServiceAccount{
|
| 40 | 45 |
ObjectMeta: kapi.ObjectMeta{
|
| 41 | 46 |
Name: "default", |
| ... | ... |
@@ -55,6 +61,12 @@ func TestAdmit(t *testing.T) {
|
| 55 | 55 |
SELinuxContext: kapi.SELinuxContextStrategyOptions{
|
| 56 | 56 |
Type: kapi.SELinuxStrategyMustRunAs, |
| 57 | 57 |
}, |
| 58 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 59 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 60 |
+ }, |
|
| 61 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 62 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 63 |
+ }, |
|
| 58 | 64 |
Groups: []string{"system:serviceaccounts"},
|
| 59 | 65 |
} |
| 60 | 66 |
// create scc that has specific requirements that shouldn't match but is permissioned to |
| ... | ... |
@@ -74,6 +86,18 @@ func TestAdmit(t *testing.T) {
|
| 74 | 74 |
Level: "s9:z0,z1", |
| 75 | 75 |
}, |
| 76 | 76 |
}, |
| 77 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 78 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 79 |
+ Ranges: []kapi.IDRange{
|
|
| 80 |
+ {Min: 999, Max: 999},
|
|
| 81 |
+ }, |
|
| 82 |
+ }, |
|
| 83 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 84 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 85 |
+ Ranges: []kapi.IDRange{
|
|
| 86 |
+ {Min: 999, Max: 999},
|
|
| 87 |
+ }, |
|
| 88 |
+ }, |
|
| 77 | 89 |
Groups: []string{"system:serviceaccounts"},
|
| 78 | 90 |
} |
| 79 | 91 |
store := cache.NewStore(cache.MetaNamespaceKeyFunc) |
| ... | ... |
@@ -125,6 +149,23 @@ func TestAdmit(t *testing.T) {
|
| 125 | 125 |
Level: "s0:c1,c0", |
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 |
+ // specifies an FSGroup in the range of preallocated sup group annotation |
|
| 129 |
+ specifyFSGroupInRange := goodPod() |
|
| 130 |
+ // group in the range of a preallocated fs group which, by default is a single digit range |
|
| 131 |
+ // based on the first value of the ns annotation. |
|
| 132 |
+ goodFSGroup := int64(2) |
|
| 133 |
+ specifyFSGroupInRange.Spec.SecurityContext.FSGroup = &goodFSGroup |
|
| 134 |
+ |
|
| 135 |
+ // specifies a sup group in the range of preallocated sup group annotation |
|
| 136 |
+ specifySupGroup := goodPod() |
|
| 137 |
+ // group is not the default but still in the range |
|
| 138 |
+ specifySupGroup.Spec.SecurityContext.SupplementalGroups = []int64{3}
|
|
| 139 |
+ |
|
| 140 |
+ specifyPodLevelSELinux := goodPod() |
|
| 141 |
+ specifyPodLevelSELinux.Spec.SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{
|
|
| 142 |
+ Level: "s0:c1,c0", |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 128 | 145 |
requestsHostNetwork := goodPod() |
| 129 | 146 |
requestsHostNetwork.Spec.SecurityContext.HostNetwork = true |
| 130 | 147 |
|
| ... | ... |
@@ -137,12 +178,29 @@ func TestAdmit(t *testing.T) {
|
| 137 | 137 |
requestsHostPorts := goodPod() |
| 138 | 138 |
requestsHostPorts.Spec.Containers[0].Ports = []kapi.ContainerPort{{HostPort: 1}}
|
| 139 | 139 |
|
| 140 |
+ requestsSupplementalGroup := goodPod() |
|
| 141 |
+ requestsSupplementalGroup.Spec.SecurityContext.SupplementalGroups = []int64{1}
|
|
| 142 |
+ |
|
| 143 |
+ requestsFSGroup := goodPod() |
|
| 144 |
+ fsGroup := int64(1) |
|
| 145 |
+ requestsFSGroup.Spec.SecurityContext.FSGroup = &fsGroup |
|
| 146 |
+ |
|
| 147 |
+ requestsPodLevelMCS := goodPod() |
|
| 148 |
+ requestsPodLevelMCS.Spec.SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{
|
|
| 149 |
+ User: "user", |
|
| 150 |
+ Type: "type", |
|
| 151 |
+ Role: "role", |
|
| 152 |
+ Level: "level", |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 140 | 155 |
testCases := map[string]struct {
|
| 141 |
- pod *kapi.Pod |
|
| 142 |
- shouldAdmit bool |
|
| 143 |
- expectedUID int64 |
|
| 144 |
- expectedLevel string |
|
| 145 |
- expectedPriv bool |
|
| 156 |
+ pod *kapi.Pod |
|
| 157 |
+ shouldAdmit bool |
|
| 158 |
+ expectedUID int64 |
|
| 159 |
+ expectedLevel string |
|
| 160 |
+ expectedFSGroup int64 |
|
| 161 |
+ expectedSupGroups []int64 |
|
| 162 |
+ expectedPriv bool |
|
| 146 | 163 |
}{
|
| 147 | 164 |
"uidNotInRange": {
|
| 148 | 165 |
pod: uidNotInRange, |
| ... | ... |
@@ -157,16 +215,44 @@ func TestAdmit(t *testing.T) {
|
| 157 | 157 |
shouldAdmit: false, |
| 158 | 158 |
}, |
| 159 | 159 |
"specifyUIDInRange": {
|
| 160 |
- pod: specifyUIDInRange, |
|
| 161 |
- shouldAdmit: true, |
|
| 162 |
- expectedUID: *specifyUIDInRange.Spec.Containers[0].SecurityContext.RunAsUser, |
|
| 163 |
- expectedLevel: "s0:c1,c0", |
|
| 160 |
+ pod: specifyUIDInRange, |
|
| 161 |
+ shouldAdmit: true, |
|
| 162 |
+ expectedUID: *specifyUIDInRange.Spec.Containers[0].SecurityContext.RunAsUser, |
|
| 163 |
+ expectedLevel: "s0:c1,c0", |
|
| 164 |
+ expectedFSGroup: defaultGroup, |
|
| 165 |
+ expectedSupGroups: []int64{defaultGroup},
|
|
| 164 | 166 |
}, |
| 165 | 167 |
"specifyLabels": {
|
| 166 |
- pod: specifyLabels, |
|
| 167 |
- shouldAdmit: true, |
|
| 168 |
- expectedUID: 1, |
|
| 169 |
- expectedLevel: specifyLabels.Spec.Containers[0].SecurityContext.SELinuxOptions.Level, |
|
| 168 |
+ pod: specifyLabels, |
|
| 169 |
+ shouldAdmit: true, |
|
| 170 |
+ expectedUID: 1, |
|
| 171 |
+ expectedLevel: specifyLabels.Spec.Containers[0].SecurityContext.SELinuxOptions.Level, |
|
| 172 |
+ expectedFSGroup: defaultGroup, |
|
| 173 |
+ expectedSupGroups: []int64{defaultGroup},
|
|
| 174 |
+ }, |
|
| 175 |
+ "specifyFSGroup": {
|
|
| 176 |
+ pod: specifyFSGroupInRange, |
|
| 177 |
+ shouldAdmit: true, |
|
| 178 |
+ expectedUID: 1, |
|
| 179 |
+ expectedLevel: "s0:c1,c0", |
|
| 180 |
+ expectedFSGroup: *specifyFSGroupInRange.Spec.SecurityContext.FSGroup, |
|
| 181 |
+ expectedSupGroups: []int64{defaultGroup},
|
|
| 182 |
+ }, |
|
| 183 |
+ "specifySupGroup": {
|
|
| 184 |
+ pod: specifySupGroup, |
|
| 185 |
+ shouldAdmit: true, |
|
| 186 |
+ expectedUID: 1, |
|
| 187 |
+ expectedLevel: "s0:c1,c0", |
|
| 188 |
+ expectedFSGroup: defaultGroup, |
|
| 189 |
+ expectedSupGroups: []int64{specifySupGroup.Spec.SecurityContext.SupplementalGroups[0]},
|
|
| 190 |
+ }, |
|
| 191 |
+ "specifyPodLevelSELinuxLevel": {
|
|
| 192 |
+ pod: specifyPodLevelSELinux, |
|
| 193 |
+ shouldAdmit: true, |
|
| 194 |
+ expectedUID: 1, |
|
| 195 |
+ expectedLevel: "s0:c1,c0", |
|
| 196 |
+ expectedFSGroup: defaultGroup, |
|
| 197 |
+ expectedSupGroups: []int64{defaultGroup},
|
|
| 170 | 198 |
}, |
| 171 | 199 |
"requestsHostNetwork": {
|
| 172 | 200 |
pod: requestsHostNetwork, |
| ... | ... |
@@ -184,6 +270,18 @@ func TestAdmit(t *testing.T) {
|
| 184 | 184 |
pod: requestsHostIPC, |
| 185 | 185 |
shouldAdmit: false, |
| 186 | 186 |
}, |
| 187 |
+ "requestsSupplementalGroup": {
|
|
| 188 |
+ pod: requestsSupplementalGroup, |
|
| 189 |
+ shouldAdmit: false, |
|
| 190 |
+ }, |
|
| 191 |
+ "requestsFSGroup": {
|
|
| 192 |
+ pod: requestsFSGroup, |
|
| 193 |
+ shouldAdmit: false, |
|
| 194 |
+ }, |
|
| 195 |
+ "requestsPodLevelMCS": {
|
|
| 196 |
+ pod: requestsPodLevelMCS, |
|
| 197 |
+ shouldAdmit: false, |
|
| 198 |
+ }, |
|
| 187 | 199 |
} |
| 188 | 200 |
|
| 189 | 201 |
for k, v := range testCases {
|
| ... | ... |
@@ -205,12 +303,30 @@ func TestAdmit(t *testing.T) {
|
| 205 | 205 |
if validatedSCC != saSCC.Name {
|
| 206 | 206 |
t.Errorf("%s should have validated against %s but found %s", k, saSCC.Name, validatedSCC)
|
| 207 | 207 |
} |
| 208 |
+ |
|
| 209 |
+ // ensure anything we expected to be defaulted on the container level is set |
|
| 208 | 210 |
if *v.pod.Spec.Containers[0].SecurityContext.RunAsUser != v.expectedUID {
|
| 209 | 211 |
t.Errorf("%s expected UID %d but found %d", k, v.expectedUID, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser)
|
| 210 | 212 |
} |
| 211 | 213 |
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions.Level != v.expectedLevel {
|
| 212 | 214 |
t.Errorf("%s expected Level %s but found %s", k, v.expectedLevel, v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions.Level)
|
| 213 | 215 |
} |
| 216 |
+ |
|
| 217 |
+ // ensure anything we expected to be defaulted on the pod level is set |
|
| 218 |
+ if v.pod.Spec.SecurityContext.SELinuxOptions.Level != v.expectedLevel {
|
|
| 219 |
+ t.Errorf("%s expected pod level SELinux Level %s but found %s", k, v.expectedLevel, v.pod.Spec.SecurityContext.SELinuxOptions.Level)
|
|
| 220 |
+ } |
|
| 221 |
+ if *v.pod.Spec.SecurityContext.FSGroup != v.expectedFSGroup {
|
|
| 222 |
+ t.Errorf("%s expected fsgroup %d but found %d", k, v.expectedFSGroup, *v.pod.Spec.SecurityContext.FSGroup)
|
|
| 223 |
+ } |
|
| 224 |
+ if len(v.pod.Spec.SecurityContext.SupplementalGroups) != len(v.expectedSupGroups) {
|
|
| 225 |
+ t.Errorf("%s found unexpected supplemental groups. Expected: %v, actual %v", k, v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups)
|
|
| 226 |
+ } |
|
| 227 |
+ for _, g := range v.expectedSupGroups {
|
|
| 228 |
+ if !hasSupGroup(g, v.pod.Spec.SecurityContext.SupplementalGroups) {
|
|
| 229 |
+ t.Errorf("%s expected sup group %d", k, g)
|
|
| 230 |
+ } |
|
| 231 |
+ } |
|
| 214 | 232 |
} |
| 215 | 233 |
} |
| 216 | 234 |
|
| ... | ... |
@@ -231,6 +347,12 @@ func TestAdmit(t *testing.T) {
|
| 231 | 231 |
SELinuxContext: kapi.SELinuxContextStrategyOptions{
|
| 232 | 232 |
Type: kapi.SELinuxStrategyRunAsAny, |
| 233 | 233 |
}, |
| 234 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 235 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 236 |
+ }, |
|
| 237 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 238 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 239 |
+ }, |
|
| 234 | 240 |
Groups: []string{"system:serviceaccounts"},
|
| 235 | 241 |
} |
| 236 | 242 |
store.Add(adminSCC) |
| ... | ... |
@@ -253,10 +375,20 @@ func TestAdmit(t *testing.T) {
|
| 253 | 253 |
} |
| 254 | 254 |
} |
| 255 | 255 |
|
| 256 |
+func hasSupGroup(group int64, groups []int64) bool {
|
|
| 257 |
+ for _, g := range groups {
|
|
| 258 |
+ if g == group {
|
|
| 259 |
+ return true |
|
| 260 |
+ } |
|
| 261 |
+ } |
|
| 262 |
+ return false |
|
| 263 |
+} |
|
| 264 |
+ |
|
| 256 | 265 |
func TestAssignSecurityContext(t *testing.T) {
|
| 257 | 266 |
// set up test data |
| 258 | 267 |
// scc that will deny privileged container requests and has a default value for a field (uid) |
| 259 | 268 |
var uid int64 = 9999 |
| 269 |
+ fsGroup := int64(1) |
|
| 260 | 270 |
scc := &kapi.SecurityContextConstraints{
|
| 261 | 271 |
ObjectMeta: kapi.ObjectMeta{
|
| 262 | 272 |
Name: "test scc", |
| ... | ... |
@@ -268,6 +400,17 @@ func TestAssignSecurityContext(t *testing.T) {
|
| 268 | 268 |
Type: kapi.RunAsUserStrategyMustRunAs, |
| 269 | 269 |
UID: &uid, |
| 270 | 270 |
}, |
| 271 |
+ |
|
| 272 |
+ // require allocation for a field in the psc as well to test changes/no changes |
|
| 273 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 274 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 275 |
+ Ranges: []kapi.IDRange{
|
|
| 276 |
+ {Min: fsGroup, Max: fsGroup},
|
|
| 277 |
+ }, |
|
| 278 |
+ }, |
|
| 279 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 280 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 281 |
+ }, |
|
| 271 | 282 |
} |
| 272 | 283 |
provider, err := kscc.NewSimpleProvider(scc) |
| 273 | 284 |
if err != nil {
|
| ... | ... |
@@ -291,7 +434,7 @@ func TestAssignSecurityContext(t *testing.T) {
|
| 291 | 291 |
shouldValidate bool |
| 292 | 292 |
expectedUID *int64 |
| 293 | 293 |
}{
|
| 294 |
- "container SC is not changed when invalid": {
|
|
| 294 |
+ "pod and container SC is not changed when invalid": {
|
|
| 295 | 295 |
pod: &kapi.Pod{
|
| 296 | 296 |
Spec: kapi.PodSpec{
|
| 297 | 297 |
SecurityContext: &kapi.PodSecurityContext{},
|
| ... | ... |
@@ -333,16 +476,23 @@ func TestAssignSecurityContext(t *testing.T) {
|
| 333 | 333 |
} |
| 334 | 334 |
|
| 335 | 335 |
// if we shouldn't have validated ensure that uid is not set on the containers |
| 336 |
+ // and ensure the psc does not have fsgroup set |
|
| 336 | 337 |
if !v.shouldValidate {
|
| 338 |
+ if v.pod.Spec.SecurityContext.FSGroup != nil {
|
|
| 339 |
+ t.Errorf("%s had a non-nil FSGroup %d. FSGroup should not be set on test cases that don't validate", k, *v.pod.Spec.SecurityContext.FSGroup)
|
|
| 340 |
+ } |
|
| 337 | 341 |
for _, c := range v.pod.Spec.Containers {
|
| 338 | 342 |
if c.SecurityContext.RunAsUser != nil {
|
| 339 |
- t.Errorf("%s had non-nil UID %d. UID should not be set on test cases that dont' validate", k, *c.SecurityContext.RunAsUser)
|
|
| 343 |
+ t.Errorf("%s had non-nil UID %d. UID should not be set on test cases that don't validate", k, *c.SecurityContext.RunAsUser)
|
|
| 340 | 344 |
} |
| 341 | 345 |
} |
| 342 | 346 |
} |
| 343 | 347 |
|
| 344 | 348 |
// if we validated then the pod sc should be updated now with the defaults from the SCC |
| 345 | 349 |
if v.shouldValidate {
|
| 350 |
+ if *v.pod.Spec.SecurityContext.FSGroup != fsGroup {
|
|
| 351 |
+ t.Errorf("%s expected fsgroup to be defaulted but found %v", k, v.pod.Spec.SecurityContext.FSGroup)
|
|
| 352 |
+ } |
|
| 346 | 353 |
for _, c := range v.pod.Spec.Containers {
|
| 347 | 354 |
if *c.SecurityContext.RunAsUser != uid {
|
| 348 | 355 |
t.Errorf("%s expected uid to be defaulted to %d but found %v", k, uid, c.SecurityContext.RunAsUser)
|
| ... | ... |
@@ -357,8 +507,9 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 357 | 357 |
ObjectMeta: kapi.ObjectMeta{
|
| 358 | 358 |
Name: "default", |
| 359 | 359 |
Annotations: map[string]string{
|
| 360 |
- allocator.UIDRangeAnnotation: "1/3", |
|
| 361 |
- allocator.MCSAnnotation: "s0:c1,c0", |
|
| 360 |
+ allocator.UIDRangeAnnotation: "1/3", |
|
| 361 |
+ allocator.MCSAnnotation: "s0:c1,c0", |
|
| 362 |
+ allocator.SupplementalGroupsAnnotation: "1/3", |
|
| 362 | 363 |
}, |
| 363 | 364 |
}, |
| 364 | 365 |
} |
| ... | ... |
@@ -366,7 +517,8 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 366 | 366 |
ObjectMeta: kapi.ObjectMeta{
|
| 367 | 367 |
Name: "default", |
| 368 | 368 |
Annotations: map[string]string{
|
| 369 |
- allocator.MCSAnnotation: "s0:c1,c0", |
|
| 369 |
+ allocator.MCSAnnotation: "s0:c1,c0", |
|
| 370 |
+ allocator.SupplementalGroupsAnnotation: "1/3", |
|
| 370 | 371 |
}, |
| 371 | 372 |
}, |
| 372 | 373 |
} |
| ... | ... |
@@ -374,7 +526,29 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 374 | 374 |
ObjectMeta: kapi.ObjectMeta{
|
| 375 | 375 |
Name: "default", |
| 376 | 376 |
Annotations: map[string]string{
|
| 377 |
+ allocator.UIDRangeAnnotation: "1/3", |
|
| 378 |
+ allocator.SupplementalGroupsAnnotation: "1/3", |
|
| 379 |
+ }, |
|
| 380 |
+ }, |
|
| 381 |
+ } |
|
| 382 |
+ |
|
| 383 |
+ namespaceNoSupplementalGroupsFallbackToUID := &kapi.Namespace{
|
|
| 384 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 385 |
+ Name: "default", |
|
| 386 |
+ Annotations: map[string]string{
|
|
| 377 | 387 |
allocator.UIDRangeAnnotation: "1/3", |
| 388 |
+ allocator.MCSAnnotation: "s0:c1,c0", |
|
| 389 |
+ }, |
|
| 390 |
+ }, |
|
| 391 |
+ } |
|
| 392 |
+ |
|
| 393 |
+ namespaceBadSupGroups := &kapi.Namespace{
|
|
| 394 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 395 |
+ Name: "default", |
|
| 396 |
+ Annotations: map[string]string{
|
|
| 397 |
+ allocator.UIDRangeAnnotation: "1/3", |
|
| 398 |
+ allocator.MCSAnnotation: "s0:c1,c0", |
|
| 399 |
+ allocator.SupplementalGroupsAnnotation: "", |
|
| 378 | 400 |
}, |
| 379 | 401 |
}, |
| 380 | 402 |
} |
| ... | ... |
@@ -397,6 +571,12 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 397 | 397 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 398 | 398 |
Type: kapi.RunAsUserStrategyRunAsAny, |
| 399 | 399 |
}, |
| 400 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 401 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 402 |
+ }, |
|
| 403 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 404 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 405 |
+ }, |
|
| 400 | 406 |
} |
| 401 | 407 |
}, |
| 402 | 408 |
namespace: namespaceValid, |
| ... | ... |
@@ -414,6 +594,12 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 414 | 414 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 415 | 415 |
Type: kapi.RunAsUserStrategyMustRunAsRange, |
| 416 | 416 |
}, |
| 417 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 418 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 419 |
+ }, |
|
| 420 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 421 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 422 |
+ }, |
|
| 417 | 423 |
} |
| 418 | 424 |
}, |
| 419 | 425 |
namespace: namespaceValid, |
| ... | ... |
@@ -430,6 +616,12 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 430 | 430 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 431 | 431 |
Type: kapi.RunAsUserStrategyMustRunAsRange, |
| 432 | 432 |
}, |
| 433 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 434 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 435 |
+ }, |
|
| 436 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 437 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 438 |
+ }, |
|
| 433 | 439 |
} |
| 434 | 440 |
}, |
| 435 | 441 |
namespace: namespaceNoUID, |
| ... | ... |
@@ -447,11 +639,62 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 447 | 447 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 448 | 448 |
Type: kapi.RunAsUserStrategyMustRunAsRange, |
| 449 | 449 |
}, |
| 450 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 451 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 452 |
+ }, |
|
| 453 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 454 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 455 |
+ }, |
|
| 450 | 456 |
} |
| 451 | 457 |
}, |
| 452 | 458 |
namespace: namespaceNoMCS, |
| 453 | 459 |
expectedErr: "unable to find pre-allocated mcs annotation", |
| 454 | 460 |
}, |
| 461 |
+ "pre-allocated group falls back to UID annotation": {
|
|
| 462 |
+ scc: func() *kapi.SecurityContextConstraints {
|
|
| 463 |
+ return &kapi.SecurityContextConstraints{
|
|
| 464 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 465 |
+ Name: "pre-allocated no sup group annotation", |
|
| 466 |
+ }, |
|
| 467 |
+ SELinuxContext: kapi.SELinuxContextStrategyOptions{
|
|
| 468 |
+ Type: kapi.SELinuxStrategyRunAsAny, |
|
| 469 |
+ }, |
|
| 470 |
+ RunAsUser: kapi.RunAsUserStrategyOptions{
|
|
| 471 |
+ Type: kapi.RunAsUserStrategyRunAsAny, |
|
| 472 |
+ }, |
|
| 473 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 474 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 475 |
+ }, |
|
| 476 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 477 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 478 |
+ }, |
|
| 479 |
+ } |
|
| 480 |
+ }, |
|
| 481 |
+ namespace: namespaceNoSupplementalGroupsFallbackToUID, |
|
| 482 |
+ }, |
|
| 483 |
+ "pre-allocated group bad value fails": {
|
|
| 484 |
+ scc: func() *kapi.SecurityContextConstraints {
|
|
| 485 |
+ return &kapi.SecurityContextConstraints{
|
|
| 486 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 487 |
+ Name: "pre-allocated no sup group annotation", |
|
| 488 |
+ }, |
|
| 489 |
+ SELinuxContext: kapi.SELinuxContextStrategyOptions{
|
|
| 490 |
+ Type: kapi.SELinuxStrategyRunAsAny, |
|
| 491 |
+ }, |
|
| 492 |
+ RunAsUser: kapi.RunAsUserStrategyOptions{
|
|
| 493 |
+ Type: kapi.RunAsUserStrategyRunAsAny, |
|
| 494 |
+ }, |
|
| 495 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 496 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 497 |
+ }, |
|
| 498 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 499 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 500 |
+ }, |
|
| 501 |
+ } |
|
| 502 |
+ }, |
|
| 503 |
+ namespace: namespaceBadSupGroups, |
|
| 504 |
+ expectedErr: "unable to find pre-allocated group annotation", |
|
| 505 |
+ }, |
|
| 455 | 506 |
"bad scc strategy options": {
|
| 456 | 507 |
scc: func() *kapi.SecurityContextConstraints {
|
| 457 | 508 |
return &kapi.SecurityContextConstraints{
|
| ... | ... |
@@ -464,6 +707,12 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
|
| 464 | 464 |
RunAsUser: kapi.RunAsUserStrategyOptions{
|
| 465 | 465 |
Type: kapi.RunAsUserStrategyMustRunAs, |
| 466 | 466 |
}, |
| 467 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 468 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 469 |
+ }, |
|
| 470 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 471 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 472 |
+ }, |
|
| 467 | 473 |
} |
| 468 | 474 |
}, |
| 469 | 475 |
namespace: namespaceValid, |
| ... | ... |
@@ -722,3 +971,315 @@ func TestDeduplicateSecurityContextConstraints(t *testing.T) {
|
| 722 | 722 |
} |
| 723 | 723 |
|
| 724 | 724 |
} |
| 725 |
+ |
|
| 726 |
+func TestRequiresPreallocatedSupplementalGroups(t *testing.T) {
|
|
| 727 |
+ testCases := map[string]struct {
|
|
| 728 |
+ scc *kapi.SecurityContextConstraints |
|
| 729 |
+ requires bool |
|
| 730 |
+ }{
|
|
| 731 |
+ "must run as": {
|
|
| 732 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 733 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 734 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 735 |
+ }, |
|
| 736 |
+ }, |
|
| 737 |
+ requires: true, |
|
| 738 |
+ }, |
|
| 739 |
+ "must with range specified": {
|
|
| 740 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 741 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 742 |
+ Type: kapi.SupplementalGroupsStrategyMustRunAs, |
|
| 743 |
+ Ranges: []kapi.IDRange{
|
|
| 744 |
+ {Min: 1, Max: 1},
|
|
| 745 |
+ }, |
|
| 746 |
+ }, |
|
| 747 |
+ }, |
|
| 748 |
+ }, |
|
| 749 |
+ "run as any": {
|
|
| 750 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 751 |
+ SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{
|
|
| 752 |
+ Type: kapi.SupplementalGroupsStrategyRunAsAny, |
|
| 753 |
+ }, |
|
| 754 |
+ }, |
|
| 755 |
+ }, |
|
| 756 |
+ } |
|
| 757 |
+ for k, v := range testCases {
|
|
| 758 |
+ result := requiresPreallocatedSupplementalGroups(v.scc) |
|
| 759 |
+ if result != v.requires {
|
|
| 760 |
+ t.Errorf("%s expected result %t but got %t", k, v.requires, result)
|
|
| 761 |
+ } |
|
| 762 |
+ } |
|
| 763 |
+} |
|
| 764 |
+ |
|
| 765 |
+func TestRequiresPreallocatedFSGroup(t *testing.T) {
|
|
| 766 |
+ testCases := map[string]struct {
|
|
| 767 |
+ scc *kapi.SecurityContextConstraints |
|
| 768 |
+ requires bool |
|
| 769 |
+ }{
|
|
| 770 |
+ "must run as": {
|
|
| 771 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 772 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 773 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 774 |
+ }, |
|
| 775 |
+ }, |
|
| 776 |
+ requires: true, |
|
| 777 |
+ }, |
|
| 778 |
+ "must with range specified": {
|
|
| 779 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 780 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 781 |
+ Type: kapi.FSGroupStrategyMustRunAs, |
|
| 782 |
+ Ranges: []kapi.IDRange{
|
|
| 783 |
+ {Min: 1, Max: 1},
|
|
| 784 |
+ }, |
|
| 785 |
+ }, |
|
| 786 |
+ }, |
|
| 787 |
+ }, |
|
| 788 |
+ "run as any": {
|
|
| 789 |
+ scc: &kapi.SecurityContextConstraints{
|
|
| 790 |
+ FSGroup: kapi.FSGroupStrategyOptions{
|
|
| 791 |
+ Type: kapi.FSGroupStrategyRunAsAny, |
|
| 792 |
+ }, |
|
| 793 |
+ }, |
|
| 794 |
+ }, |
|
| 795 |
+ } |
|
| 796 |
+ for k, v := range testCases {
|
|
| 797 |
+ result := requiresPreallocatedFSGroup(v.scc) |
|
| 798 |
+ if result != v.requires {
|
|
| 799 |
+ t.Errorf("%s expected result %t but got %t", k, v.requires, result)
|
|
| 800 |
+ } |
|
| 801 |
+ } |
|
| 802 |
+} |
|
| 803 |
+ |
|
| 804 |
+func TestParseSupplementalGroupAnnotation(t *testing.T) {
|
|
| 805 |
+ tests := map[string]struct {
|
|
| 806 |
+ groups string |
|
| 807 |
+ expected []uid.Block |
|
| 808 |
+ shouldFail bool |
|
| 809 |
+ }{
|
|
| 810 |
+ "single block slash": {
|
|
| 811 |
+ groups: "1/5", |
|
| 812 |
+ expected: []uid.Block{
|
|
| 813 |
+ {Start: 1, End: 5},
|
|
| 814 |
+ }, |
|
| 815 |
+ }, |
|
| 816 |
+ "single block dash": {
|
|
| 817 |
+ groups: "1-5", |
|
| 818 |
+ expected: []uid.Block{
|
|
| 819 |
+ {Start: 1, End: 5},
|
|
| 820 |
+ }, |
|
| 821 |
+ }, |
|
| 822 |
+ "multiple blocks": {
|
|
| 823 |
+ groups: "1/5,6/5,11/5", |
|
| 824 |
+ expected: []uid.Block{
|
|
| 825 |
+ {Start: 1, End: 5},
|
|
| 826 |
+ {Start: 6, End: 10},
|
|
| 827 |
+ {Start: 11, End: 15},
|
|
| 828 |
+ }, |
|
| 829 |
+ }, |
|
| 830 |
+ "dash format": {
|
|
| 831 |
+ groups: "1-5,6-10,11-15", |
|
| 832 |
+ expected: []uid.Block{
|
|
| 833 |
+ {Start: 1, End: 5},
|
|
| 834 |
+ {Start: 6, End: 10},
|
|
| 835 |
+ {Start: 11, End: 15},
|
|
| 836 |
+ }, |
|
| 837 |
+ }, |
|
| 838 |
+ "no blocks": {
|
|
| 839 |
+ groups: "", |
|
| 840 |
+ shouldFail: true, |
|
| 841 |
+ }, |
|
| 842 |
+ } |
|
| 843 |
+ for k, v := range tests {
|
|
| 844 |
+ blocks, err := parseSupplementalGroupAnnotation(v.groups) |
|
| 845 |
+ |
|
| 846 |
+ if v.shouldFail && err == nil {
|
|
| 847 |
+ t.Errorf("%s was expected to fail but received no error and blocks %v", k, blocks)
|
|
| 848 |
+ continue |
|
| 849 |
+ } |
|
| 850 |
+ |
|
| 851 |
+ if !v.shouldFail && err != nil {
|
|
| 852 |
+ t.Errorf("%s had an unexpected error %v", k, err)
|
|
| 853 |
+ continue |
|
| 854 |
+ } |
|
| 855 |
+ |
|
| 856 |
+ if len(blocks) != len(v.expected) {
|
|
| 857 |
+ t.Errorf("%s received unexpected number of blocks expected: %v, actual %v", k, v.expected, blocks)
|
|
| 858 |
+ } |
|
| 859 |
+ |
|
| 860 |
+ for _, b := range v.expected {
|
|
| 861 |
+ if !hasBlock(b, blocks) {
|
|
| 862 |
+ t.Errorf("%s was missing block %v", k, b)
|
|
| 863 |
+ } |
|
| 864 |
+ } |
|
| 865 |
+ } |
|
| 866 |
+} |
|
| 867 |
+ |
|
| 868 |
+func hasBlock(block uid.Block, blocks []uid.Block) bool {
|
|
| 869 |
+ for _, b := range blocks {
|
|
| 870 |
+ if b.Start == block.Start && b.End == block.End {
|
|
| 871 |
+ return true |
|
| 872 |
+ } |
|
| 873 |
+ } |
|
| 874 |
+ return false |
|
| 875 |
+} |
|
| 876 |
+ |
|
| 877 |
+func TestGetPreallocatedFSGroup(t *testing.T) {
|
|
| 878 |
+ ns := func() *kapi.Namespace {
|
|
| 879 |
+ return &kapi.Namespace{
|
|
| 880 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 881 |
+ Annotations: map[string]string{},
|
|
| 882 |
+ }, |
|
| 883 |
+ } |
|
| 884 |
+ } |
|
| 885 |
+ |
|
| 886 |
+ fallbackNS := ns() |
|
| 887 |
+ fallbackNS.Annotations[allocator.UIDRangeAnnotation] = "1/5" |
|
| 888 |
+ |
|
| 889 |
+ emptyAnnotationNS := ns() |
|
| 890 |
+ emptyAnnotationNS.Annotations[allocator.SupplementalGroupsAnnotation] = "" |
|
| 891 |
+ |
|
| 892 |
+ badBlockNS := ns() |
|
| 893 |
+ badBlockNS.Annotations[allocator.SupplementalGroupsAnnotation] = "foo" |
|
| 894 |
+ |
|
| 895 |
+ goodNS := ns() |
|
| 896 |
+ goodNS.Annotations[allocator.SupplementalGroupsAnnotation] = "1/5" |
|
| 897 |
+ |
|
| 898 |
+ tests := map[string]struct {
|
|
| 899 |
+ ns *kapi.Namespace |
|
| 900 |
+ expected []kapi.IDRange |
|
| 901 |
+ shouldFail bool |
|
| 902 |
+ }{
|
|
| 903 |
+ "fall back to uid if sup group doesn't exist": {
|
|
| 904 |
+ ns: fallbackNS, |
|
| 905 |
+ expected: []kapi.IDRange{
|
|
| 906 |
+ {Min: 1, Max: 1},
|
|
| 907 |
+ }, |
|
| 908 |
+ }, |
|
| 909 |
+ "no annotation": {
|
|
| 910 |
+ ns: ns(), |
|
| 911 |
+ shouldFail: true, |
|
| 912 |
+ }, |
|
| 913 |
+ "empty annotation": {
|
|
| 914 |
+ ns: emptyAnnotationNS, |
|
| 915 |
+ shouldFail: true, |
|
| 916 |
+ }, |
|
| 917 |
+ "bad block": {
|
|
| 918 |
+ ns: badBlockNS, |
|
| 919 |
+ shouldFail: true, |
|
| 920 |
+ }, |
|
| 921 |
+ "good sup group annotation": {
|
|
| 922 |
+ ns: goodNS, |
|
| 923 |
+ expected: []kapi.IDRange{
|
|
| 924 |
+ {Min: 1, Max: 1},
|
|
| 925 |
+ }, |
|
| 926 |
+ }, |
|
| 927 |
+ } |
|
| 928 |
+ |
|
| 929 |
+ for k, v := range tests {
|
|
| 930 |
+ ranges, err := getPreallocatedFSGroup(v.ns) |
|
| 931 |
+ if v.shouldFail && err == nil {
|
|
| 932 |
+ t.Errorf("%s was expected to fail but received no error and ranges %v", k, ranges)
|
|
| 933 |
+ continue |
|
| 934 |
+ } |
|
| 935 |
+ |
|
| 936 |
+ if !v.shouldFail && err != nil {
|
|
| 937 |
+ t.Errorf("%s had an unexpected error %v", k, err)
|
|
| 938 |
+ continue |
|
| 939 |
+ } |
|
| 940 |
+ |
|
| 941 |
+ if len(ranges) != len(v.expected) {
|
|
| 942 |
+ t.Errorf("%s received unexpected number of ranges expected: %v, actual %v", k, v.expected, ranges)
|
|
| 943 |
+ } |
|
| 944 |
+ |
|
| 945 |
+ for _, r := range v.expected {
|
|
| 946 |
+ if !hasRange(r, ranges) {
|
|
| 947 |
+ t.Errorf("%s was missing range %v", k, r)
|
|
| 948 |
+ } |
|
| 949 |
+ } |
|
| 950 |
+ } |
|
| 951 |
+} |
|
| 952 |
+ |
|
| 953 |
+func TestGetPreallocatedSupplementalGroups(t *testing.T) {
|
|
| 954 |
+ ns := func() *kapi.Namespace {
|
|
| 955 |
+ return &kapi.Namespace{
|
|
| 956 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 957 |
+ Annotations: map[string]string{},
|
|
| 958 |
+ }, |
|
| 959 |
+ } |
|
| 960 |
+ } |
|
| 961 |
+ |
|
| 962 |
+ fallbackNS := ns() |
|
| 963 |
+ fallbackNS.Annotations[allocator.UIDRangeAnnotation] = "1/5" |
|
| 964 |
+ |
|
| 965 |
+ emptyAnnotationNS := ns() |
|
| 966 |
+ emptyAnnotationNS.Annotations[allocator.SupplementalGroupsAnnotation] = "" |
|
| 967 |
+ |
|
| 968 |
+ badBlockNS := ns() |
|
| 969 |
+ badBlockNS.Annotations[allocator.SupplementalGroupsAnnotation] = "foo" |
|
| 970 |
+ |
|
| 971 |
+ goodNS := ns() |
|
| 972 |
+ goodNS.Annotations[allocator.SupplementalGroupsAnnotation] = "1/5" |
|
| 973 |
+ |
|
| 974 |
+ tests := map[string]struct {
|
|
| 975 |
+ ns *kapi.Namespace |
|
| 976 |
+ expected []kapi.IDRange |
|
| 977 |
+ shouldFail bool |
|
| 978 |
+ }{
|
|
| 979 |
+ "fall back to uid if sup group doesn't exist": {
|
|
| 980 |
+ ns: fallbackNS, |
|
| 981 |
+ expected: []kapi.IDRange{
|
|
| 982 |
+ {Min: 1, Max: 5},
|
|
| 983 |
+ }, |
|
| 984 |
+ }, |
|
| 985 |
+ "no annotation": {
|
|
| 986 |
+ ns: ns(), |
|
| 987 |
+ shouldFail: true, |
|
| 988 |
+ }, |
|
| 989 |
+ "empty annotation": {
|
|
| 990 |
+ ns: emptyAnnotationNS, |
|
| 991 |
+ shouldFail: true, |
|
| 992 |
+ }, |
|
| 993 |
+ "bad block": {
|
|
| 994 |
+ ns: badBlockNS, |
|
| 995 |
+ shouldFail: true, |
|
| 996 |
+ }, |
|
| 997 |
+ "good sup group annotation": {
|
|
| 998 |
+ ns: goodNS, |
|
| 999 |
+ expected: []kapi.IDRange{
|
|
| 1000 |
+ {Min: 1, Max: 5},
|
|
| 1001 |
+ }, |
|
| 1002 |
+ }, |
|
| 1003 |
+ } |
|
| 1004 |
+ |
|
| 1005 |
+ for k, v := range tests {
|
|
| 1006 |
+ ranges, err := getPreallocatedSupplementalGroups(v.ns) |
|
| 1007 |
+ if v.shouldFail && err == nil {
|
|
| 1008 |
+ t.Errorf("%s was expected to fail but received no error and ranges %v", k, ranges)
|
|
| 1009 |
+ continue |
|
| 1010 |
+ } |
|
| 1011 |
+ |
|
| 1012 |
+ if !v.shouldFail && err != nil {
|
|
| 1013 |
+ t.Errorf("%s had an unexpected error %v", k, err)
|
|
| 1014 |
+ continue |
|
| 1015 |
+ } |
|
| 1016 |
+ |
|
| 1017 |
+ if len(ranges) != len(v.expected) {
|
|
| 1018 |
+ t.Errorf("%s received unexpected number of ranges expected: %v, actual %v", k, v.expected, ranges)
|
|
| 1019 |
+ } |
|
| 1020 |
+ |
|
| 1021 |
+ for _, r := range v.expected {
|
|
| 1022 |
+ if !hasRange(r, ranges) {
|
|
| 1023 |
+ t.Errorf("%s was missing range %v", k, r)
|
|
| 1024 |
+ } |
|
| 1025 |
+ } |
|
| 1026 |
+ } |
|
| 1027 |
+} |
|
| 1028 |
+ |
|
| 1029 |
+func hasRange(rng kapi.IDRange, ranges []kapi.IDRange) bool {
|
|
| 1030 |
+ for _, r := range ranges {
|
|
| 1031 |
+ if r.Min == rng.Min && r.Max == rng.Max {
|
|
| 1032 |
+ return true |
|
| 1033 |
+ } |
|
| 1034 |
+ } |
|
| 1035 |
+ return false |
|
| 1036 |
+} |
| ... | ... |
@@ -3,8 +3,7 @@ package security |
| 3 | 3 |
const ( |
| 4 | 4 |
UIDRangeAnnotation = "openshift.io/sa.scc.uid-range" |
| 5 | 5 |
// SupplementalGroupsAnnotation contains a comma delimited list of allocated supplemental groups |
| 6 |
- // for the namespace. Groups are in the form of individual group ids or a range of group ids. |
|
| 7 |
- // Range format is supported in the form of a Block which supports {start}/{length} or {start}-{end}
|
|
| 6 |
+ // for the namespace. Groups are in the form of a Block which supports {start}/{length} or {start}-{end}
|
|
| 8 | 7 |
SupplementalGroupsAnnotation = "openshift.io/sa.scc.supplemental-groups" |
| 9 | 8 |
MCSAnnotation = "openshift.io/sa.scc.mcs" |
| 10 | 9 |
ValidatedSCCAnnotation = "openshift.io/scc" |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
_ "github.com/openshift/origin/test/extended/cli" |
| 8 | 8 |
_ "github.com/openshift/origin/test/extended/images" |
| 9 | 9 |
_ "github.com/openshift/origin/test/extended/router" |
| 10 |
+ _ "github.com/openshift/origin/test/extended/security" |
|
| 10 | 11 |
|
| 11 | 12 |
exutil "github.com/openshift/origin/test/extended/util" |
| 12 | 13 |
) |
| 13 | 14 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,182 @@ |
| 0 |
+package security |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strconv" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/fsouza/go-dockerclient" |
|
| 8 |
+ g "github.com/onsi/ginkgo" |
|
| 9 |
+ o "github.com/onsi/gomega" |
|
| 10 |
+ |
|
| 11 |
+ kapi "k8s.io/kubernetes/pkg/api" |
|
| 12 |
+ "k8s.io/kubernetes/test/e2e" |
|
| 13 |
+ |
|
| 14 |
+ testutil "github.com/openshift/origin/test/util" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+var _ = g.Describe("security: supplemental groups", func() {
|
|
| 18 |
+ defer g.GinkgoRecover() |
|
| 19 |
+ |
|
| 20 |
+ var ( |
|
| 21 |
+ f = e2e.NewFramework("security-supgroups")
|
|
| 22 |
+ ) |
|
| 23 |
+ |
|
| 24 |
+ g.Describe("Ensure supplemental groups propagate to docker", func() {
|
|
| 25 |
+ g.It("should propagate requested groups to the docker host config", func() {
|
|
| 26 |
+ // Before running any of this test we need to first check that |
|
| 27 |
+ // the docker version being used supports the supplemental groups feature |
|
| 28 |
+ g.By("ensuring the feature is supported")
|
|
| 29 |
+ dockerCli, err := testutil.NewDockerClient() |
|
| 30 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 31 |
+ |
|
| 32 |
+ env, err := dockerCli.Version() |
|
| 33 |
+ o.Expect(err).NotTo(o.HaveOccurred(), "error getting docker environment") |
|
| 34 |
+ version := env.Get("Version")
|
|
| 35 |
+ supports, err, requiredVersion := supportsSupplementalGroups(version) |
|
| 36 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 37 |
+ |
|
| 38 |
+ if !supports {
|
|
| 39 |
+ msg := fmt.Sprintf("skipping supplemental groups test, docker version %s does not meet required version %s", version, requiredVersion)
|
|
| 40 |
+ g.Skip(msg) |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ // on to the real test |
|
| 44 |
+ fsGroup := int64(1111) |
|
| 45 |
+ supGroup := int64(2222) |
|
| 46 |
+ |
|
| 47 |
+ // create a pod that is requesting supplemental groups. We request specific sup groups |
|
| 48 |
+ // so that we can check for the exact values later and not rely on SCC allocation. |
|
| 49 |
+ g.By("creating a pod that requests supplemental groups")
|
|
| 50 |
+ submittedPod := supGroupPod(fsGroup, supGroup) |
|
| 51 |
+ _, err = f.Client.Pods(f.Namespace.Name).Create(submittedPod) |
|
| 52 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 53 |
+ defer f.Client.Pods(f.Namespace.Name).Delete(submittedPod.Name, nil) |
|
| 54 |
+ |
|
| 55 |
+ // we should have been admitted with the groups that we requested but if for any |
|
| 56 |
+ // reason they are different we will fail. |
|
| 57 |
+ g.By("retrieving the pod and ensuring groups are set")
|
|
| 58 |
+ retrievedPod, err := f.Client.Pods(f.Namespace.Name).Get(submittedPod.Name) |
|
| 59 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 60 |
+ o.Expect(*retrievedPod.Spec.SecurityContext.FSGroup).To(o.Equal(*submittedPod.Spec.SecurityContext.FSGroup)) |
|
| 61 |
+ o.Expect(retrievedPod.Spec.SecurityContext.SupplementalGroups).To(o.Equal(submittedPod.Spec.SecurityContext.SupplementalGroups)) |
|
| 62 |
+ |
|
| 63 |
+ // wait for the pod to run so we can inspect it. |
|
| 64 |
+ g.By("waiting for the pod to become running")
|
|
| 65 |
+ err = f.WaitForPodRunning(submittedPod.Name) |
|
| 66 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 67 |
+ |
|
| 68 |
+ // find the docker id of our running container. |
|
| 69 |
+ g.By("finding the docker container id on the pod")
|
|
| 70 |
+ retrievedPod, err = f.Client.Pods(f.Namespace.Name).Get(submittedPod.Name) |
|
| 71 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 72 |
+ containerID, err := getContainerID(retrievedPod) |
|
| 73 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 74 |
+ |
|
| 75 |
+ // now check the host config of the container which should have been updated by the |
|
| 76 |
+ // kubelet. If that is good then ensure we have the groups we expected. |
|
| 77 |
+ g.By("inspecting the container")
|
|
| 78 |
+ dockerContainer, err := dockerCli.InspectContainer(containerID) |
|
| 79 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 80 |
+ |
|
| 81 |
+ g.By("ensuring the host config has GroupAdd")
|
|
| 82 |
+ groupAdd := dockerContainer.HostConfig.GroupAdd |
|
| 83 |
+ o.Expect(groupAdd).ToNot(o.BeEmpty(), fmt.Sprintf("groupAdd on host config was %v", groupAdd))
|
|
| 84 |
+ |
|
| 85 |
+ g.By("ensuring the groups are set")
|
|
| 86 |
+ o.Expect(configHasGroup(fsGroup, dockerContainer.HostConfig)).To(o.Equal(true), fmt.Sprintf("fsGroup should exist on host config: %v", groupAdd))
|
|
| 87 |
+ o.Expect(configHasGroup(supGroup, dockerContainer.HostConfig)).To(o.Equal(true), fmt.Sprintf("supGroup should exist on host config: %v", groupAdd))
|
|
| 88 |
+ }) |
|
| 89 |
+ |
|
| 90 |
+ }) |
|
| 91 |
+}) |
|
| 92 |
+ |
|
| 93 |
+// supportsSupplementalGroups does a check on the docker version to ensure it is at least |
|
| 94 |
+// 1.8.2. This could still fail if the version does not have the /etc/groups patch |
|
| 95 |
+// but it will fail when launching the pod so this is as safe as we can get. |
|
| 96 |
+func supportsSupplementalGroups(dockerVersion string) (bool, error, string) {
|
|
| 97 |
+ parts := strings.Split(dockerVersion, ".") |
|
| 98 |
+ |
|
| 99 |
+ var ( |
|
| 100 |
+ requiredMajor = 1 |
|
| 101 |
+ requiredMinor = 8 |
|
| 102 |
+ requiredPatch = 2 |
|
| 103 |
+ requiredVersion = fmt.Sprintf("%d.%d.%d", requiredMajor, requiredMinor, requiredPatch)
|
|
| 104 |
+ |
|
| 105 |
+ major = 0 |
|
| 106 |
+ minor = 0 |
|
| 107 |
+ patch = 0 |
|
| 108 |
+ err error = nil |
|
| 109 |
+ ) |
|
| 110 |
+ if len(parts) > 0 {
|
|
| 111 |
+ major, err = strconv.Atoi(parts[0]) |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ return false, err, requiredVersion |
|
| 114 |
+ } |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ if len(parts) > 1 {
|
|
| 118 |
+ minor, err = strconv.Atoi(parts[1]) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ return false, err, requiredVersion |
|
| 121 |
+ } |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ if len(parts) > 2 {
|
|
| 125 |
+ patch, err = strconv.Atoi(parts[2]) |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ return false, err, requiredVersion |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ // requires at least 1.8.2 |
|
| 132 |
+ if major > requiredMajor || (major == requiredMajor && minor > requiredMinor) || |
|
| 133 |
+ (major == requiredMajor && minor == requiredMinor && patch >= requiredPatch) {
|
|
| 134 |
+ return true, nil, requiredVersion |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ return false, nil, requiredVersion |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+// configHasGroup is a helper to ensure that a group is in the host config's addGroups field. |
|
| 141 |
+func configHasGroup(group int64, config *docker.HostConfig) bool {
|
|
| 142 |
+ strGroup := strconv.FormatInt(group, 10) |
|
| 143 |
+ for _, g := range config.GroupAdd {
|
|
| 144 |
+ if g == strGroup {
|
|
| 145 |
+ return true |
|
| 146 |
+ } |
|
| 147 |
+ } |
|
| 148 |
+ return false |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+// getContainerID is a helper to parse the docker container id from a status. |
|
| 152 |
+func getContainerID(p *kapi.Pod) (string, error) {
|
|
| 153 |
+ for _, status := range p.Status.ContainerStatuses {
|
|
| 154 |
+ if len(status.ContainerID) > 0 {
|
|
| 155 |
+ containerID := strings.Replace(status.ContainerID, "docker://", "", -1) |
|
| 156 |
+ return containerID, nil |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ return "", fmt.Errorf("unable to find container id on pod")
|
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+// supGroupPod generates the pod requesting supplemental groups. |
|
| 163 |
+func supGroupPod(fsGroup int64, supGroup int64) *kapi.Pod {
|
|
| 164 |
+ return &kapi.Pod{
|
|
| 165 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 166 |
+ Name: "supplemental-groups", |
|
| 167 |
+ }, |
|
| 168 |
+ Spec: kapi.PodSpec{
|
|
| 169 |
+ SecurityContext: &kapi.PodSecurityContext{
|
|
| 170 |
+ FSGroup: &fsGroup, |
|
| 171 |
+ SupplementalGroups: []int64{supGroup},
|
|
| 172 |
+ }, |
|
| 173 |
+ Containers: []kapi.Container{
|
|
| 174 |
+ {
|
|
| 175 |
+ Name: "supplemental-groups", |
|
| 176 |
+ Image: "openshift/origin-pod", |
|
| 177 |
+ }, |
|
| 178 |
+ }, |
|
| 179 |
+ }, |
|
| 180 |
+ } |
|
| 181 |
+} |