Merge pull request #52849 from liggitt/psp-defaulting-order

Automatic merge from submit-queue (batch tested with PRs 48665, 52849, 54006, 53755). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Order PSP by name, prefer non-mutating PSPs

Fixes #36184
Fixes #23217
Related to #23217

Removes unnecessary mutation of pods:
* Determines effective security context for pods using a wrapper containing the pod and container security context, rather than building/setting a combined struct on every admission
* Does not set `privileged:&false` on security contexts with `privileged:nil`
* Does not set `runAsNonRoot:&true` on security contexts that already have a non-nil, non-0 `runAsUser`
* Does not mutate/normalize container capabilities unless changes are required (missing  defaultAddCapabilities or requiredDropCapabilities)

Defines behavior when multiple PSP objects allow a pod:
* PSPs which allow the pod as-is (no defaulting/mutating) are preferred
* If the pod must be defaulted/mutated to be allowed, the first PSP (ordered by name) to allow the pod is selected
* During update operations, when mutations to pod specs are disallowed, only non-mutating PSPs are used to validate the pod

```release-note
PodSecurityPolicy: when multiple policies allow a submitted pod, priority is given to ones which do not require any fields in the pod spec to be defaulted. If the pod must be defaulted, the first policy (ordered by name) that allows the pod is used.
```
This commit is contained in:
Kubernetes Submit Queue 2017-10-16 18:09:41 -07:00 committed by GitHub
commit 97808e5a86
29 changed files with 1927 additions and 543 deletions

View File

@ -17,6 +17,8 @@ limitations under the License.
package rbac package rbac
import ( import (
"reflect"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -25,6 +27,10 @@ import (
// IsOnlyMutatingGCFields checks finalizers and ownerrefs which GC manipulates // IsOnlyMutatingGCFields checks finalizers and ownerrefs which GC manipulates
// and indicates that only those fields are changing // and indicates that only those fields are changing
func IsOnlyMutatingGCFields(obj, old runtime.Object, equalities conversion.Equalities) bool { func IsOnlyMutatingGCFields(obj, old runtime.Object, equalities conversion.Equalities) bool {
if old == nil || reflect.ValueOf(old).IsNil() {
return false
}
// make a copy of the newObj so that we can stomp for comparison // make a copy of the newObj so that we can stomp for comparison
copied := obj.DeepCopyObject() copied := obj.DeepCopyObject()
copiedMeta, err := meta.Accessor(copied) copiedMeta, err := meta.Accessor(copied)

View File

@ -128,6 +128,19 @@ func TestIsOnlyMutatingGCFields(t *testing.T) {
}, },
expected: false, expected: false,
}, },
{
name: "and nil",
obj: func() runtime.Object {
obj := newPod()
obj.OwnerReferences = append(obj.OwnerReferences, metav1.OwnerReference{Name: "foo"})
obj.Spec.RestartPolicy = kapi.RestartPolicyAlways
return obj
},
old: func() runtime.Object {
return (*kapi.Pod)(nil)
},
expected: false,
},
} }
for _, tc := range tests { for _, tc := range tests {

View File

@ -26,6 +26,7 @@ go_library(
"//pkg/security/podsecuritypolicy/sysctl:go_default_library", "//pkg/security/podsecuritypolicy/sysctl:go_default_library",
"//pkg/security/podsecuritypolicy/user:go_default_library", "//pkg/security/podsecuritypolicy/user:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/maps:go_default_library", "//pkg/util/maps:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",

View File

@ -48,13 +48,17 @@ func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, al
// 1. a capabilities.Add set containing all the required adds (unless the // 1. a capabilities.Add set containing all the required adds (unless the
// container specifically is dropping the cap) and container requested adds // container specifically is dropping the cap) and container requested adds
// 2. a capabilities.Drop set containing all the required drops and container requested drops // 2. a capabilities.Drop set containing all the required drops and container requested drops
//
// Returns the original container capabilities if no changes are required.
func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) { func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) {
defaultAdd := makeCapSet(s.defaultAddCapabilities) defaultAdd := makeCapSet(s.defaultAddCapabilities)
requiredDrop := makeCapSet(s.requiredDropCapabilities) requiredDrop := makeCapSet(s.requiredDropCapabilities)
containerAdd := sets.NewString() containerAdd := sets.NewString()
containerDrop := sets.NewString() containerDrop := sets.NewString()
var containerCapabilities *api.Capabilities
if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil {
containerCapabilities = container.SecurityContext.Capabilities
containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add)
containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop)
} }
@ -62,32 +66,25 @@ func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (
// remove any default adds that the container is specifically dropping // remove any default adds that the container is specifically dropping
defaultAdd = defaultAdd.Difference(containerDrop) defaultAdd = defaultAdd.Difference(containerDrop)
combinedAdd := defaultAdd.Union(containerAdd).List() combinedAdd := defaultAdd.Union(containerAdd)
combinedDrop := requiredDrop.Union(containerDrop).List() combinedDrop := requiredDrop.Union(containerDrop)
// nothing generated? return nil // no changes? return the original capabilities
if len(combinedAdd) == 0 && len(combinedDrop) == 0 { if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) {
return nil, nil return containerCapabilities, nil
} }
return &api.Capabilities{ return &api.Capabilities{
Add: capabilityFromStringSlice(combinedAdd), Add: capabilityFromStringSlice(combinedAdd.List()),
Drop: capabilityFromStringSlice(combinedDrop), Drop: capabilityFromStringSlice(combinedDrop.List()),
}, nil }, nil
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
// if the security context isn't set then we haven't generated correctly. Shouldn't get here if capabilities == nil {
// if using the provider correctly
if container.SecurityContext == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("securityContext"), container.SecurityContext, "no security context is set"))
return allErrs
}
if container.SecurityContext.Capabilities == nil {
// if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author // if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author
// if there are no required caps on the strategy and nothing is requested on the pod // if there are no required caps on the strategy and nothing is requested on the pod
// then we can safely return here without further validation. // then we can safely return here without further validation.
@ -97,7 +94,7 @@ func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container) f
// container has no requested caps but we have required caps. We should have something in // container has no requested caps but we have required caps. We should have something in
// at least the drops on the container. // at least the drops on the container.
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities"), container.SecurityContext.Capabilities, allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities"), capabilities,
"required capabilities are not set on the securityContext")) "required capabilities are not set on the securityContext"))
return allErrs return allErrs
} }
@ -112,7 +109,7 @@ func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container) f
// validate that anything being added is in the default or allowed sets // validate that anything being added is in the default or allowed sets
defaultAdd := makeCapSet(s.defaultAddCapabilities) defaultAdd := makeCapSet(s.defaultAddCapabilities)
for _, cap := range container.SecurityContext.Capabilities.Add { for _, cap := range capabilities.Add {
sCap := string(cap) sCap := string(cap)
if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) { if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) {
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "add"), sCap, "capability may not be added")) allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "add"), sCap, "capability may not be added"))
@ -120,12 +117,12 @@ func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container) f
} }
// validate that anything that is required to be dropped is in the drop set // validate that anything that is required to be dropped is in the drop set
containerDrops := makeCapSet(container.SecurityContext.Capabilities.Drop) containerDrops := makeCapSet(capabilities.Drop)
for _, requiredDrop := range s.requiredDropCapabilities { for _, requiredDrop := range s.requiredDropCapabilities {
sDrop := string(requiredDrop) sDrop := string(requiredDrop)
if !containerDrops.Has(sDrop) { if !containerDrops.Has(sDrop) {
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "drop"), container.SecurityContext.Capabilities.Drop, allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "drop"), capabilities.Drop,
fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) fmt.Sprintf("%s is required to be dropped but was not found", sDrop)))
} }
} }

View File

@ -31,6 +31,10 @@ func TestGenerateAdds(t *testing.T) {
expectedCaps *api.Capabilities expectedCaps *api.Capabilities
}{ }{
"no required, no container requests": {}, "no required, no container requests": {},
"no required, no container requests, non-nil": {
containerCaps: &api.Capabilities{},
expectedCaps: &api.Capabilities{},
},
"required, no container requests": { "required, no container requests": {
defaultAddCaps: []api.Capability{"foo"}, defaultAddCaps: []api.Capability{"foo"},
expectedCaps: &api.Capabilities{ expectedCaps: &api.Capabilities{
@ -64,13 +68,22 @@ func TestGenerateAdds(t *testing.T) {
Add: []api.Capability{"bar", "foo"}, Add: []api.Capability{"bar", "foo"},
}, },
}, },
"generation dedupes": { "generation does not mutate unnecessarily": {
defaultAddCaps: []api.Capability{"foo", "foo", "foo", "foo"}, defaultAddCaps: []api.Capability{"foo", "bar"},
containerCaps: &api.Capabilities{ containerCaps: &api.Capabilities{
Add: []api.Capability{"foo", "foo", "foo"}, Add: []api.Capability{"foo", "foo", "bar", "baz"},
}, },
expectedCaps: &api.Capabilities{ expectedCaps: &api.Capabilities{
Add: []api.Capability{"foo"}, Add: []api.Capability{"foo", "foo", "bar", "baz"},
},
},
"generation dedupes": {
defaultAddCaps: []api.Capability{"foo", "bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo", "baz"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"bar", "baz", "foo"},
}, },
}, },
"generation is case sensitive - will not dedupe": { "generation is case sensitive - will not dedupe": {
@ -121,6 +134,10 @@ func TestGenerateDrops(t *testing.T) {
"no required, no container requests": { "no required, no container requests": {
expectedCaps: nil, expectedCaps: nil,
}, },
"no required, no container requests, non-nil": {
containerCaps: &api.Capabilities{},
expectedCaps: &api.Capabilities{},
},
"required drops are defaulted": { "required drops are defaulted": {
requiredDropCaps: []api.Capability{"foo"}, requiredDropCaps: []api.Capability{"foo"},
expectedCaps: &api.Capabilities{ expectedCaps: &api.Capabilities{
@ -128,12 +145,21 @@ func TestGenerateDrops(t *testing.T) {
}, },
}, },
"required drops are defaulted when making container requests": { "required drops are defaulted when making container requests": {
requiredDropCaps: []api.Capability{"foo"}, requiredDropCaps: []api.Capability{"baz"},
containerCaps: &api.Capabilities{ containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar"}, Drop: []api.Capability{"foo", "bar"},
}, },
expectedCaps: &api.Capabilities{ expectedCaps: &api.Capabilities{
Drop: []api.Capability{"bar", "foo"}, Drop: []api.Capability{"bar", "baz", "foo"},
},
},
"required drops do not mutate unnecessarily": {
requiredDropCaps: []api.Capability{"baz"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar", "baz"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar", "baz"},
}, },
}, },
"can drop a required add": { "can drop a required add": {
@ -167,12 +193,12 @@ func TestGenerateDrops(t *testing.T) {
}, },
}, },
"generation dedupes": { "generation dedupes": {
requiredDropCaps: []api.Capability{"bar", "bar", "bar", "bar"}, requiredDropCaps: []api.Capability{"baz", "foo"},
containerCaps: &api.Capabilities{ containerCaps: &api.Capabilities{
Drop: []api.Capability{"bar", "bar", "bar"}, Drop: []api.Capability{"bar", "foo"},
}, },
expectedCaps: &api.Capabilities{ expectedCaps: &api.Capabilities{
Drop: []api.Capability{"bar"}, Drop: []api.Capability{"bar", "baz", "foo"},
}, },
}, },
"generation is case sensitive - will not dedupe": { "generation is case sensitive - will not dedupe": {
@ -298,18 +324,12 @@ func TestValidateAdds(t *testing.T) {
} }
for k, v := range tests { for k, v := range tests {
container := &api.Container{
SecurityContext: &api.SecurityContext{
Capabilities: v.containerCaps,
},
}
strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps) strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps)
if err != nil { if err != nil {
t.Errorf("%s failed: %v", k, err) t.Errorf("%s failed: %v", k, err)
continue continue
} }
errs := strategy.Validate(nil, container) errs := strategy.Validate(nil, nil, v.containerCaps)
if v.expectedError == "" && len(errs) > 0 { if v.expectedError == "" && len(errs) > 0 {
t.Errorf("%s should have passed but had errors %v", k, errs) t.Errorf("%s should have passed but had errors %v", k, errs)
continue continue
@ -365,18 +385,12 @@ func TestValidateDrops(t *testing.T) {
} }
for k, v := range tests { for k, v := range tests {
container := &api.Container{
SecurityContext: &api.SecurityContext{
Capabilities: v.containerCaps,
},
}
strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil) strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil)
if err != nil { if err != nil {
t.Errorf("%s failed: %v", k, err) t.Errorf("%s failed: %v", k, err)
continue continue
} }
errs := strategy.Validate(nil, container) errs := strategy.Validate(nil, nil, v.containerCaps)
if v.expectedError == "" && len(errs) > 0 { if v.expectedError == "" && len(errs) > 0 {
t.Errorf("%s should have passed but had errors %v", k, errs) t.Errorf("%s should have passed but had errors %v", k, errs)
continue continue

View File

@ -26,5 +26,5 @@ type Strategy interface {
// Generate creates the capabilities based on policy rules. // Generate creates the capabilities based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error)
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container) field.ErrorList Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList
} }

View File

@ -46,13 +46,13 @@ func NewMustRunAs(ranges []extensions.GroupIDRange, field string) (GroupStrategy
// Generate creates the group based on policy rules. By default this returns the first group of the // Generate creates the group based on policy rules. By default this returns the first group of the
// first range (min val). // first range (min val).
func (s *mustRunAs) Generate(pod *api.Pod) ([]int64, error) { func (s *mustRunAs) Generate(_ *api.Pod) ([]int64, error) {
return []int64{s.ranges[0].Min}, nil return []int64{s.ranges[0].Min}, nil
} }
// Generate a single value to be applied. This is used for FSGroup. This strategy will return // Generate a single value to be applied. This is used for FSGroup. This strategy will return
// the first group of the first range (min val). // the first group of the first range (min val).
func (s *mustRunAs) GenerateSingle(pod *api.Pod) (*int64, error) { func (s *mustRunAs) GenerateSingle(_ *api.Pod) (*int64, error) {
single := new(int64) single := new(int64)
*single = s.ranges[0].Min *single = s.ranges[0].Min
return single, nil return single, nil
@ -61,14 +61,9 @@ func (s *mustRunAs) GenerateSingle(pod *api.Pod) (*int64, error) {
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and // Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and
// supplemental groups). // supplemental groups).
func (s *mustRunAs) Validate(pod *api.Pod, groups []int64) field.ErrorList { func (s *mustRunAs) Validate(_ *api.Pod, groups []int64) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if pod.Spec.SecurityContext == nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("securityContext"), pod.Spec.SecurityContext, "unable to validate nil security context"))
return allErrs
}
if len(groups) == 0 && len(s.ranges) > 0 { if len(groups) == 0 && len(s.ranges) > 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath(s.field), groups, "unable to validate empty groups against required ranges")) allErrs = append(allErrs, field.Invalid(field.NewPath(s.field), groups, "unable to validate empty groups against required ranges"))
} }

View File

@ -109,14 +109,6 @@ func TestGenerate(t *testing.T) {
} }
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
validPod := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{},
},
}
}
tests := map[string]struct { tests := map[string]struct {
ranges []extensions.GroupIDRange ranges []extensions.GroupIDRange
pod *api.Pod pod *api.Pod
@ -124,19 +116,16 @@ func TestValidate(t *testing.T) {
pass bool pass bool
}{ }{
"nil security context": { "nil security context": {
pod: &api.Pod{},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
}, },
}, },
"empty groups": { "empty groups": {
pod: validPod(),
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
}, },
}, },
"not in range": { "not in range": {
pod: validPod(),
groups: []int64{5}, groups: []int64{5},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
@ -144,7 +133,6 @@ func TestValidate(t *testing.T) {
}, },
}, },
"in range 1": { "in range 1": {
pod: validPod(),
groups: []int64{2}, groups: []int64{2},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
@ -152,7 +140,6 @@ func TestValidate(t *testing.T) {
pass: true, pass: true,
}, },
"in range boundry min": { "in range boundry min": {
pod: validPod(),
groups: []int64{1}, groups: []int64{1},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
@ -160,7 +147,6 @@ func TestValidate(t *testing.T) {
pass: true, pass: true,
}, },
"in range boundry max": { "in range boundry max": {
pod: validPod(),
groups: []int64{3}, groups: []int64{3},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3}, {Min: 1, Max: 3},
@ -168,7 +154,6 @@ func TestValidate(t *testing.T) {
pass: true, pass: true,
}, },
"singular range": { "singular range": {
pod: validPod(),
groups: []int64{4}, groups: []int64{4},
ranges: []extensions.GroupIDRange{ ranges: []extensions.GroupIDRange{
{Min: 4, Max: 4}, {Min: 4, Max: 4},
@ -182,7 +167,7 @@ func TestValidate(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("error creating strategy for %s: %v", k, err) t.Errorf("error creating strategy for %s: %v", k, err)
} }
errs := s.Validate(v.pod, v.groups) errs := s.Validate(nil, v.groups)
if v.pass && len(errs) > 0 { if v.pass && len(errs) > 0 {
t.Errorf("unexpected errors for %s: %v", k, errs) t.Errorf("unexpected errors for %s: %v", k, errs)
} }

View File

@ -33,17 +33,17 @@ func NewRunAsAny() (GroupStrategy, error) {
} }
// Generate creates the group based on policy rules. This strategy returns an empty slice. // Generate creates the group based on policy rules. This strategy returns an empty slice.
func (s *runAsAny) Generate(pod *api.Pod) ([]int64, error) { func (s *runAsAny) Generate(_ *api.Pod) ([]int64, error) {
return []int64{}, nil return nil, nil
} }
// Generate a single value to be applied. This is used for FSGroup. This strategy returns nil. // Generate a single value to be applied. This is used for FSGroup. This strategy returns nil.
func (s *runAsAny) GenerateSingle(pod *api.Pod) (*int64, error) { func (s *runAsAny) GenerateSingle(_ *api.Pod) (*int64, error) {
return nil, nil return nil, nil
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(pod *api.Pod, groups []int64) field.ErrorList { func (s *runAsAny) Validate(_ *api.Pod, groups []int64) field.ErrorList {
return field.ErrorList{} return field.ErrorList{}
} }

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/securitycontext"
"k8s.io/kubernetes/pkg/util/maps" "k8s.io/kubernetes/pkg/util/maps"
) )
@ -66,42 +67,32 @@ func NewSimpleProvider(psp *extensions.PodSecurityPolicy, namespace string, stra
// Create a PodSecurityContext based on the given constraints. If a setting is already set // Create a PodSecurityContext based on the given constraints. If a setting is already set
// on the PodSecurityContext it will not be changed. Validate should be used after the context // on the PodSecurityContext it will not be changed. Validate should be used after the context
// is created to ensure it complies with the required restrictions. // is created to ensure it complies with the required restrictions.
//
// NOTE: this method works on a copy of the PodSecurityContext. It is up to the caller to
// apply the PSC if validation passes.
func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, map[string]string, error) { func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, map[string]string, error) {
var sc *api.PodSecurityContext = nil sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext)
if pod.Spec.SecurityContext != nil {
// work with a copy
copy := *pod.Spec.SecurityContext
sc = &copy
} else {
sc = &api.PodSecurityContext{}
}
annotations := maps.CopySS(pod.Annotations) annotations := maps.CopySS(pod.Annotations)
if len(sc.SupplementalGroups) == 0 { if sc.SupplementalGroups() == nil {
supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod) supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.SupplementalGroups = supGroups sc.SetSupplementalGroups(supGroups)
} }
if sc.FSGroup == nil { if sc.FSGroup() == nil {
fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod) fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.FSGroup = fsGroup sc.SetFSGroup(fsGroup)
} }
if sc.SELinuxOptions == nil { if sc.SELinuxOptions() == nil {
seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil) seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.SELinuxOptions = seLinux sc.SetSELinuxOptions(seLinux)
} }
// This is only generated on the pod level. Containers inherit the pod's profile. If the // This is only generated on the pod level. Containers inherit the pod's profile. If the
@ -116,40 +107,34 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit
} }
annotations[api.SeccompPodAnnotationKey] = seccompProfile annotations[api.SeccompPodAnnotationKey] = seccompProfile
} }
return sc, annotations, nil return sc.PodSecurityContext(), annotations, nil
} }
// Create a SecurityContext based on the given constraints. If a setting is already set on the // Create a SecurityContext based on the given constraints. If a setting is already set on the
// container's security context then it will not be changed. Validation should be used after // container's security context then it will not be changed. Validation should be used after
// the context is created to ensure it complies with the required restrictions. // the context is created to ensure it complies with the required restrictions.
//
// NOTE: this method works on a copy of the SC of the container. It is up to the caller to apply
// the SC if validation passes.
func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, map[string]string, error) { func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, map[string]string, error) {
var sc *api.SecurityContext = nil sc := securitycontext.NewEffectiveContainerSecurityContextMutator(
if container.SecurityContext != nil { securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext),
// work with a copy of the original securitycontext.NewContainerSecurityContextMutator(container.SecurityContext),
copy := *container.SecurityContext )
sc = &copy
} else {
sc = &api.SecurityContext{}
}
annotations := maps.CopySS(pod.Annotations) annotations := maps.CopySS(pod.Annotations)
if sc.RunAsUser == nil { if sc.RunAsUser() == nil {
uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container) uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.RunAsUser = uid sc.SetRunAsUser(uid)
} }
if sc.SELinuxOptions == nil { if sc.SELinuxOptions() == nil {
seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container) seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.SELinuxOptions = seLinux sc.SetSELinuxOptions(seLinux)
} }
annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container) annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container)
@ -157,82 +142,67 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
return nil, nil, err return nil, nil, err
} }
if sc.Privileged == nil {
priv := false
sc.Privileged = &priv
}
// if we're using the non-root strategy set the marker that this container should not be // if we're using the non-root strategy set the marker that this container should not be
// run as root which will signal to the kubelet to do a final check either on the runAsUser // run as root which will signal to the kubelet to do a final check either on the runAsUser
// or, if runAsUser is not set, the image UID will be checked. // or, if runAsUser is not set, the image UID will be checked.
if sc.RunAsNonRoot == nil && s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot { if sc.RunAsNonRoot() == nil && sc.RunAsUser() == nil && s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot {
nonRoot := true nonRoot := true
sc.RunAsNonRoot = &nonRoot sc.SetRunAsNonRoot(&nonRoot)
} }
caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container) caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sc.Capabilities = caps sc.SetCapabilities(caps)
// if the PSP requires a read only root filesystem and the container has not made a specific // if the PSP requires a read only root filesystem and the container has not made a specific
// request then default ReadOnlyRootFilesystem to true. // request then default ReadOnlyRootFilesystem to true.
if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem == nil { if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem() == nil {
readOnlyRootFS := true readOnlyRootFS := true
sc.ReadOnlyRootFilesystem = &readOnlyRootFS sc.SetReadOnlyRootFilesystem(&readOnlyRootFS)
} }
// if the PSP sets DefaultAllowPrivilegeEscalation and the container security context // if the PSP sets DefaultAllowPrivilegeEscalation and the container security context
// allowPrivilegeEscalation is not set, then default to that set by the PSP. // allowPrivilegeEscalation is not set, then default to that set by the PSP.
if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation == nil { if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation() == nil {
sc.AllowPrivilegeEscalation = s.psp.Spec.DefaultAllowPrivilegeEscalation sc.SetAllowPrivilegeEscalation(s.psp.Spec.DefaultAllowPrivilegeEscalation)
} }
// if the PSP sets psp.AllowPrivilegeEscalation to false set that as the default // if the PSP sets psp.AllowPrivilegeEscalation to false set that as the default
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation == nil { if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation() == nil {
sc.AllowPrivilegeEscalation = &s.psp.Spec.AllowPrivilegeEscalation sc.SetAllowPrivilegeEscalation(&s.psp.Spec.AllowPrivilegeEscalation)
} }
return sc, annotations, nil return sc.ContainerSecurityContext(), annotations, nil
} }
// Ensure a pod's SecurityContext is in compliance with the given constraints. // Ensure a pod's SecurityContext is in compliance with the given constraints.
func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList { func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if pod.Spec.SecurityContext == nil { sc := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), pod.Spec.SecurityContext, "No security context is set"))
return allErrs
}
fsGroups := []int64{} fsGroups := []int64{}
if pod.Spec.SecurityContext.FSGroup != nil { if fsGroup := sc.FSGroup(); fsGroup != nil {
fsGroups = append(fsGroups, *pod.Spec.SecurityContext.FSGroup) fsGroups = append(fsGroups, *fsGroup)
} }
allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...) allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...)
allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, pod.Spec.SecurityContext.SupplementalGroups)...) allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, sc.SupplementalGroups())...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...) allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...)
// make a dummy container context to reuse the selinux strategies allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...)
container := &api.Container{
Name: pod.Name,
SecurityContext: &api.SecurityContext{
SELinuxOptions: pod.Spec.SecurityContext.SELinuxOptions,
},
}
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
if !s.psp.Spec.HostNetwork && pod.Spec.SecurityContext.HostNetwork { if !s.psp.Spec.HostNetwork && sc.HostNetwork() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), sc.HostNetwork(), "Host network is not allowed to be used"))
} }
if !s.psp.Spec.HostPID && pod.Spec.SecurityContext.HostPID { if !s.psp.Spec.HostPID && sc.HostPID() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), sc.HostPID(), "Host PID is not allowed to be used"))
} }
if !s.psp.Spec.HostIPC && pod.Spec.SecurityContext.HostIPC { if !s.psp.Spec.HostIPC && sc.HostIPC() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), sc.HostIPC(), "Host IPC is not allowed to be used"))
} }
allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...) allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...)
@ -273,25 +243,23 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList { func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if container.SecurityContext == nil { podSC := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), container.SecurityContext, "No security context is set")) sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext))
return allErrs
}
sc := container.SecurityContext allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(fldPath.Child("securityContext"), pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...)
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...) allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...) allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...) allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...)
if !s.psp.Spec.Privileged && *sc.Privileged { privileged := sc.Privileged()
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed")) if !s.psp.Spec.Privileged && privileged != nil && *privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *privileged, "Privileged containers are not allowed"))
} }
allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(pod, container)...) allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(pod, container, sc.Capabilities())...)
if !s.psp.Spec.HostNetwork && pod.Spec.SecurityContext.HostNetwork { if !s.psp.Spec.HostNetwork && podSC.HostNetwork() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), podSC.HostNetwork(), "Host network is not allowed to be used"))
} }
containersPath := fldPath.Child("containers") containersPath := fldPath.Child("containers")
@ -306,29 +274,30 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...) allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
} }
if !s.psp.Spec.HostPID && pod.Spec.SecurityContext.HostPID { if !s.psp.Spec.HostPID && podSC.HostPID() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), podSC.HostPID(), "Host PID is not allowed to be used"))
} }
if !s.psp.Spec.HostIPC && pod.Spec.SecurityContext.HostIPC { if !s.psp.Spec.HostIPC && podSC.HostIPC() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used")) allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), podSC.HostIPC(), "Host IPC is not allowed to be used"))
} }
if s.psp.Spec.ReadOnlyRootFilesystem { if s.psp.Spec.ReadOnlyRootFilesystem {
if sc.ReadOnlyRootFilesystem == nil { readOnly := sc.ReadOnlyRootFilesystem()
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem may not be nil and must be set to true")) if readOnly == nil {
} else if !*sc.ReadOnlyRootFilesystem { allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), readOnly, "ReadOnlyRootFilesystem may not be nil and must be set to true"))
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), *sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem must be set to true")) } else if !*readOnly {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), *readOnly, "ReadOnlyRootFilesystem must be set to true"))
} }
} }
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation == nil { allowEscalation := sc.AllowPrivilegeEscalation()
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), sc.AllowPrivilegeEscalation, "Allowing privilege escalation for containers is not allowed")) if !s.psp.Spec.AllowPrivilegeEscalation && allowEscalation == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), allowEscalation, "Allowing privilege escalation for containers is not allowed"))
} }
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation != nil && *sc.AllowPrivilegeEscalation { if !s.psp.Spec.AllowPrivilegeEscalation && allowEscalation != nil && *allowEscalation {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), *sc.AllowPrivilegeEscalation, "Allowing privilege escalation for containers is not allowed")) allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), *allowEscalation, "Allowing privilege escalation for containers is not allowed"))
} }
return allErrs return allErrs

View File

@ -55,30 +55,21 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
Name: "psp-sa", Name: "psp-sa",
Annotations: map[string]string{ Annotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "*", seccomp.AllowedProfilesAnnotationKey: "*",
seccomp.DefaultProfileAnnotationKey: "foo",
}, },
}, },
Spec: extensions.PodSecurityPolicySpec{ Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"}, AllowPrivilegeEscalation: true,
RequiredDropCapabilities: []api.Capability{"bar"},
RunAsUser: extensions.RunAsUserStrategyOptions{ RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyRunAsAny, Rule: extensions.RunAsUserStrategyRunAsAny,
}, },
SELinux: extensions.SELinuxStrategyOptions{ SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny, Rule: extensions.SELinuxStrategyRunAsAny,
}, },
// these are pod mutating strategies that are tested above
FSGroup: extensions.FSGroupStrategyOptions{ FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyMustRunAs, Rule: extensions.FSGroupStrategyRunAsAny,
Ranges: []extensions.GroupIDRange{
{Min: 1, Max: 1},
},
}, },
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyMustRunAs, Rule: extensions.SupplementalGroupsStrategyRunAsAny,
Ranges: []extensions.GroupIDRange{
{Min: 1, Max: 1},
},
}, },
}, },
} }
@ -91,17 +82,13 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to create provider %v", err) t.Fatalf("unable to create provider %v", err)
} }
sc, _, err := provider.CreatePodSecurityContext(pod) _, _, err = provider.CreatePodSecurityContext(pod)
if err != nil { if err != nil {
t.Fatalf("unable to create psc %v", err) t.Fatalf("unable to create psc %v", err)
} }
// The generated security context should have filled in missing options, so they should differ
if reflect.DeepEqual(sc, &pod.Spec.SecurityContext) {
t.Error("expected created security context to be different than container's, but they were identical")
}
// Creating the provider or the security context should not have mutated the psp or pod // Creating the provider or the security context should not have mutated the psp or pod
// since all the strategies were permissive
if !reflect.DeepEqual(createPod(), pod) { if !reflect.DeepEqual(createPod(), pod) {
diffs := diff.ObjectDiff(createPod(), pod) diffs := diff.ObjectDiff(createPod(), pod)
t.Errorf("pod was mutated by CreatePodSecurityContext. diff:\n%s", diffs) t.Errorf("pod was mutated by CreatePodSecurityContext. diff:\n%s", diffs)
@ -134,7 +121,6 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
// Create a PSP with strategies that will populate a blank security context // Create a PSP with strategies that will populate a blank security context
createPSP := func() *extensions.PodSecurityPolicy { createPSP := func() *extensions.PodSecurityPolicy {
uid := int64(1)
return &extensions.PodSecurityPolicy{ return &extensions.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "psp-sa", Name: "psp-sa",
@ -144,25 +130,19 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
}, },
}, },
Spec: extensions.PodSecurityPolicySpec{ Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"}, AllowPrivilegeEscalation: true,
RequiredDropCapabilities: []api.Capability{"bar"},
RunAsUser: extensions.RunAsUserStrategyOptions{ RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyMustRunAs, Rule: extensions.RunAsUserStrategyRunAsAny,
Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}},
}, },
SELinux: extensions.SELinuxStrategyOptions{ SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyMustRunAs, Rule: extensions.SELinuxStrategyRunAsAny,
SELinuxOptions: &api.SELinuxOptions{User: "you"},
}, },
// these are pod mutating strategies that are tested above
FSGroup: extensions.FSGroupStrategyOptions{ FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny, Rule: extensions.FSGroupStrategyRunAsAny,
}, },
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyRunAsAny, Rule: extensions.SupplementalGroupsStrategyRunAsAny,
}, },
// mutates the container SC by defaulting to true if container sets nil
ReadOnlyRootFilesystem: true,
}, },
} }
} }
@ -174,17 +154,13 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to create provider %v", err) t.Fatalf("unable to create provider %v", err)
} }
sc, _, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0]) _, _, err = provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0])
if err != nil { if err != nil {
t.Fatalf("unable to create container security context %v", err) t.Fatalf("unable to create container security context %v", err)
} }
// The generated security context should have filled in missing options, so they should differ
if reflect.DeepEqual(sc, &pod.Spec.Containers[0].SecurityContext) {
t.Error("expected created security context to be different than container's, but they were identical")
}
// Creating the provider or the security context should not have mutated the psp or pod // Creating the provider or the security context should not have mutated the psp or pod
// since all the strategies were permissive
if !reflect.DeepEqual(createPod(), pod) { if !reflect.DeepEqual(createPod(), pod) {
diffs := diff.ObjectDiff(createPod(), pod) diffs := diff.ObjectDiff(createPod(), pod)
t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs) t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs)
@ -323,12 +299,12 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
"failNilSELinux": { "failNilSELinux": {
pod: failNilSELinuxPod, pod: failNilSELinuxPod,
psp: failSELinuxPSP, psp: failSELinuxPSP,
expectedError: "unable to validate nil seLinuxOptions", expectedError: "seLinuxOptions: Required",
}, },
"failInvalidSELinux": { "failInvalidSELinux": {
pod: failInvalidSELinuxPod, pod: failInvalidSELinuxPod,
psp: failSELinuxPSP, psp: failSELinuxPSP,
expectedError: "does not match required level. Found bar, wanted foo", expectedError: "seLinuxOptions.level: Invalid value",
}, },
"failHostDirPSP": { "failHostDirPSP": {
pod: failHostDirPod, pod: failHostDirPod,
@ -455,12 +431,12 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
"failUserPSP": { "failUserPSP": {
pod: failUserPod, pod: failUserPod,
psp: failUserPSP, psp: failUserPSP,
expectedError: "does not match required range", expectedError: "runAsUser: Invalid value",
}, },
"failSELinuxPSP": { "failSELinuxPSP": {
pod: failSELinuxPod, pod: failSELinuxPod,
psp: failSELinuxPSP, psp: failSELinuxPSP,
expectedError: "does not match required level", expectedError: "seLinuxOptions.level: Invalid value",
}, },
"failNilAppArmor": { "failNilAppArmor": {
pod: failNilAppArmorPod, pod: failNilAppArmorPod,

View File

@ -43,41 +43,33 @@ func NewMustRunAs(options *extensions.SELinuxStrategyOptions) (SELinuxStrategy,
} }
// Generate creates the SELinuxOptions based on constraint rules. // Generate creates the SELinuxOptions based on constraint rules.
func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) { func (s *mustRunAs) Generate(_ *api.Pod, _ *api.Container) (*api.SELinuxOptions, error) {
return s.opts.SELinuxOptions, nil return s.opts.SELinuxOptions, nil
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *mustRunAs) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, seLinux *api.SELinuxOptions) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if container.SecurityContext == nil { if seLinux == nil {
detail := fmt.Sprintf("unable to validate nil security context for %s", container.Name) allErrs = append(allErrs, field.Required(fldPath, ""))
allErrs = append(allErrs, field.Invalid(field.NewPath("securityContext"), container.SecurityContext, detail))
return allErrs return allErrs
} }
if container.SecurityContext.SELinuxOptions == nil {
detail := fmt.Sprintf("unable to validate nil seLinuxOptions for %s", container.Name)
allErrs = append(allErrs, field.Invalid(field.NewPath("seLinuxOptions"), container.SecurityContext.SELinuxOptions, detail))
return allErrs
}
seLinuxOptionsPath := field.NewPath("seLinuxOptions")
seLinux := container.SecurityContext.SELinuxOptions
if seLinux.Level != s.opts.SELinuxOptions.Level { if seLinux.Level != s.opts.SELinuxOptions.Level {
detail := fmt.Sprintf("seLinuxOptions.level on %s does not match required level. Found %s, wanted %s", container.Name, seLinux.Level, s.opts.SELinuxOptions.Level) detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Level)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("level"), seLinux.Level, detail)) allErrs = append(allErrs, field.Invalid(fldPath.Child("level"), seLinux.Level, detail))
} }
if seLinux.Role != s.opts.SELinuxOptions.Role { if seLinux.Role != s.opts.SELinuxOptions.Role {
detail := fmt.Sprintf("seLinuxOptions.role on %s does not match required role. Found %s, wanted %s", container.Name, seLinux.Role, s.opts.SELinuxOptions.Role) detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Role)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("role"), seLinux.Role, detail)) allErrs = append(allErrs, field.Invalid(fldPath.Child("role"), seLinux.Role, detail))
} }
if seLinux.Type != s.opts.SELinuxOptions.Type { if seLinux.Type != s.opts.SELinuxOptions.Type {
detail := fmt.Sprintf("seLinuxOptions.type on %s does not match required type. Found %s, wanted %s", container.Name, seLinux.Type, s.opts.SELinuxOptions.Type) detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Type)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("type"), seLinux.Type, detail)) allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), seLinux.Type, detail))
} }
if seLinux.User != s.opts.SELinuxOptions.User { if seLinux.User != s.opts.SELinuxOptions.User {
detail := fmt.Sprintf("seLinuxOptions.user on %s does not match required user. Found %s, wanted %s", container.Name, seLinux.User, s.opts.SELinuxOptions.User) detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.User)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("user"), seLinux.User, detail)) allErrs = append(allErrs, field.Invalid(fldPath.Child("user"), seLinux.User, detail))
} }
return allErrs return allErrs

View File

@ -100,19 +100,19 @@ func TestMustRunAsValidate(t *testing.T) {
}{ }{
"invalid role": { "invalid role": {
seLinux: role, seLinux: role,
expectedMsg: "does not match required role", expectedMsg: "role: Invalid value",
}, },
"invalid user": { "invalid user": {
seLinux: user, seLinux: user,
expectedMsg: "does not match required user", expectedMsg: "user: Invalid value",
}, },
"invalid level": { "invalid level": {
seLinux: level, seLinux: level,
expectedMsg: "does not match required level", expectedMsg: "level: Invalid value",
}, },
"invalid type": { "invalid type": {
seLinux: seType, seLinux: seType,
expectedMsg: "does not match required type", expectedMsg: "type: Invalid value",
}, },
"valid": { "valid": {
seLinux: newValidOpts(), seLinux: newValidOpts(),
@ -130,13 +130,8 @@ func TestMustRunAsValidate(t *testing.T) {
t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
continue continue
} }
container := &api.Container{
SecurityContext: &api.SecurityContext{
SELinuxOptions: tc.seLinux,
},
}
errs := mustRunAs.Validate(nil, container) errs := mustRunAs.Validate(nil, nil, nil, tc.seLinux)
//should've passed but didn't //should've passed but didn't
if len(tc.expectedMsg) == 0 && len(errs) > 0 { if len(tc.expectedMsg) == 0 && len(errs) > 0 {
t.Errorf("%s expected no errors but received %v", name, errs) t.Errorf("%s expected no errors but received %v", name, errs)

View File

@ -38,6 +38,6 @@ func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*api.SELinu
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, options *api.SELinuxOptions) field.ErrorList {
return field.ErrorList{} return field.ErrorList{}
} }

View File

@ -58,7 +58,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err) t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
} }
errs := s.Validate(nil, nil) errs := s.Validate(nil, nil, nil, nil)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("unexpected errors validating with ") t.Errorf("unexpected errors validating with ")
} }
@ -66,7 +66,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err) t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
} }
errs = s.Validate(nil, nil) errs = s.Validate(nil, nil, nil, nil)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("unexpected errors validating %v", errs) t.Errorf("unexpected errors validating %v", errs)
} }

View File

@ -26,5 +26,5 @@ type SELinuxStrategy interface {
// Generate creates the SELinuxOptions based on constraint rules. // Generate creates the SELinuxOptions based on constraint rules.
Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error)
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container) field.ErrorList Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, options *api.SELinuxOptions) field.ErrorList
} }

View File

@ -49,27 +49,17 @@ func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*int64, er
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *mustRunAs) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
securityContextPath := field.NewPath("securityContext") if runAsUser == nil {
if container.SecurityContext == nil { allErrs = append(allErrs, field.Required(fldPath.Child("runAsUser"), ""))
detail := fmt.Sprintf("unable to validate nil security context for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail))
return allErrs
}
if container.SecurityContext.RunAsUser == nil {
detail := fmt.Sprintf("unable to validate nil RunAsUser for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), container.SecurityContext.RunAsUser, detail))
return allErrs return allErrs
} }
if !s.isValidUID(*container.SecurityContext.RunAsUser) { if !s.isValidUID(*runAsUser) {
detail := fmt.Sprintf("UID on container %s does not match required range. Found %d, allowed: %v", detail := fmt.Sprintf("must be in the ranges: %v", s.opts.Ranges)
container.Name, allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, detail))
*container.SecurityContext.RunAsUser,
s.opts.Ranges)
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail))
} }
return allErrs return allErrs
} }

View File

@ -98,19 +98,13 @@ func TestValidate(t *testing.T) {
}, },
}, },
}, },
"nil security context": {
container: &api.Container{
SecurityContext: nil,
},
expectedMsg: "unable to validate nil security context for container",
},
"nil run as user": { "nil run as user": {
container: &api.Container{ container: &api.Container{
SecurityContext: &api.SecurityContext{ SecurityContext: &api.SecurityContext{
RunAsUser: nil, RunAsUser: nil,
}, },
}, },
expectedMsg: "unable to validate nil RunAsUser for container", expectedMsg: "runAsUser: Required",
}, },
"invalid id": { "invalid id": {
container: &api.Container{ container: &api.Container{
@ -118,7 +112,7 @@ func TestValidate(t *testing.T) {
RunAsUser: &invalidID, RunAsUser: &invalidID,
}, },
}, },
expectedMsg: "does not match required range", expectedMsg: "runAsUser: Invalid",
}, },
} }
@ -128,7 +122,7 @@ func TestValidate(t *testing.T) {
t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
continue continue
} }
errs := mustRunAs.Validate(nil, tc.container) errs := mustRunAs.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser)
//should've passed but didn't //should've passed but didn't
if len(tc.expectedMsg) == 0 && len(errs) > 0 { if len(tc.expectedMsg) == 0 && len(errs) > 0 {
t.Errorf("%s expected no errors but received %v", name, errs) t.Errorf("%s expected no errors but received %v", name, errs)

View File

@ -17,8 +17,6 @@ limitations under the License.
package user package user
import ( import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
@ -43,22 +41,18 @@ func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*int64, erro
// or if the UID is set it is not root. Validation will fail if RunAsNonRoot is set to false. // or if the UID is set it is not root. Validation will fail if RunAsNonRoot is set to false.
// In order to work properly this assumes that the kubelet performs a final check on runAsUser // In order to work properly this assumes that the kubelet performs a final check on runAsUser
// or the image UID when runAsUser is nil. // or the image UID when runAsUser is nil.
func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *nonRoot) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
securityContextPath := field.NewPath("securityContext") if runAsNonRoot == nil && runAsUser == nil {
if container.SecurityContext == nil { allErrs = append(allErrs, field.Required(fldPath.Child("runAsNonRoot"), "must be true"))
detail := fmt.Sprintf("unable to validate nil security context for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail))
return allErrs return allErrs
} }
if container.SecurityContext.RunAsNonRoot != nil && *container.SecurityContext.RunAsNonRoot == false { if runAsNonRoot != nil && *runAsNonRoot == false {
detail := fmt.Sprintf("RunAsNonRoot must be true for container %s", container.Name) allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsNonRoot"), *runAsNonRoot, "must be true"))
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsNonRoot"), *container.SecurityContext.RunAsNonRoot, detail))
return allErrs return allErrs
} }
if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 { if runAsUser != nil && *runAsUser == 0 {
detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy for container %s", container.Name) allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, "running with the root UID is forbidden"))
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail))
return allErrs return allErrs
} }
return allErrs return allErrs

View File

@ -102,17 +102,17 @@ func TestNonRootValidate(t *testing.T) {
{ {
container: &api.Container{ container: &api.Container{
SecurityContext: &api.SecurityContext{ SecurityContext: &api.SecurityContext{
RunAsNonRoot: &unfalse, RunAsNonRoot: nil,
RunAsUser: &badUID, RunAsUser: nil,
}, },
}, },
expectedErr: true, expectedErr: true,
msg: "in test case %d, expected errors from root uid but got %v", msg: "in test case %d, expected errors from nil runAsNonRoot and nil runAsUser but got %v",
}, },
} }
for i, tc := range tests { for i, tc := range tests {
errs := s.Validate(nil, tc.container) errs := s.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser)
if (len(errs) == 0) == tc.expectedErr { if (len(errs) == 0) == tc.expectedErr {
t.Errorf(tc.msg, i, errs) t.Errorf(tc.msg, i, errs)
} }

View File

@ -38,6 +38,6 @@ func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*int64, err
} }
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
return field.ErrorList{} return field.ErrorList{}
} }

View File

@ -52,7 +52,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err) t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
} }
errs := s.Validate(nil, nil) errs := s.Validate(nil, nil, nil, nil, nil)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("unexpected errors validating with ") t.Errorf("unexpected errors validating with ")
} }

View File

@ -26,5 +26,5 @@ type RunAsUserStrategy interface {
// Generate creates the uid based on policy rules. // Generate creates the uid based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*int64, error) Generate(pod *api.Pod, container *api.Container) (*int64, error)
// Validate ensures that the specified values fall within the range of the strategy. // Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container) field.ErrorList Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList
} }

View File

@ -9,6 +9,7 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"accessors.go",
"doc.go", "doc.go",
"fake.go", "fake.go",
"util.go", "util.go",
@ -22,10 +23,17 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["util_test.go"], srcs = [
"accessors_test.go",
"util_test.go",
],
importpath = "k8s.io/kubernetes/pkg/securitycontext", importpath = "k8s.io/kubernetes/pkg/securitycontext",
library = ":go_default_library", library = ":go_default_library",
deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], deps = [
"//pkg/api:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
],
) )
filegroup( filegroup(

View File

@ -0,0 +1,407 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package securitycontext
import (
"reflect"
"k8s.io/kubernetes/pkg/api"
)
// PodSecurityContextAccessor allows reading the values of a PodSecurityContext object
type PodSecurityContextAccessor interface {
HostNetwork() bool
HostPID() bool
HostIPC() bool
SELinuxOptions() *api.SELinuxOptions
RunAsUser() *int64
RunAsNonRoot() *bool
SupplementalGroups() []int64
FSGroup() *int64
}
// PodSecurityContextMutator allows reading and writing the values of a PodSecurityContext object
type PodSecurityContextMutator interface {
PodSecurityContextAccessor
SetHostNetwork(bool)
SetHostPID(bool)
SetHostIPC(bool)
SetSELinuxOptions(*api.SELinuxOptions)
SetRunAsUser(*int64)
SetRunAsNonRoot(*bool)
SetSupplementalGroups([]int64)
SetFSGroup(*int64)
// PodSecurityContext returns the current PodSecurityContext object
PodSecurityContext() *api.PodSecurityContext
}
// NewPodSecurityContextAccessor returns an accessor for the given pod security context.
// May be initialized with a nil PodSecurityContext.
func NewPodSecurityContextAccessor(podSC *api.PodSecurityContext) PodSecurityContextAccessor {
return &podSecurityContextWrapper{podSC: podSC}
}
// NewPodSecurityContextMutator returns a mutator for the given pod security context.
// May be initialized with a nil PodSecurityContext.
func NewPodSecurityContextMutator(podSC *api.PodSecurityContext) PodSecurityContextMutator {
return &podSecurityContextWrapper{podSC: podSC}
}
type podSecurityContextWrapper struct {
podSC *api.PodSecurityContext
}
func (w *podSecurityContextWrapper) PodSecurityContext() *api.PodSecurityContext {
return w.podSC
}
func (w *podSecurityContextWrapper) ensurePodSC() {
if w.podSC == nil {
w.podSC = &api.PodSecurityContext{}
}
}
func (w *podSecurityContextWrapper) HostNetwork() bool {
if w.podSC == nil {
return false
}
return w.podSC.HostNetwork
}
func (w *podSecurityContextWrapper) SetHostNetwork(v bool) {
if w.podSC == nil && v == false {
return
}
w.ensurePodSC()
w.podSC.HostNetwork = v
}
func (w *podSecurityContextWrapper) HostPID() bool {
if w.podSC == nil {
return false
}
return w.podSC.HostPID
}
func (w *podSecurityContextWrapper) SetHostPID(v bool) {
if w.podSC == nil && v == false {
return
}
w.ensurePodSC()
w.podSC.HostPID = v
}
func (w *podSecurityContextWrapper) HostIPC() bool {
if w.podSC == nil {
return false
}
return w.podSC.HostIPC
}
func (w *podSecurityContextWrapper) SetHostIPC(v bool) {
if w.podSC == nil && v == false {
return
}
w.ensurePodSC()
w.podSC.HostIPC = v
}
func (w *podSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
if w.podSC == nil {
return nil
}
return w.podSC.SELinuxOptions
}
func (w *podSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
if w.podSC == nil && v == nil {
return
}
w.ensurePodSC()
w.podSC.SELinuxOptions = v
}
func (w *podSecurityContextWrapper) RunAsUser() *int64 {
if w.podSC == nil {
return nil
}
return w.podSC.RunAsUser
}
func (w *podSecurityContextWrapper) SetRunAsUser(v *int64) {
if w.podSC == nil && v == nil {
return
}
w.ensurePodSC()
w.podSC.RunAsUser = v
}
func (w *podSecurityContextWrapper) RunAsNonRoot() *bool {
if w.podSC == nil {
return nil
}
return w.podSC.RunAsNonRoot
}
func (w *podSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
if w.podSC == nil && v == nil {
return
}
w.ensurePodSC()
w.podSC.RunAsNonRoot = v
}
func (w *podSecurityContextWrapper) SupplementalGroups() []int64 {
if w.podSC == nil {
return nil
}
return w.podSC.SupplementalGroups
}
func (w *podSecurityContextWrapper) SetSupplementalGroups(v []int64) {
if w.podSC == nil && len(v) == 0 {
return
}
w.ensurePodSC()
if len(v) == 0 && len(w.podSC.SupplementalGroups) == 0 {
return
}
w.podSC.SupplementalGroups = v
}
func (w *podSecurityContextWrapper) FSGroup() *int64 {
if w.podSC == nil {
return nil
}
return w.podSC.FSGroup
}
func (w *podSecurityContextWrapper) SetFSGroup(v *int64) {
if w.podSC == nil && v == nil {
return
}
w.ensurePodSC()
w.podSC.FSGroup = v
}
type ContainerSecurityContextAccessor interface {
Capabilities() *api.Capabilities
Privileged() *bool
SELinuxOptions() *api.SELinuxOptions
RunAsUser() *int64
RunAsNonRoot() *bool
ReadOnlyRootFilesystem() *bool
AllowPrivilegeEscalation() *bool
}
type ContainerSecurityContextMutator interface {
ContainerSecurityContextAccessor
ContainerSecurityContext() *api.SecurityContext
SetCapabilities(*api.Capabilities)
SetPrivileged(*bool)
SetSELinuxOptions(*api.SELinuxOptions)
SetRunAsUser(*int64)
SetRunAsNonRoot(*bool)
SetReadOnlyRootFilesystem(*bool)
SetAllowPrivilegeEscalation(*bool)
}
func NewContainerSecurityContextAccessor(containerSC *api.SecurityContext) ContainerSecurityContextAccessor {
return &containerSecurityContextWrapper{containerSC: containerSC}
}
func NewContainerSecurityContextMutator(containerSC *api.SecurityContext) ContainerSecurityContextMutator {
return &containerSecurityContextWrapper{containerSC: containerSC}
}
type containerSecurityContextWrapper struct {
containerSC *api.SecurityContext
}
func (w *containerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext {
return w.containerSC
}
func (w *containerSecurityContextWrapper) ensureContainerSC() {
if w.containerSC == nil {
w.containerSC = &api.SecurityContext{}
}
}
func (w *containerSecurityContextWrapper) Capabilities() *api.Capabilities {
if w.containerSC == nil {
return nil
}
return w.containerSC.Capabilities
}
func (w *containerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.Capabilities = v
}
func (w *containerSecurityContextWrapper) Privileged() *bool {
if w.containerSC == nil {
return nil
}
return w.containerSC.Privileged
}
func (w *containerSecurityContextWrapper) SetPrivileged(v *bool) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.Privileged = v
}
func (w *containerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
if w.containerSC == nil {
return nil
}
return w.containerSC.SELinuxOptions
}
func (w *containerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.SELinuxOptions = v
}
func (w *containerSecurityContextWrapper) RunAsUser() *int64 {
if w.containerSC == nil {
return nil
}
return w.containerSC.RunAsUser
}
func (w *containerSecurityContextWrapper) SetRunAsUser(v *int64) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.RunAsUser = v
}
func (w *containerSecurityContextWrapper) RunAsNonRoot() *bool {
if w.containerSC == nil {
return nil
}
return w.containerSC.RunAsNonRoot
}
func (w *containerSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.RunAsNonRoot = v
}
func (w *containerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool {
if w.containerSC == nil {
return nil
}
return w.containerSC.ReadOnlyRootFilesystem
}
func (w *containerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.ReadOnlyRootFilesystem = v
}
func (w *containerSecurityContextWrapper) AllowPrivilegeEscalation() *bool {
if w.containerSC == nil {
return nil
}
return w.containerSC.AllowPrivilegeEscalation
}
func (w *containerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) {
if w.containerSC == nil && v == nil {
return
}
w.ensureContainerSC()
w.containerSC.AllowPrivilegeEscalation = v
}
func NewEffectiveContainerSecurityContextAccessor(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextAccessor {
return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC}
}
func NewEffectiveContainerSecurityContextMutator(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextMutator {
return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC}
}
type effectiveContainerSecurityContextWrapper struct {
podSC PodSecurityContextAccessor
containerSC ContainerSecurityContextMutator
}
func (w *effectiveContainerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext {
return w.containerSC.ContainerSecurityContext()
}
func (w *effectiveContainerSecurityContextWrapper) Capabilities() *api.Capabilities {
return w.containerSC.Capabilities()
}
func (w *effectiveContainerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) {
if !reflect.DeepEqual(w.Capabilities(), v) {
w.containerSC.SetCapabilities(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) Privileged() *bool {
return w.containerSC.Privileged()
}
func (w *effectiveContainerSecurityContextWrapper) SetPrivileged(v *bool) {
if !reflect.DeepEqual(w.Privileged(), v) {
w.containerSC.SetPrivileged(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
if v := w.containerSC.SELinuxOptions(); v != nil {
return v
}
return w.podSC.SELinuxOptions()
}
func (w *effectiveContainerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
if !reflect.DeepEqual(w.SELinuxOptions(), v) {
w.containerSC.SetSELinuxOptions(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) RunAsUser() *int64 {
if v := w.containerSC.RunAsUser(); v != nil {
return v
}
return w.podSC.RunAsUser()
}
func (w *effectiveContainerSecurityContextWrapper) SetRunAsUser(v *int64) {
if !reflect.DeepEqual(w.RunAsUser(), v) {
w.containerSC.SetRunAsUser(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) RunAsNonRoot() *bool {
if v := w.containerSC.RunAsNonRoot(); v != nil {
return v
}
return w.podSC.RunAsNonRoot()
}
func (w *effectiveContainerSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
if !reflect.DeepEqual(w.RunAsNonRoot(), v) {
w.containerSC.SetRunAsNonRoot(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool {
return w.containerSC.ReadOnlyRootFilesystem()
}
func (w *effectiveContainerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) {
if !reflect.DeepEqual(w.ReadOnlyRootFilesystem(), v) {
w.containerSC.SetReadOnlyRootFilesystem(v)
}
}
func (w *effectiveContainerSecurityContextWrapper) AllowPrivilegeEscalation() *bool {
return w.containerSC.AllowPrivilegeEscalation()
}
func (w *effectiveContainerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) {
if !reflect.DeepEqual(w.AllowPrivilegeEscalation(), v) {
w.containerSC.SetAllowPrivilegeEscalation(v)
}
}

View File

@ -0,0 +1,726 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package securitycontext
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/api"
)
func TestPodSecurityContextAccessor(t *testing.T) {
fsGroup := int64(2)
runAsUser := int64(1)
runAsNonRoot := true
testcases := []*api.PodSecurityContext{
nil,
{},
{FSGroup: &fsGroup},
{HostIPC: true},
{HostNetwork: true},
{HostPID: true},
{RunAsNonRoot: &runAsNonRoot},
{RunAsUser: &runAsUser},
{SELinuxOptions: &api.SELinuxOptions{User: "bob"}},
{SupplementalGroups: []int64{1, 2, 3}},
}
for i, tc := range testcases {
expected := tc
if expected == nil {
expected = &api.PodSecurityContext{}
}
a := NewPodSecurityContextAccessor(tc)
if v := a.FSGroup(); !reflect.DeepEqual(expected.FSGroup, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.FSGroup, v)
}
if v := a.HostIPC(); !reflect.DeepEqual(expected.HostIPC, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.HostIPC, v)
}
if v := a.HostNetwork(); !reflect.DeepEqual(expected.HostNetwork, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.HostNetwork, v)
}
if v := a.HostPID(); !reflect.DeepEqual(expected.HostPID, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.HostPID, v)
}
if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v)
}
if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v)
}
if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v)
}
if v := a.SupplementalGroups(); !reflect.DeepEqual(expected.SupplementalGroups, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.SupplementalGroups, v)
}
}
}
func TestPodSecurityContextMutator(t *testing.T) {
testcases := map[string]struct {
newSC func() *api.PodSecurityContext
}{
"nil": {
newSC: func() *api.PodSecurityContext { return nil },
},
"zero": {
newSC: func() *api.PodSecurityContext { return &api.PodSecurityContext{} },
},
"populated": {
newSC: func() *api.PodSecurityContext {
return &api.PodSecurityContext{
HostNetwork: true,
HostIPC: true,
HostPID: true,
SELinuxOptions: &api.SELinuxOptions{},
RunAsUser: nil,
RunAsNonRoot: nil,
SupplementalGroups: nil,
FSGroup: nil,
}
},
},
}
nonNilSC := func(sc *api.PodSecurityContext) *api.PodSecurityContext {
if sc == nil {
return &api.PodSecurityContext{}
}
return sc
}
for k, tc := range testcases {
{
sc := tc.newSC()
originalSC := tc.newSC()
m := NewPodSecurityContextMutator(sc)
// no-op sets should not modify the object
m.SetFSGroup(m.FSGroup())
m.SetHostNetwork(m.HostNetwork())
m.SetHostIPC(m.HostIPC())
m.SetHostPID(m.HostPID())
m.SetRunAsNonRoot(m.RunAsNonRoot())
m.SetRunAsUser(m.RunAsUser())
m.SetSELinuxOptions(m.SELinuxOptions())
m.SetSupplementalGroups(m.SupplementalGroups())
if !reflect.DeepEqual(sc, originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC)
}
if !reflect.DeepEqual(m.PodSecurityContext(), originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.PodSecurityContext(), originalSC)
}
}
// FSGroup
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
i := int64(1123)
modifiedSC.FSGroup = &i
m.SetFSGroup(&i)
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// HostNetwork
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
modifiedSC.HostNetwork = !modifiedSC.HostNetwork
m.SetHostNetwork(!m.HostNetwork())
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// HostIPC
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
modifiedSC.HostIPC = !modifiedSC.HostIPC
m.SetHostIPC(!m.HostIPC())
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// HostPID
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
modifiedSC.HostPID = !modifiedSC.HostPID
m.SetHostPID(!m.HostPID())
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// RunAsNonRoot
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
b := true
modifiedSC.RunAsNonRoot = &b
m.SetRunAsNonRoot(&b)
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// RunAsUser
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
i := int64(1123)
modifiedSC.RunAsUser = &i
m.SetRunAsUser(&i)
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// SELinuxOptions
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"}
m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"})
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
// SupplementalGroups
{
modifiedSC := nonNilSC(tc.newSC())
m := NewPodSecurityContextMutator(tc.newSC())
modifiedSC.SupplementalGroups = []int64{1, 1, 2, 3}
m.SetSupplementalGroups([]int64{1, 1, 2, 3})
if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext()))
continue
}
}
}
}
func TestContainerSecurityContextAccessor(t *testing.T) {
privileged := true
runAsUser := int64(1)
runAsNonRoot := true
readOnlyRootFilesystem := true
allowPrivilegeEscalation := true
testcases := []*api.SecurityContext{
nil,
{},
{Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}},
{Privileged: &privileged},
{SELinuxOptions: &api.SELinuxOptions{User: "bob"}},
{RunAsUser: &runAsUser},
{RunAsNonRoot: &runAsNonRoot},
{ReadOnlyRootFilesystem: &readOnlyRootFilesystem},
{AllowPrivilegeEscalation: &allowPrivilegeEscalation},
}
for i, tc := range testcases {
expected := tc
if expected == nil {
expected = &api.SecurityContext{}
}
a := NewContainerSecurityContextAccessor(tc)
if v := a.Capabilities(); !reflect.DeepEqual(expected.Capabilities, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.Capabilities, v)
}
if v := a.Privileged(); !reflect.DeepEqual(expected.Privileged, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.Privileged, v)
}
if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v)
}
if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v)
}
if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v)
}
if v := a.ReadOnlyRootFilesystem(); !reflect.DeepEqual(expected.ReadOnlyRootFilesystem, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.ReadOnlyRootFilesystem, v)
}
if v := a.AllowPrivilegeEscalation(); !reflect.DeepEqual(expected.AllowPrivilegeEscalation, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.AllowPrivilegeEscalation, v)
}
}
}
func TestContainerSecurityContextMutator(t *testing.T) {
testcases := map[string]struct {
newSC func() *api.SecurityContext
}{
"nil": {
newSC: func() *api.SecurityContext { return nil },
},
"zero": {
newSC: func() *api.SecurityContext { return &api.SecurityContext{} },
},
"populated": {
newSC: func() *api.SecurityContext {
return &api.SecurityContext{
Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}},
SELinuxOptions: &api.SELinuxOptions{},
}
},
},
}
nonNilSC := func(sc *api.SecurityContext) *api.SecurityContext {
if sc == nil {
return &api.SecurityContext{}
}
return sc
}
for k, tc := range testcases {
{
sc := tc.newSC()
originalSC := tc.newSC()
m := NewContainerSecurityContextMutator(sc)
// no-op sets should not modify the object
m.SetAllowPrivilegeEscalation(m.AllowPrivilegeEscalation())
m.SetCapabilities(m.Capabilities())
m.SetPrivileged(m.Privileged())
m.SetReadOnlyRootFilesystem(m.ReadOnlyRootFilesystem())
m.SetRunAsNonRoot(m.RunAsNonRoot())
m.SetRunAsUser(m.RunAsUser())
m.SetSELinuxOptions(m.SELinuxOptions())
if !reflect.DeepEqual(sc, originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC)
}
if !reflect.DeepEqual(m.ContainerSecurityContext(), originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.ContainerSecurityContext(), originalSC)
}
}
// AllowPrivilegeEscalation
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
b := true
modifiedSC.AllowPrivilegeEscalation = &b
m.SetAllowPrivilegeEscalation(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// Capabilities
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
modifiedSC.Capabilities = &api.Capabilities{Drop: []api.Capability{"test2"}}
m.SetCapabilities(&api.Capabilities{Drop: []api.Capability{"test2"}})
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// Privileged
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
b := true
modifiedSC.Privileged = &b
m.SetPrivileged(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// ReadOnlyRootFilesystem
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
b := true
modifiedSC.ReadOnlyRootFilesystem = &b
m.SetReadOnlyRootFilesystem(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// RunAsNonRoot
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
b := true
modifiedSC.RunAsNonRoot = &b
m.SetRunAsNonRoot(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// RunAsUser
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
i := int64(1123)
modifiedSC.RunAsUser = &i
m.SetRunAsUser(&i)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// SELinuxOptions
{
modifiedSC := nonNilSC(tc.newSC())
m := NewContainerSecurityContextMutator(tc.newSC())
modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"}
m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"})
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
}
}
func TestEffectiveContainerSecurityContextAccessor(t *testing.T) {
privileged := true
runAsUser := int64(1)
runAsUserPod := int64(12)
runAsNonRoot := true
runAsNonRootPod := false
readOnlyRootFilesystem := true
allowPrivilegeEscalation := true
testcases := []struct {
PodSC *api.PodSecurityContext
SC *api.SecurityContext
Effective *api.SecurityContext
}{
{
PodSC: nil,
SC: nil,
Effective: nil,
},
{
PodSC: &api.PodSecurityContext{},
SC: &api.SecurityContext{},
Effective: &api.SecurityContext{},
},
{
PodSC: &api.PodSecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
RunAsUser: &runAsUser,
RunAsNonRoot: &runAsNonRoot,
},
SC: nil,
Effective: &api.SecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
RunAsUser: &runAsUser,
RunAsNonRoot: &runAsNonRoot,
},
},
{
PodSC: &api.PodSecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
RunAsUser: &runAsUserPod,
RunAsNonRoot: &runAsNonRootPod,
},
SC: &api.SecurityContext{},
Effective: &api.SecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
RunAsUser: &runAsUserPod,
RunAsNonRoot: &runAsNonRootPod,
},
},
{
PodSC: &api.PodSecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
RunAsUser: &runAsUserPod,
RunAsNonRoot: &runAsNonRootPod,
},
SC: &api.SecurityContext{
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}},
Privileged: &privileged,
ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
RunAsUser: &runAsUser,
RunAsNonRoot: &runAsNonRoot,
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
},
Effective: &api.SecurityContext{
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}},
Privileged: &privileged,
ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
RunAsUser: &runAsUser,
RunAsNonRoot: &runAsNonRoot,
SELinuxOptions: &api.SELinuxOptions{User: "bob"},
},
},
}
for i, tc := range testcases {
expected := tc.Effective
if expected == nil {
expected = &api.SecurityContext{}
}
a := NewEffectiveContainerSecurityContextAccessor(
NewPodSecurityContextAccessor(tc.PodSC),
NewContainerSecurityContextMutator(tc.SC),
)
if v := a.Capabilities(); !reflect.DeepEqual(expected.Capabilities, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.Capabilities, v)
}
if v := a.Privileged(); !reflect.DeepEqual(expected.Privileged, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.Privileged, v)
}
if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v)
}
if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v)
}
if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v)
}
if v := a.ReadOnlyRootFilesystem(); !reflect.DeepEqual(expected.ReadOnlyRootFilesystem, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.ReadOnlyRootFilesystem, v)
}
if v := a.AllowPrivilegeEscalation(); !reflect.DeepEqual(expected.AllowPrivilegeEscalation, v) {
t.Errorf("%d: expected %#v, got %#v", i, expected.AllowPrivilegeEscalation, v)
}
}
}
func TestEffectiveContainerSecurityContextMutator(t *testing.T) {
runAsNonRootPod := false
runAsUserPod := int64(12)
testcases := map[string]struct {
newPodSC func() *api.PodSecurityContext
newSC func() *api.SecurityContext
}{
"nil": {
newPodSC: func() *api.PodSecurityContext { return nil },
newSC: func() *api.SecurityContext { return nil },
},
"zero": {
newPodSC: func() *api.PodSecurityContext { return &api.PodSecurityContext{} },
newSC: func() *api.SecurityContext { return &api.SecurityContext{} },
},
"populated pod sc": {
newPodSC: func() *api.PodSecurityContext {
return &api.PodSecurityContext{
SELinuxOptions: &api.SELinuxOptions{User: "poduser"},
RunAsNonRoot: &runAsNonRootPod,
RunAsUser: &runAsUserPod,
}
},
newSC: func() *api.SecurityContext {
return &api.SecurityContext{}
},
},
"populated sc": {
newPodSC: func() *api.PodSecurityContext { return nil },
newSC: func() *api.SecurityContext {
return &api.SecurityContext{
Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}},
SELinuxOptions: &api.SELinuxOptions{},
}
},
},
}
nonNilSC := func(sc *api.SecurityContext) *api.SecurityContext {
if sc == nil {
return &api.SecurityContext{}
}
return sc
}
for k, tc := range testcases {
{
podSC := tc.newPodSC()
sc := tc.newSC()
originalPodSC := tc.newPodSC()
originalSC := tc.newSC()
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(podSC),
NewContainerSecurityContextMutator(sc),
)
// no-op sets should not modify the object
m.SetAllowPrivilegeEscalation(m.AllowPrivilegeEscalation())
m.SetCapabilities(m.Capabilities())
m.SetPrivileged(m.Privileged())
m.SetReadOnlyRootFilesystem(m.ReadOnlyRootFilesystem())
m.SetRunAsNonRoot(m.RunAsNonRoot())
m.SetRunAsUser(m.RunAsUser())
m.SetSELinuxOptions(m.SELinuxOptions())
if !reflect.DeepEqual(podSC, originalPodSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, podSC, originalPodSC)
}
if !reflect.DeepEqual(sc, originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC)
}
if !reflect.DeepEqual(m.ContainerSecurityContext(), originalSC) {
t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.ContainerSecurityContext(), originalSC)
}
}
// AllowPrivilegeEscalation
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
b := true
modifiedSC.AllowPrivilegeEscalation = &b
m.SetAllowPrivilegeEscalation(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// Capabilities
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
modifiedSC.Capabilities = &api.Capabilities{Drop: []api.Capability{"test2"}}
m.SetCapabilities(&api.Capabilities{Drop: []api.Capability{"test2"}})
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// Privileged
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
b := true
modifiedSC.Privileged = &b
m.SetPrivileged(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// ReadOnlyRootFilesystem
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
b := true
modifiedSC.ReadOnlyRootFilesystem = &b
m.SetReadOnlyRootFilesystem(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// RunAsNonRoot
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
b := true
modifiedSC.RunAsNonRoot = &b
m.SetRunAsNonRoot(&b)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// RunAsUser
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
i := int64(1123)
modifiedSC.RunAsUser = &i
m.SetRunAsUser(&i)
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
// SELinuxOptions
{
modifiedSC := nonNilSC(tc.newSC())
m := NewEffectiveContainerSecurityContextMutator(
NewPodSecurityContextAccessor(tc.newPodSC()),
NewContainerSecurityContextMutator(tc.newSC()),
)
modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"}
m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"})
if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) {
t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext()))
continue
}
}
}
}

View File

@ -16,12 +16,12 @@ go_library(
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/extensions/internalversion:go_default_library", "//pkg/client/listers/extensions/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/security/podsecuritypolicy:go_default_library", "//pkg/security/podsecuritypolicy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/serviceaccount:go_default_library", "//pkg/serviceaccount:go_default_library",
"//pkg/util/maps:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
@ -48,6 +48,8 @@ go_test(
"//pkg/security/podsecuritypolicy/seccomp:go_default_library", "//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",

View File

@ -19,10 +19,12 @@ package podsecuritypolicy
import ( import (
"fmt" "fmt"
"io" "io"
"sort"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
@ -34,11 +36,10 @@ import (
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
sc "k8s.io/kubernetes/pkg/securitycontext"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util/maps"
) )
const ( const (
@ -120,8 +121,17 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
} }
pod, ok := a.GetObject().(*api.Pod) pod, ok := a.GetObject().(*api.Pod)
// if we can't convert then we don't handle this object so just return // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object.
// this shouldn't normally happen during admission but could happen if an integrator passes a versioned
// pod object rather than an internal object.
if !ok { if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
}
// if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this
// and we should allow it in general, since you had the power to update and the power to delete.
// The worst that happens is that you delete something, but you aren't controlling the privileged object itself
if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) {
return nil return nil
} }
@ -143,32 +153,70 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
return nil return nil
} }
// sort by name to make order deterministic
// TODO(liggitt): add priority field to allow admins to bucket differently
sort.SliceStable(matchedPolicies, func(i, j int) bool {
return strings.Compare(matchedPolicies[i].Name, matchedPolicies[j].Name) < 0
})
providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace)
logProviders(pod, providers, errs) logProviders(a, pod, providers, errs)
if len(providers) == 0 { if len(providers) == 0 {
return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request"))
} }
// TODO(liggitt): allow spec mutation during initializing updates?
specMutationAllowed := a.GetOperation() == admission.Create
// all containers in a single pod must validate under a single provider or we will reject the request // all containers in a single pod must validate under a single provider or we will reject the request
validationErrs := field.ErrorList{} validationErrs := field.ErrorList{}
var (
allowedPod *api.Pod
allowingProvider psp.Provider
)
loop:
for _, provider := range providers { for _, provider := range providers {
if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { podCopy := pod.DeepCopy()
if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 {
validationErrs = append(validationErrs, errs...) validationErrs = append(validationErrs, errs...)
continue continue
} }
// the entire pod validated, annotate and accept the pod // the entire pod validated
glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetPSPName())
switch {
case apiequality.Semantic.DeepEqual(pod, podCopy):
// if it validated without mutating anything, use this result
allowedPod = podCopy
allowingProvider = provider
break loop
case specMutationAllowed && allowedPod == nil:
// if mutation is allowed and this is the first PSP to allow the pod, remember it,
// but continue to see if another PSP allows without mutating
allowedPod = podCopy
allowingProvider = provider
glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s with mutation", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName())
case !specMutationAllowed:
glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s, but required mutation, skipping", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName())
}
}
if allowedPod != nil {
*pod = *allowedPod
// annotate and accept the pod
glog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), allowingProvider.GetPSPName())
if pod.ObjectMeta.Annotations == nil { if pod.ObjectMeta.Annotations == nil {
pod.ObjectMeta.Annotations = map[string]string{} pod.ObjectMeta.Annotations = map[string]string{}
} }
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName() pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName()
return nil return nil
} }
// we didn't validate against any provider, reject the pod and give the errors for each attempt // we didn't validate against any provider, reject the pod and give the errors for each attempt
glog.V(4).Infof("unable to validate pod %s (generate: %s) against any pod security policy: %v", pod.Name, pod.GenerateName, validationErrs) glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs)
return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs))
} }
@ -176,82 +224,43 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
// and validates that the sc falls within the psp constraints. All containers must validate against // and validates that the sc falls within the psp constraints. All containers must validate against
// the same psp or is not considered valid. // the same psp or is not considered valid.
func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList { func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList {
generatedSCs := make([]*api.SecurityContext, len(pod.Spec.Containers))
var generatedInitSCs []*api.SecurityContext
errs := field.ErrorList{} errs := field.ErrorList{}
psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod) psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error()))
} }
// save the original PSC and validate the generated PSC. Leave the generated PSC
// set for container generation/validation. We will reset to original post container
// validation.
originalPSC := pod.Spec.SecurityContext
pod.Spec.SecurityContext = psc pod.Spec.SecurityContext = psc
originalAnnotations := maps.CopySS(pod.Annotations)
pod.Annotations = pscAnnotations pod.Annotations = pscAnnotations
errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...) errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...)
// Note: this is not changing the original container, we will set container SCs later so long for i := range pod.Spec.InitContainers {
// as all containers validated under the same PSP. sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i])
for i, containerCopy := range pod.Spec.InitContainers {
// We will determine the effective security context for the container and validate against that
// since that is how the sc provider will eventually apply settings in the runtime.
// This results in an SC that is based on the Pod's PSC with the set fields from the container
// overriding pod level settings.
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error()))
continue continue
} }
generatedInitSCs = append(generatedInitSCs, sc) pod.Spec.InitContainers[i].SecurityContext = sc
containerCopy.SecurityContext = sc
pod.Annotations = scAnnotations pod.Annotations = scAnnotations
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...) errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.InitContainers[i], field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...)
} }
// Note: this is not changing the original container, we will set container SCs later so long for i := range pod.Spec.Containers {
// as all containers validated under the same PSP. sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i])
for i, containerCopy := range pod.Spec.Containers {
// We will determine the effective security context for the container and validate against that
// since that is how the sc provider will eventually apply settings in the runtime.
// This results in an SC that is based on the Pod's PSC with the set fields from the container
// overriding pod level settings.
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
if err != nil { if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error())) errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error()))
continue continue
} }
generatedSCs[i] = sc
containerCopy.SecurityContext = sc pod.Spec.Containers[i].SecurityContext = sc
pod.Annotations = scAnnotations pod.Annotations = scAnnotations
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "containers").Index(i).Child("securityContext"))...) errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[i], field.NewPath("spec", "containers").Index(i).Child("securityContext"))...)
} }
if len(errs) > 0 { if len(errs) > 0 {
// ensure psc is not mutated if there are errors
pod.Spec.SecurityContext = originalPSC
pod.Annotations = originalAnnotations
return errs return errs
} }
// if we've reached this code then we've generated and validated an SC for every container in the
// pod so let's apply what we generated. Note: the psc is already applied.
for i, sc := range generatedInitSCs {
pod.Spec.InitContainers[i].SecurityContext = sc
}
for i, sc := range generatedSCs {
pod.Spec.Containers[i].SecurityContext = sc
}
return nil return nil
} }
@ -328,13 +337,13 @@ func buildAttributes(info user.Info, namespace string, policy *extensions.PodSec
// logProviders logs what providers were found for the pod as well as any errors that were encountered // logProviders logs what providers were found for the pod as well as any errors that were encountered
// while creating providers. // while creating providers.
func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { func logProviders(a admission.Attributes, pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) {
for _, err := range providerCreationErrs { for _, err := range providerCreationErrs {
glog.V(4).Infof("provider creation error: %v", err) glog.V(4).Infof("provider creation error: %v", err)
} }
if len(providers) == 0 { if len(providers) == 0 {
glog.V(4).Infof("unable to validate pod %s (generate: %s) against any provider.", pod.Name, pod.GenerateName) glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any provider.", pod.Name, pod.GenerateName, a.GetNamespace())
return return
} }
@ -342,5 +351,5 @@ func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs [
for i, p := range providers { for i, p := range providers {
names[i] = p.GetPSPName() names[i] = p.GetPSPName()
} }
glog.V(4).Infof("validating pod %s (generate: %s) against providers: %s", pod.Name, pod.GenerateName, strings.Join(names, ",")) glog.V(4).Infof("validating pod %s (generate: %s) in namespace %s against providers: %s", pod.Name, pod.GenerateName, a.GetNamespace(), strings.Join(names, ","))
} }

View File

@ -24,6 +24,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@ -48,7 +50,7 @@ const defaultContainerName = "test-c"
// an authorizer that always returns true. // an authorizer that always returns true.
func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface { func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface {
return &podSecurityPolicyPlugin{ return &podSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create), Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update),
strategyFactory: kpsp.NewSimpleStrategyFactory(), strategyFactory: kpsp.NewSimpleStrategyFactory(),
pspMatcher: getMatchingPolicies, pspMatcher: getMatchingPolicies,
authz: &TestAuthorizer{}, authz: &TestAuthorizer{},
@ -204,37 +206,54 @@ func TestAdmitPrivileged(t *testing.T) {
privilegedPSP.Name = "priv" privilegedPSP.Name = "priv"
privilegedPSP.Spec.Privileged = true privilegedPSP.Spec.Privileged = true
trueValue := true
falseValue := false
tests := map[string]struct { tests := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedPriv bool expectedPriv *bool
expectedPSP string expectedPSP string
}{ }{
"pod without priv request allowed under non priv PSP": { "pod with priv=nil allowed under non priv PSP": {
pod: goodPod(), pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP},
shouldPass: true, shouldPass: true,
expectedPriv: false, expectedPriv: nil,
expectedPSP: nonPrivilegedPSP.Name, expectedPSP: nonPrivilegedPSP.Name,
}, },
"pod without priv request allowed under priv PSP": { "pod with priv=nil allowed under priv PSP": {
pod: goodPod(), pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{privilegedPSP}, psps: []*extensions.PodSecurityPolicy{privilegedPSP},
shouldPass: true, shouldPass: true,
expectedPriv: false, expectedPriv: nil,
expectedPSP: privilegedPSP.Name, expectedPSP: privilegedPSP.Name,
}, },
"pod with priv request denied by non priv PSP": { "pod with priv=false allowed under non priv PSP": {
pod: createPodWithPriv(false),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP},
shouldPass: true,
expectedPriv: &falseValue,
expectedPSP: nonPrivilegedPSP.Name,
},
"pod with priv=false allowed under priv PSP": {
pod: createPodWithPriv(false),
psps: []*extensions.PodSecurityPolicy{privilegedPSP},
shouldPass: true,
expectedPriv: &falseValue,
expectedPSP: privilegedPSP.Name,
},
"pod with priv=true denied by non priv PSP": {
pod: createPodWithPriv(true), pod: createPodWithPriv(true),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP},
shouldPass: false, shouldPass: false,
}, },
"pod with priv request allowed by priv PSP": { "pod with priv=true allowed by priv PSP": {
pod: createPodWithPriv(true), pod: createPodWithPriv(true),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP}, psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP},
shouldPass: true, shouldPass: true,
expectedPriv: true, expectedPriv: &trueValue,
expectedPSP: privilegedPSP.Name, expectedPSP: privilegedPSP.Name,
}, },
} }
@ -243,14 +262,183 @@ func TestAdmitPrivileged(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.Privileged == nil || priv := v.pod.Spec.Containers[0].SecurityContext.Privileged
*v.pod.Spec.Containers[0].SecurityContext.Privileged != v.expectedPriv { if (priv == nil) != (v.expectedPriv == nil) {
t.Errorf("%s expected privileged to be %t", k, v.expectedPriv) t.Errorf("%s expected privileged to be %v, got %v", k, v.expectedPriv, priv)
} else if priv != nil && *priv != *v.expectedPriv {
t.Errorf("%s expected privileged to be %v, got %v", k, *v.expectedPriv, *priv)
} }
} }
} }
} }
func defaultPod(t *testing.T, pod *kapi.Pod) *kapi.Pod {
v1Pod := &v1.Pod{}
if err := kapi.Scheme.Convert(pod, v1Pod, nil); err != nil {
t.Fatal(err)
}
kapi.Scheme.Default(v1Pod)
apiPod := &kapi.Pod{}
if err := kapi.Scheme.Convert(v1Pod, apiPod, nil); err != nil {
t.Fatal(err)
}
return apiPod
}
func TestAdmitPreferNonmutating(t *testing.T) {
mutating1 := restrictivePSP()
mutating1.Name = "mutating1"
mutating1.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(1), Max: int64(1)}}
mutating2 := restrictivePSP()
mutating2.Name = "mutating2"
mutating2.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(2), Max: int64(2)}}
privilegedPSP := permissivePSP()
privilegedPSP.Name = "privileged"
unprivilegedRunAsAnyPod := defaultPod(t, &kapi.Pod{
ObjectMeta: metav1.ObjectMeta{},
Spec: kapi.PodSpec{
ServiceAccountName: "default",
Containers: []kapi.Container{{Name: "mycontainer", Image: "myimage"}},
},
})
changedPod := unprivilegedRunAsAnyPod.DeepCopy()
changedPod.Spec.Containers[0].Image = "myimage2"
gcChangedPod := unprivilegedRunAsAnyPod.DeepCopy()
gcChangedPod.OwnerReferences = []metav1.OwnerReference{{Kind: "Foo", Name: "bar"}}
gcChangedPod.Finalizers = []string{"foo"}
tests := map[string]struct {
operation kadmission.Operation
pod *kapi.Pod
oldPod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectMutation bool
expectedPodUser *int64
expectedContainerUser *int64
expectedPSP string
}{
"pod should not be mutated by allow-all strategies": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should prefer non-mutating PSP on create": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should use deterministic mutating PSP on create": {
operation: kadmission.Create,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: true,
expectedPodUser: nil,
expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min,
expectedPSP: mutating1.Name,
},
"pod should prefer non-mutating PSP on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: changedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: privilegedPSP.Name,
},
"pod should not allow mutation on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: changedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: false,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
"pod should be allowed if completely unchanged on update": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: unprivilegedRunAsAnyPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
"pod should be allowed if unchanged on update except finalizers,ownerrefs": {
operation: kadmission.Update,
pod: unprivilegedRunAsAnyPod.DeepCopy(),
oldPod: gcChangedPod.DeepCopy(),
psps: []*extensions.PodSecurityPolicy{mutating2, mutating1},
shouldPass: true,
expectMutation: false,
expectedPodUser: nil,
expectedContainerUser: nil,
expectedPSP: "",
},
}
for k, v := range tests {
testPSPAdmitAdvanced(k, v.operation, v.psps, v.pod, v.oldPod, v.shouldPass, v.expectMutation, v.expectedPSP, t)
if v.shouldPass {
actualPodUser := (*int64)(nil)
if v.pod.Spec.SecurityContext != nil {
actualPodUser = v.pod.Spec.SecurityContext.RunAsUser
}
if (actualPodUser == nil) != (v.expectedPodUser == nil) {
t.Errorf("%s expected pod user %v, got %v", k, v.expectedPodUser, actualPodUser)
} else if actualPodUser != nil && *actualPodUser != *v.expectedPodUser {
t.Errorf("%s expected pod user %v, got %v", k, *v.expectedPodUser, *actualPodUser)
}
actualContainerUser := (*int64)(nil)
if v.pod.Spec.Containers[0].SecurityContext != nil {
actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser
}
if (actualContainerUser == nil) != (v.expectedContainerUser == nil) {
t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser)
} else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser {
t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser)
}
}
}
}
func TestFailClosedOnInvalidPod(t *testing.T) {
plugin := NewTestAdmission(nil)
pod := &v1.Pod{}
attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{})
err := plugin.Admit(attrs)
if err == nil {
t.Fatalf("expected versioned pod object to fail admission")
}
if !strings.Contains(err.Error(), "unexpected type") {
t.Errorf("expected type error but got: %v", err)
}
}
func TestAdmitCaps(t *testing.T) { func TestAdmitCaps(t *testing.T) {
createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod {
pod := goodPod() pod := goodPod()
@ -674,22 +862,23 @@ func TestAdmitHostIPC(t *testing.T) {
} }
} }
func TestAdmitSELinux(t *testing.T) { func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod {
createPodWithSELinux := func(opts *kapi.SELinuxOptions) *kapi.Pod {
pod := goodPod() pod := goodPod()
// doesn't matter if we set it here or on the container, the pod.Spec.SecurityContext = podSC
// admission controller uses DetermineEffectiveSC to get the defaulting pod.Spec.Containers[0].SecurityContext = containerSC
// behavior so it can validate what will be applied at runtime
pod.Spec.SecurityContext.SELinuxOptions = opts
return pod return pod
} }
runAsAny := restrictivePSP() func TestAdmitSELinux(t *testing.T) {
runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny
runAsAny.Spec.SELinux.SELinuxOptions = nil
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs
mustRunAs.Spec.SELinux.SELinuxOptions = &kapi.SELinuxOptions{}
mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level" mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level"
mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role" mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role"
mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type" mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type"
@ -699,40 +888,98 @@ func TestAdmitSELinux(t *testing.T) {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedSELinux *kapi.SELinuxOptions expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny with no pod request": { "runAsAny with no request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSELinux: nil, expectedPodSC: nil,
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny with empty pod request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{},
expectedContainerSC: nil,
expectedPSP: runAsAny.Name,
},
"runAsAny with empty container request": {
pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{},
expectedPSP: runAsAny.Name,
},
"runAsAny with pod request": { "runAsAny with pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSELinux: &kapi.SELinuxOptions{User: "foo"}, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny with container request": {
pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedPSP: runAsAny.Name,
},
"runAsAny with pod and container request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}},
expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedPSP: runAsAny.Name,
},
"mustRunAs with bad pod request": { "mustRunAs with bad pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs with no pod request": { "mustRunAs with bad container request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs with no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs with good pod request": { "mustRunAs with good pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}), pod: createPodWithSecurityContexts(
&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}},
nil,
),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name,
},
"mustRunAs with good container request": {
pod: createPodWithSecurityContexts(
&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}},
nil,
),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
} }
@ -741,20 +988,11 @@ func TestAdmitSELinux(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux == nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
// ok, don't need to worry about identifying specific diffs t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
} }
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux != nil { if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s expected selinux to be: %v but found nil", k, v.expectedSELinux) t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
continue
}
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions != nil && v.expectedSELinux == nil {
t.Errorf("%s expected selinux to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions)
continue
}
if !reflect.DeepEqual(*v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions) {
t.Errorf("%s expected selinux to be: %v but found %v", k, *v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions)
} }
} }
} }
@ -840,84 +1078,138 @@ func TestAdmitAppArmor(t *testing.T) {
} }
func TestAdmitRunAsUser(t *testing.T) { func TestAdmitRunAsUser(t *testing.T) {
createPodWithRunAsUser := func(user int64) *kapi.Pod { podSC := func(user *int64) *kapi.PodSecurityContext {
pod := goodPod() return &kapi.PodSecurityContext{RunAsUser: user}
// doesn't matter if we set it here or on the container, the }
// admission controller uses DetermineEffectiveSC to get the defaulting containerSC := func(user *int64) *kapi.SecurityContext {
// behavior so it can validate what will be applied at runtime return &kapi.SecurityContext{RunAsUser: user}
userID := int64(user)
pod.Spec.SecurityContext.RunAsUser = &userID
return pod
} }
runAsAny := restrictivePSP() runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs
mustRunAs.Spec.RunAsUser.Ranges = []extensions.UserIDRange{
{Min: int64(999), Max: int64(1000)},
}
runAsNonRoot := restrictivePSP() runAsNonRoot := permissivePSP()
runAsNonRoot.Name = "runAsNonRoot" runAsNonRoot.Name = "runAsNonRoot"
runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot
trueValue := true
tests := map[string]struct { tests := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedRunAsUser *int64 expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny no pod request": { "runAsAny no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedRunAsUser: nil, expectedPodSC: nil,
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny pod request": { "runAsAny pod request": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedRunAsUser: userIDPtr(1), expectedPodSC: podSC(userIDPtr(1)),
expectedContainerSC: nil,
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny container request": {
pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(1))),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: containerSC(userIDPtr(1)),
expectedPSP: runAsAny.Name,
},
"mustRunAs pod request out of range": { "mustRunAs pod request out of range": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs container request out of range": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1))),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs pod request in range": { "mustRunAs pod request in range": {
pod: createPodWithRunAsUser(999), pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs no pod request": { "mustRunAs container request in range": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, expectedPodSC: nil,
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"runAsNonRoot no pod request": { "mustRunAs pod and container request in range": {
pod: goodPod(), pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1000))),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: podSC(userIDPtr(999)),
expectedContainerSC: containerSC(userIDPtr(1000)),
expectedPSP: mustRunAs.Name,
},
"mustRunAs no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedPSP: mustRunAs.Name,
},
"runAsNonRoot no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true, shouldPass: true,
expectedRunAsUser: nil, expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue},
expectedPSP: runAsNonRoot.Name, expectedPSP: runAsNonRoot.Name,
}, },
"runAsNonRoot pod request root": { "runAsNonRoot pod request root": {
pod: createPodWithRunAsUser(0), pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: false, shouldPass: false,
}, },
"runAsNonRoot pod request non-root": { "runAsNonRoot pod request non-root": {
pod: createPodWithRunAsUser(1), pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true, shouldPass: true,
expectedRunAsUser: userIDPtr(1), expectedPodSC: podSC(userIDPtr(1)),
expectedPSP: runAsNonRoot.Name,
},
"runAsNonRoot container request root": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(0))),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: false,
},
"runAsNonRoot container request non-root": {
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(2))),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true,
expectedPodSC: podSC(userIDPtr(1)),
expectedContainerSC: containerSC(userIDPtr(2)),
expectedPSP: runAsNonRoot.Name, expectedPSP: runAsNonRoot.Name,
}, },
} }
@ -926,81 +1218,82 @@ func TestAdmitRunAsUser(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser == nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
// ok, don't need to worry about identifying specific diffs t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
} }
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser != nil { if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s expected RunAsUser to be: %v but found nil", k, v.expectedRunAsUser) t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
continue
}
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser != nil && v.expectedRunAsUser == nil {
t.Errorf("%s expected RunAsUser to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser)
continue
}
if *v.expectedRunAsUser != *v.pod.Spec.Containers[0].SecurityContext.RunAsUser {
t.Errorf("%s expected RunAsUser to be: %v but found %v", k, *v.expectedRunAsUser, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser)
} }
} }
} }
} }
func TestAdmitSupplementalGroups(t *testing.T) { func TestAdmitSupplementalGroups(t *testing.T) {
createPodWithSupGroup := func(group int64) *kapi.Pod { podSC := func(group int64) *kapi.PodSecurityContext {
pod := goodPod() return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}}
// doesn't matter if we set it here or on the container, the
// admission controller uses DetermineEffectiveSC to get the defaulting
// behavior so it can validate what will be applied at runtime
groupID := int64(group)
pod.Spec.SecurityContext.SupplementalGroups = []int64{groupID}
return pod
} }
runAsAny := restrictivePSP() runAsAny := permissivePSP()
runAsAny.Name = "runAsAny" runAsAny.Name = "runAsAny"
runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny
mustRunAs := restrictivePSP() mustRunAs := permissivePSP()
mustRunAs.Name = "mustRunAs" mustRunAs.Name = "mustRunAs"
mustRunAs.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyMustRunAs
mustRunAs.Spec.SupplementalGroups.Ranges = []extensions.GroupIDRange{{Min: int64(999), Max: int64(1000)}}
tests := map[string]struct { tests := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
psps []*extensions.PodSecurityPolicy psps []*extensions.PodSecurityPolicy
shouldPass bool shouldPass bool
expectedSupGroups []int64 expectedPodSC *kapi.PodSecurityContext
expectedPSP string expectedPSP string
}{ }{
"runAsAny no pod request": { "runAsAny no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{}, expectedPodSC: nil,
expectedPSP: runAsAny.Name,
},
"runAsAny empty pod request": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{},
expectedPSP: runAsAny.Name,
},
"runAsAny empty pod request empty supplemental groups": {
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}},
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"runAsAny pod request": { "runAsAny pod request": {
pod: createPodWithSupGroup(1), pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny}, psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{1}, expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}},
expectedPSP: runAsAny.Name, expectedPSP: runAsAny.Name,
}, },
"mustRunAs no pod request": { "mustRunAs no pod request": {
pod: goodPod(), pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{mustRunAs.Spec.SupplementalGroups.Ranges[0].Min}, expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
"mustRunAs bad pod request": { "mustRunAs bad pod request": {
pod: createPodWithSupGroup(1), pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false, shouldPass: false,
}, },
"mustRunAs good pod request": { "mustRunAs good pod request": {
pod: createPodWithSupGroup(999), pod: createPodWithSecurityContexts(podSC(999), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs}, psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true, shouldPass: true,
expectedSupGroups: []int64{999}, expectedPodSC: podSC(999),
expectedPSP: mustRunAs.Name, expectedPSP: mustRunAs.Name,
}, },
} }
@ -1009,16 +1302,8 @@ func TestAdmitSupplementalGroups(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass { if v.shouldPass {
if v.pod.Spec.SecurityContext.SupplementalGroups == nil && v.expectedSupGroups != nil { if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
t.Errorf("%s expected SupplementalGroups to be: %v but found nil", k, v.expectedSupGroups) t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
continue
}
if v.pod.Spec.SecurityContext.SupplementalGroups != nil && v.expectedSupGroups == nil {
t.Errorf("%s expected SupplementalGroups to be nil but found: %v", k, v.pod.Spec.SecurityContext.SupplementalGroups)
continue
}
if !reflect.DeepEqual(v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups) {
t.Errorf("%s expected SupplementalGroups to be: %v but found %v", k, v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups)
} }
} }
} }
@ -1353,6 +1638,10 @@ func TestAdmitSysctls(t *testing.T) {
} }
func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) { func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) {
testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, pod, nil, shouldPass, true, expectedPSP, t)
}
func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, pod, oldPod *kapi.Pod, shouldPass bool, canMutate bool, expectedPSP string, t *testing.T) {
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore()
@ -1360,9 +1649,11 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
store.Add(psp) store.Add(psp)
} }
originalPod := pod.DeepCopy()
plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister())
attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{})
err := plugin.Admit(attrs) err := plugin.Admit(attrs)
if shouldPass && err != nil { if shouldPass && err != nil {
@ -1373,6 +1664,18 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP { if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP {
t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation])
} }
if !canMutate {
podWithoutPSPAnnotation := pod.DeepCopy()
delete(podWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation)
originalPodWithoutPSPAnnotation := originalPod.DeepCopy()
delete(originalPodWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation)
if !apiequality.Semantic.DeepEqual(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec) {
t.Errorf("%s: expected no mutation, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec))
}
}
} }
if !shouldPass && err == nil { if !shouldPass && err == nil {
@ -1396,10 +1699,6 @@ func TestAssignSecurityContext(t *testing.T) {
} }
} }
// these are set up such that the containers always have a nil uid. If the case should not
// validate then the uids should not have been updated by the strategy. If the case should
// validate then uids should be set. This is ensuring that we're hanging on to the old SC
// as we generate/validate and only updating the original container if the entire pod validates
testCases := map[string]struct { testCases := map[string]struct {
pod *kapi.Pod pod *kapi.Pod
shouldValidate bool shouldValidate bool
@ -1445,24 +1744,6 @@ func TestAssignSecurityContext(t *testing.T) {
t.Errorf("%s expected validation errors but received none", k) t.Errorf("%s expected validation errors but received none", k)
continue continue
} }
// if we shouldn't have validated ensure that uid is not set on the containers
if !v.shouldValidate {
for _, c := range v.pod.Spec.Containers {
if c.SecurityContext.RunAsUser != nil {
t.Errorf("%s had non-nil UID %d. UID should not be set on test cases that don't validate", k, *c.SecurityContext.RunAsUser)
}
}
}
// if we validated then the pod sc should be updated now with the defaults from the psp
if v.shouldValidate {
for _, c := range v.pod.Spec.Containers {
if *c.SecurityContext.RunAsUser != 999 {
t.Errorf("%s expected uid to be defaulted to 999 but found %v", k, *c.SecurityContext.RunAsUser)
}
}
}
} }
} }
@ -1746,6 +2027,36 @@ func restrictivePSP() *extensions.PodSecurityPolicy {
} }
} }
func permissivePSP() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "privileged",
Annotations: map[string]string{},
},
Spec: extensions.PodSecurityPolicySpec{
AllowPrivilegeEscalation: true,
HostIPC: true,
HostNetwork: true,
HostPID: true,
HostPorts: []extensions.HostPortRange{{Min: 0, Max: 65536}},
Volumes: []extensions.FSType{extensions.All},
AllowedCapabilities: []kapi.Capability{extensions.AllowAllCapabilities},
RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyRunAsAny,
},
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny,
},
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny,
},
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyRunAsAny,
},
},
}
}
func createNamespaceForTest() *kapi.Namespace { func createNamespaceForTest() *kapi.Namespace {
return &kapi.Namespace{ return &kapi.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{