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
import (
"reflect"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
@ -25,6 +27,10 @@ import (
// IsOnlyMutatingGCFields checks finalizers and ownerrefs which GC manipulates
// and indicates that only those fields are changing
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
copied := obj.DeepCopyObject()
copiedMeta, err := meta.Accessor(copied)

View File

@ -128,6 +128,19 @@ func TestIsOnlyMutatingGCFields(t *testing.T) {
},
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 {

View File

@ -26,6 +26,7 @@ go_library(
"//pkg/security/podsecuritypolicy/sysctl:go_default_library",
"//pkg/security/podsecuritypolicy/user:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext: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/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
// container specifically is dropping the cap) and container requested adds
// 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) {
defaultAdd := makeCapSet(s.defaultAddCapabilities)
requiredDrop := makeCapSet(s.requiredDropCapabilities)
containerAdd := sets.NewString()
containerDrop := sets.NewString()
var containerCapabilities *api.Capabilities
if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil {
containerCapabilities = container.SecurityContext.Capabilities
containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add)
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
defaultAdd = defaultAdd.Difference(containerDrop)
combinedAdd := defaultAdd.Union(containerAdd).List()
combinedDrop := requiredDrop.Union(containerDrop).List()
combinedAdd := defaultAdd.Union(containerAdd)
combinedDrop := requiredDrop.Union(containerDrop)
// nothing generated? return nil
if len(combinedAdd) == 0 && len(combinedDrop) == 0 {
return nil, nil
// no changes? return the original capabilities
if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) {
return containerCapabilities, nil
}
return &api.Capabilities{
Add: capabilityFromStringSlice(combinedAdd),
Drop: capabilityFromStringSlice(combinedDrop),
Add: capabilityFromStringSlice(combinedAdd.List()),
Drop: capabilityFromStringSlice(combinedDrop.List()),
}, nil
}
// 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{}
// if the security context isn't set then we haven't generated correctly. Shouldn't get here
// 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 capabilities == nil {
// 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
// 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
// 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"))
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
defaultAdd := makeCapSet(s.defaultAddCapabilities)
for _, cap := range container.SecurityContext.Capabilities.Add {
for _, cap := range capabilities.Add {
sCap := string(cap)
if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) {
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
containerDrops := makeCapSet(container.SecurityContext.Capabilities.Drop)
containerDrops := makeCapSet(capabilities.Drop)
for _, requiredDrop := range s.requiredDropCapabilities {
sDrop := string(requiredDrop)
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)))
}
}

View File

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

View File

@ -26,5 +26,5 @@ type Strategy interface {
// Generate creates the capabilities based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error)
// 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
// 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
}
// 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).
func (s *mustRunAs) GenerateSingle(pod *api.Pod) (*int64, error) {
func (s *mustRunAs) GenerateSingle(_ *api.Pod) (*int64, error) {
single := new(int64)
*single = s.ranges[0].Min
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.
// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and
// 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{}
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 {
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) {
validPod := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
SecurityContext: &api.PodSecurityContext{},
},
}
}
tests := map[string]struct {
ranges []extensions.GroupIDRange
pod *api.Pod
@ -124,19 +116,16 @@ func TestValidate(t *testing.T) {
pass bool
}{
"nil security context": {
pod: &api.Pod{},
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
},
},
"empty groups": {
pod: validPod(),
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
},
},
"not in range": {
pod: validPod(),
groups: []int64{5},
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
@ -144,7 +133,6 @@ func TestValidate(t *testing.T) {
},
},
"in range 1": {
pod: validPod(),
groups: []int64{2},
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
@ -152,7 +140,6 @@ func TestValidate(t *testing.T) {
pass: true,
},
"in range boundry min": {
pod: validPod(),
groups: []int64{1},
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
@ -160,7 +147,6 @@ func TestValidate(t *testing.T) {
pass: true,
},
"in range boundry max": {
pod: validPod(),
groups: []int64{3},
ranges: []extensions.GroupIDRange{
{Min: 1, Max: 3},
@ -168,7 +154,6 @@ func TestValidate(t *testing.T) {
pass: true,
},
"singular range": {
pod: validPod(),
groups: []int64{4},
ranges: []extensions.GroupIDRange{
{Min: 4, Max: 4},
@ -182,7 +167,7 @@ func TestValidate(t *testing.T) {
if err != nil {
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 {
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.
func (s *runAsAny) Generate(pod *api.Pod) ([]int64, error) {
return []int64{}, nil
func (s *runAsAny) Generate(_ *api.Pod) ([]int64, error) {
return nil, 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
}
// 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{}
}

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/securitycontext"
"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
// 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.
//
// 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) {
var sc *api.PodSecurityContext = nil
if pod.Spec.SecurityContext != nil {
// work with a copy
copy := *pod.Spec.SecurityContext
sc = &copy
} else {
sc = &api.PodSecurityContext{}
}
sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext)
annotations := maps.CopySS(pod.Annotations)
if len(sc.SupplementalGroups) == 0 {
if sc.SupplementalGroups() == nil {
supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod)
if err != nil {
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)
if err != nil {
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)
if err != nil {
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
@ -116,40 +107,34 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit
}
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
// 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.
//
// 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) {
var sc *api.SecurityContext = nil
if container.SecurityContext != nil {
// work with a copy of the original
copy := *container.SecurityContext
sc = &copy
} else {
sc = &api.SecurityContext{}
}
sc := securitycontext.NewEffectiveContainerSecurityContextMutator(
securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext),
securitycontext.NewContainerSecurityContextMutator(container.SecurityContext),
)
annotations := maps.CopySS(pod.Annotations)
if sc.RunAsUser == nil {
if sc.RunAsUser() == nil {
uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container)
if err != nil {
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)
if err != nil {
return nil, nil, err
}
sc.SELinuxOptions = seLinux
sc.SetSELinuxOptions(seLinux)
}
annotations, err := s.strategies.AppArmorStrategy.Generate(annotations, container)
@ -157,82 +142,67 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
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
// 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.
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
sc.RunAsNonRoot = &nonRoot
sc.SetRunAsNonRoot(&nonRoot)
}
caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container)
if err != nil {
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
// request then default ReadOnlyRootFilesystem to true.
if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem == nil {
if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem() == nil {
readOnlyRootFS := true
sc.ReadOnlyRootFilesystem = &readOnlyRootFS
sc.SetReadOnlyRootFilesystem(&readOnlyRootFS)
}
// if the PSP sets DefaultAllowPrivilegeEscalation and the container security context
// allowPrivilegeEscalation is not set, then default to that set by the PSP.
if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation == nil {
sc.AllowPrivilegeEscalation = s.psp.Spec.DefaultAllowPrivilegeEscalation
if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation() == nil {
sc.SetAllowPrivilegeEscalation(s.psp.Spec.DefaultAllowPrivilegeEscalation)
}
// if the PSP sets psp.AllowPrivilegeEscalation to false set that as the default
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation == nil {
sc.AllowPrivilegeEscalation = &s.psp.Spec.AllowPrivilegeEscalation
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation() == nil {
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.
func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if pod.Spec.SecurityContext == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), pod.Spec.SecurityContext, "No security context is set"))
return allErrs
}
sc := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
fsGroups := []int64{}
if pod.Spec.SecurityContext.FSGroup != nil {
fsGroups = append(fsGroups, *pod.Spec.SecurityContext.FSGroup)
if fsGroup := sc.FSGroup(); fsGroup != nil {
fsGroups = append(fsGroups, *fsGroup)
}
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)...)
// make a dummy container context to reuse the selinux strategies
container := &api.Container{
Name: pod.Name,
SecurityContext: &api.SecurityContext{
SELinuxOptions: pod.Spec.SecurityContext.SELinuxOptions,
},
}
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...)
if !s.psp.Spec.HostNetwork && pod.Spec.SecurityContext.HostNetwork {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used"))
if !s.psp.Spec.HostNetwork && sc.HostNetwork() {
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 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used"))
if !s.psp.Spec.HostPID && sc.HostPID() {
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 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used"))
if !s.psp.Spec.HostIPC && sc.HostIPC() {
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)...)
@ -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 {
allErrs := field.ErrorList{}
if container.SecurityContext == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("securityContext"), container.SecurityContext, "No security context is set"))
return allErrs
}
podSC := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext))
sc := container.SecurityContext
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(fldPath.Child("securityContext"), pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...)
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...)
if !s.psp.Spec.Privileged && *sc.Privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed"))
privileged := sc.Privileged()
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 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), pod.Spec.SecurityContext.HostNetwork, "Host network is not allowed to be used"))
if !s.psp.Spec.HostNetwork && podSC.HostNetwork() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), podSC.HostNetwork(), "Host network is not allowed to be used"))
}
containersPath := fldPath.Child("containers")
@ -306,29 +274,30 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe
allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
}
if !s.psp.Spec.HostPID && pod.Spec.SecurityContext.HostPID {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), pod.Spec.SecurityContext.HostPID, "Host PID is not allowed to be used"))
if !s.psp.Spec.HostPID && podSC.HostPID() {
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 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used"))
if !s.psp.Spec.HostIPC && podSC.HostIPC() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), podSC.HostIPC(), "Host IPC is not allowed to be used"))
}
if s.psp.Spec.ReadOnlyRootFilesystem {
if sc.ReadOnlyRootFilesystem == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem may not be nil and must be set to true"))
} else if !*sc.ReadOnlyRootFilesystem {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), *sc.ReadOnlyRootFilesystem, "ReadOnlyRootFilesystem must be set to true"))
readOnly := sc.ReadOnlyRootFilesystem()
if readOnly == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), readOnly, "ReadOnlyRootFilesystem may not be nil and 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 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), sc.AllowPrivilegeEscalation, "Allowing privilege escalation for containers is not allowed"))
allowEscalation := sc.AllowPrivilegeEscalation()
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 {
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 && *allowEscalation {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), *allowEscalation, "Allowing privilege escalation for containers is not allowed"))
}
return allErrs

View File

@ -55,30 +55,21 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
Name: "psp-sa",
Annotations: map[string]string{
seccomp.AllowedProfilesAnnotationKey: "*",
seccomp.DefaultProfileAnnotationKey: "foo",
},
},
Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"},
RequiredDropCapabilities: []api.Capability{"bar"},
AllowPrivilegeEscalation: true,
RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyRunAsAny,
},
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyRunAsAny,
},
// these are pod mutating strategies that are tested above
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyMustRunAs,
Ranges: []extensions.GroupIDRange{
{Min: 1, Max: 1},
},
Rule: extensions.FSGroupStrategyRunAsAny,
},
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
Rule: extensions.SupplementalGroupsStrategyMustRunAs,
Ranges: []extensions.GroupIDRange{
{Min: 1, Max: 1},
},
Rule: extensions.SupplementalGroupsStrategyRunAsAny,
},
},
}
@ -91,17 +82,13 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
if err != nil {
t.Fatalf("unable to create provider %v", err)
}
sc, _, err := provider.CreatePodSecurityContext(pod)
_, _, err = provider.CreatePodSecurityContext(pod)
if err != nil {
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
// since all the strategies were permissive
if !reflect.DeepEqual(createPod(), pod) {
diffs := diff.ObjectDiff(createPod(), pod)
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
createPSP := func() *extensions.PodSecurityPolicy {
uid := int64(1)
return &extensions.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "psp-sa",
@ -144,25 +130,19 @@ func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
},
},
Spec: extensions.PodSecurityPolicySpec{
DefaultAddCapabilities: []api.Capability{"foo"},
RequiredDropCapabilities: []api.Capability{"bar"},
AllowPrivilegeEscalation: true,
RunAsUser: extensions.RunAsUserStrategyOptions{
Rule: extensions.RunAsUserStrategyMustRunAs,
Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}},
Rule: extensions.RunAsUserStrategyRunAsAny,
},
SELinux: extensions.SELinuxStrategyOptions{
Rule: extensions.SELinuxStrategyMustRunAs,
SELinuxOptions: &api.SELinuxOptions{User: "you"},
Rule: extensions.SELinuxStrategyRunAsAny,
},
// these are pod mutating strategies that are tested above
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny,
},
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{
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 {
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 {
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
// since all the strategies were permissive
if !reflect.DeepEqual(createPod(), pod) {
diffs := diff.ObjectDiff(createPod(), pod)
t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs)
@ -323,12 +299,12 @@ func TestValidatePodSecurityContextFailures(t *testing.T) {
"failNilSELinux": {
pod: failNilSELinuxPod,
psp: failSELinuxPSP,
expectedError: "unable to validate nil seLinuxOptions",
expectedError: "seLinuxOptions: Required",
},
"failInvalidSELinux": {
pod: failInvalidSELinuxPod,
psp: failSELinuxPSP,
expectedError: "does not match required level. Found bar, wanted foo",
expectedError: "seLinuxOptions.level: Invalid value",
},
"failHostDirPSP": {
pod: failHostDirPod,
@ -455,12 +431,12 @@ func TestValidateContainerSecurityContextFailures(t *testing.T) {
"failUserPSP": {
pod: failUserPod,
psp: failUserPSP,
expectedError: "does not match required range",
expectedError: "runAsUser: Invalid value",
},
"failSELinuxPSP": {
pod: failSELinuxPod,
psp: failSELinuxPSP,
expectedError: "does not match required level",
expectedError: "seLinuxOptions.level: Invalid value",
},
"failNilAppArmor": {
pod: failNilAppArmorPod,

View File

@ -43,41 +43,33 @@ func NewMustRunAs(options *extensions.SELinuxStrategyOptions) (SELinuxStrategy,
}
// 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
}
// 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{}
if container.SecurityContext == nil {
detail := fmt.Sprintf("unable to validate nil security context for %s", container.Name)
allErrs = append(allErrs, field.Invalid(field.NewPath("securityContext"), container.SecurityContext, detail))
if seLinux == nil {
allErrs = append(allErrs, field.Required(fldPath, ""))
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 {
detail := fmt.Sprintf("seLinuxOptions.level on %s does not match required level. Found %s, wanted %s", container.Name, seLinux.Level, s.opts.SELinuxOptions.Level)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("level"), seLinux.Level, detail))
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Level)
allErrs = append(allErrs, field.Invalid(fldPath.Child("level"), seLinux.Level, detail))
}
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)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("role"), seLinux.Role, detail))
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Role)
allErrs = append(allErrs, field.Invalid(fldPath.Child("role"), seLinux.Role, detail))
}
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)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("type"), seLinux.Type, detail))
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Type)
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), seLinux.Type, detail))
}
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)
allErrs = append(allErrs, field.Invalid(seLinuxOptionsPath.Child("user"), seLinux.User, detail))
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.User)
allErrs = append(allErrs, field.Invalid(fldPath.Child("user"), seLinux.User, detail))
}
return allErrs

View File

@ -100,19 +100,19 @@ func TestMustRunAsValidate(t *testing.T) {
}{
"invalid role": {
seLinux: role,
expectedMsg: "does not match required role",
expectedMsg: "role: Invalid value",
},
"invalid user": {
seLinux: user,
expectedMsg: "does not match required user",
expectedMsg: "user: Invalid value",
},
"invalid level": {
seLinux: level,
expectedMsg: "does not match required level",
expectedMsg: "level: Invalid value",
},
"invalid type": {
seLinux: seType,
expectedMsg: "does not match required type",
expectedMsg: "type: Invalid value",
},
"valid": {
seLinux: newValidOpts(),
@ -130,13 +130,8 @@ func TestMustRunAsValidate(t *testing.T) {
t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
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
if len(tc.expectedMsg) == 0 && len(errs) > 0 {
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.
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{}
}

View File

@ -58,7 +58,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs := s.Validate(nil, nil)
errs := s.Validate(nil, nil, nil, nil)
if len(errs) != 0 {
t.Errorf("unexpected errors validating with ")
}
@ -66,7 +66,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs = s.Validate(nil, nil)
errs = s.Validate(nil, nil, nil, nil)
if len(errs) != 0 {
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(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error)
// 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.
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{}
securityContextPath := field.NewPath("securityContext")
if container.SecurityContext == nil {
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))
if runAsUser == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("runAsUser"), ""))
return allErrs
}
if !s.isValidUID(*container.SecurityContext.RunAsUser) {
detail := fmt.Sprintf("UID on container %s does not match required range. Found %d, allowed: %v",
container.Name,
*container.SecurityContext.RunAsUser,
s.opts.Ranges)
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail))
if !s.isValidUID(*runAsUser) {
detail := fmt.Sprintf("must be in the ranges: %v", s.opts.Ranges)
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, detail))
}
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": {
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: nil,
},
},
expectedMsg: "unable to validate nil RunAsUser for container",
expectedMsg: "runAsUser: Required",
},
"invalid id": {
container: &api.Container{
@ -118,7 +112,7 @@ func TestValidate(t *testing.T) {
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)
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
if len(tc.expectedMsg) == 0 && len(errs) > 0 {
t.Errorf("%s expected no errors but received %v", name, errs)

View File

@ -17,8 +17,6 @@ limitations under the License.
package user
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api"
"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.
// In order to work properly this assumes that the kubelet performs a final check on runAsUser
// 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{}
securityContextPath := field.NewPath("securityContext")
if container.SecurityContext == nil {
detail := fmt.Sprintf("unable to validate nil security context for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail))
if runAsNonRoot == nil && runAsUser == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("runAsNonRoot"), "must be true"))
return allErrs
}
if container.SecurityContext.RunAsNonRoot != nil && *container.SecurityContext.RunAsNonRoot == false {
detail := fmt.Sprintf("RunAsNonRoot must be true for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsNonRoot"), *container.SecurityContext.RunAsNonRoot, detail))
if runAsNonRoot != nil && *runAsNonRoot == false {
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsNonRoot"), *runAsNonRoot, "must be true"))
return allErrs
}
if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.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(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail))
if runAsUser != nil && *runAsUser == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, "running with the root UID is forbidden"))
return allErrs
}
return allErrs

View File

@ -102,17 +102,17 @@ func TestNonRootValidate(t *testing.T) {
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsNonRoot: &unfalse,
RunAsUser: &badUID,
RunAsNonRoot: nil,
RunAsUser: nil,
},
},
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 {
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 {
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.
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{}
}

View File

@ -52,7 +52,7 @@ func TestRunAsAnyValidate(t *testing.T) {
if err != nil {
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 {
t.Errorf("unexpected errors validating with ")
}

View File

@ -26,5 +26,5 @@ type RunAsUserStrategy interface {
// Generate creates the uid based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*int64, error)
// 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(
name = "go_default_library",
srcs = [
"accessors.go",
"doc.go",
"fake.go",
"util.go",
@ -22,10 +23,17 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
srcs = [
"accessors_test.go",
"util_test.go",
],
importpath = "k8s.io/kubernetes/pkg/securitycontext",
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(

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/listers/extensions/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/registry/rbac:go_default_library",
"//pkg/security/podsecuritypolicy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//pkg/util/maps: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/util/validation/field: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/util: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/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",

View File

@ -19,10 +19,12 @@ package podsecuritypolicy
import (
"fmt"
"io"
"sort"
"strings"
"github.com/golang/glog"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
@ -34,11 +36,10 @@ import (
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
rbacregistry "k8s.io/kubernetes/pkg/registry/rbac"
psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
sc "k8s.io/kubernetes/pkg/securitycontext"
"k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util/maps"
)
const (
@ -120,8 +121,17 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
}
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 {
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
}
@ -143,32 +153,70 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
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)
logProviders(pod, providers, errs)
logProviders(a, pod, providers, errs)
if len(providers) == 0 {
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
validationErrs := field.ErrorList{}
var (
allowedPod *api.Pod
allowingProvider psp.Provider
)
loop:
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...)
continue
}
// the entire pod validated, annotate and accept the pod
glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetPSPName())
// the entire pod validated
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 {
pod.ObjectMeta.Annotations = map[string]string{}
}
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName()
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName()
return nil
}
// 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))
}
@ -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
// the same psp or is not considered valid.
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{}
psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod)
if err != nil {
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
originalAnnotations := maps.CopySS(pod.Annotations)
pod.Annotations = pscAnnotations
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
// as all containers validated under the same PSP.
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)
for i := range pod.Spec.InitContainers {
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i])
if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error()))
continue
}
generatedInitSCs = append(generatedInitSCs, sc)
containerCopy.SecurityContext = sc
pod.Spec.InitContainers[i].SecurityContext = sc
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
// as all containers validated under the same PSP.
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)
for i := range pod.Spec.Containers {
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i])
if err != nil {
errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error()))
continue
}
generatedSCs[i] = sc
containerCopy.SecurityContext = sc
pod.Spec.Containers[i].SecurityContext = sc
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 {
// ensure psc is not mutated if there are errors
pod.Spec.SecurityContext = originalPSC
pod.Annotations = originalAnnotations
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
}
@ -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
// 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 {
glog.V(4).Infof("provider creation error: %v", err)
}
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
}
@ -342,5 +351,5 @@ func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs [
for i, p := range providers {
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"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
@ -48,7 +50,7 @@ const defaultContainerName = "test-c"
// an authorizer that always returns true.
func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface {
return &podSecurityPolicyPlugin{
Handler: kadmission.NewHandler(kadmission.Create),
Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update),
strategyFactory: kpsp.NewSimpleStrategyFactory(),
pspMatcher: getMatchingPolicies,
authz: &TestAuthorizer{},
@ -204,37 +206,54 @@ func TestAdmitPrivileged(t *testing.T) {
privilegedPSP.Name = "priv"
privilegedPSP.Spec.Privileged = true
trueValue := true
falseValue := false
tests := map[string]struct {
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedPriv bool
expectedPriv *bool
expectedPSP string
}{
"pod without priv request allowed under non priv PSP": {
"pod with priv=nil allowed under non priv PSP": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP},
shouldPass: true,
expectedPriv: false,
expectedPriv: nil,
expectedPSP: nonPrivilegedPSP.Name,
},
"pod without priv request allowed under priv PSP": {
"pod with priv=nil allowed under priv PSP": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{privilegedPSP},
shouldPass: true,
expectedPriv: false,
expectedPriv: nil,
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),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP},
shouldPass: false,
},
"pod with priv request allowed by priv PSP": {
"pod with priv=true allowed by priv PSP": {
pod: createPodWithPriv(true),
psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP},
shouldPass: true,
expectedPriv: true,
expectedPriv: &trueValue,
expectedPSP: privilegedPSP.Name,
},
}
@ -243,14 +262,183 @@ func TestAdmitPrivileged(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.Privileged == nil ||
*v.pod.Spec.Containers[0].SecurityContext.Privileged != v.expectedPriv {
t.Errorf("%s expected privileged to be %t", k, v.expectedPriv)
priv := v.pod.Spec.Containers[0].SecurityContext.Privileged
if (priv == nil) != (v.expectedPriv == nil) {
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) {
createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod {
pod := goodPod()
@ -674,66 +862,125 @@ func TestAdmitHostIPC(t *testing.T) {
}
}
func TestAdmitSELinux(t *testing.T) {
createPodWithSELinux := func(opts *kapi.SELinuxOptions) *kapi.Pod {
pod := goodPod()
// 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
pod.Spec.SecurityContext.SELinuxOptions = opts
return pod
}
func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod {
pod := goodPod()
pod.Spec.SecurityContext = podSC
pod.Spec.Containers[0].SecurityContext = containerSC
return pod
}
runAsAny := restrictivePSP()
func TestAdmitSELinux(t *testing.T) {
runAsAny := permissivePSP()
runAsAny.Name = "runAsAny"
runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny
runAsAny.Spec.SELinux.SELinuxOptions = nil
mustRunAs := restrictivePSP()
mustRunAs := permissivePSP()
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.Role = "role"
mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type"
mustRunAs.Spec.SELinux.SELinuxOptions.User = "user"
tests := map[string]struct {
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedSELinux *kapi.SELinuxOptions
expectedPSP string
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string
}{
"runAsAny with no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedSELinux: nil,
expectedPSP: runAsAny.Name,
"runAsAny with no request": {
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: nil,
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": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedSELinux: &kapi.SELinuxOptions{User: "foo"},
expectedPSP: runAsAny.Name,
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}},
expectedContainerSC: nil,
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": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}),
pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs with no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions,
expectedPSP: mustRunAs.Name,
"mustRunAs with bad container request": {
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},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions},
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name,
},
"mustRunAs with good pod request": {
pod: createPodWithSELinux(&kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions,
expectedPSP: mustRunAs.Name,
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,
},
"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,
},
}
@ -741,20 +988,11 @@ func TestAdmitSELinux(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux == nil {
// ok, don't need to worry about identifying specific diffs
continue
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
}
if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux != nil {
t.Errorf("%s expected selinux to be: %v but found nil", k, v.expectedSELinux)
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)
if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
}
}
}
@ -840,85 +1078,139 @@ func TestAdmitAppArmor(t *testing.T) {
}
func TestAdmitRunAsUser(t *testing.T) {
createPodWithRunAsUser := func(user int64) *kapi.Pod {
pod := goodPod()
// 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
userID := int64(user)
pod.Spec.SecurityContext.RunAsUser = &userID
return pod
podSC := func(user *int64) *kapi.PodSecurityContext {
return &kapi.PodSecurityContext{RunAsUser: user}
}
containerSC := func(user *int64) *kapi.SecurityContext {
return &kapi.SecurityContext{RunAsUser: user}
}
runAsAny := restrictivePSP()
runAsAny := permissivePSP()
runAsAny.Name = "runAsAny"
runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny
mustRunAs := restrictivePSP()
mustRunAs := permissivePSP()
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.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot
trueValue := true
tests := map[string]struct {
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedRunAsUser *int64
expectedPSP string
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedPodSC *kapi.PodSecurityContext
expectedContainerSC *kapi.SecurityContext
expectedPSP string
}{
"runAsAny no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedRunAsUser: nil,
expectedPSP: runAsAny.Name,
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: nil,
expectedPSP: runAsAny.Name,
},
"runAsAny pod request": {
pod: createPodWithRunAsUser(1),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedRunAsUser: userIDPtr(1),
expectedPSP: runAsAny.Name,
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: podSC(userIDPtr(1)),
expectedContainerSC: nil,
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": {
pod: createPodWithRunAsUser(1),
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
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": {
pod: createPodWithRunAsUser(999),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min,
expectedPSP: mustRunAs.Name,
pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedContainerSC: nil,
expectedPSP: mustRunAs.Name,
},
"mustRunAs no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min,
expectedPSP: mustRunAs.Name,
"mustRunAs container request in range": {
pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min),
expectedPSP: mustRunAs.Name,
},
"runAsNonRoot no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true,
expectedRunAsUser: nil,
expectedPSP: runAsNonRoot.Name,
"mustRunAs pod and container request in range": {
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},
shouldPass: true,
expectedPodSC: nil,
expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue},
expectedPSP: runAsNonRoot.Name,
},
"runAsNonRoot pod request root": {
pod: createPodWithRunAsUser(0),
pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: false,
},
"runAsNonRoot pod request non-root": {
pod: createPodWithRunAsUser(1),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true,
expectedRunAsUser: userIDPtr(1),
expectedPSP: runAsNonRoot.Name,
pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil),
psps: []*extensions.PodSecurityPolicy{runAsNonRoot},
shouldPass: true,
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,
},
}
@ -926,82 +1218,83 @@ func TestAdmitRunAsUser(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass {
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser == nil {
// ok, don't need to worry about identifying specific diffs
continue
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
}
if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser != nil {
t.Errorf("%s expected RunAsUser to be: %v but found nil", k, v.expectedRunAsUser)
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)
if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) {
t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext))
}
}
}
}
func TestAdmitSupplementalGroups(t *testing.T) {
createPodWithSupGroup := func(group int64) *kapi.Pod {
pod := goodPod()
// 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
podSC := func(group int64) *kapi.PodSecurityContext {
return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}}
}
runAsAny := restrictivePSP()
runAsAny := permissivePSP()
runAsAny.Name = "runAsAny"
runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny
mustRunAs := restrictivePSP()
mustRunAs := permissivePSP()
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 {
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedSupGroups []int64
expectedPSP string
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedPodSC *kapi.PodSecurityContext
expectedPSP string
}{
"runAsAny no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedSupGroups: []int64{},
expectedPSP: runAsAny.Name,
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
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,
},
"runAsAny pod request": {
pod: createPodWithSupGroup(1),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedSupGroups: []int64{1},
expectedPSP: runAsAny.Name,
pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{runAsAny},
shouldPass: true,
expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}},
expectedPSP: runAsAny.Name,
},
"mustRunAs no pod request": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedSupGroups: []int64{mustRunAs.Spec.SupplementalGroups.Ranges[0].Min},
expectedPSP: mustRunAs.Name,
pod: createPodWithSecurityContexts(nil, nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min),
expectedPSP: mustRunAs.Name,
},
"mustRunAs bad pod request": {
pod: createPodWithSupGroup(1),
pod: createPodWithSecurityContexts(podSC(1), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: false,
},
"mustRunAs good pod request": {
pod: createPodWithSupGroup(999),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedSupGroups: []int64{999},
expectedPSP: mustRunAs.Name,
pod: createPodWithSecurityContexts(podSC(999), nil),
psps: []*extensions.PodSecurityPolicy{mustRunAs},
shouldPass: true,
expectedPodSC: podSC(999),
expectedPSP: mustRunAs.Name,
},
}
@ -1009,16 +1302,8 @@ func TestAdmitSupplementalGroups(t *testing.T) {
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass {
if v.pod.Spec.SecurityContext.SupplementalGroups == nil && v.expectedSupGroups != nil {
t.Errorf("%s expected SupplementalGroups to be: %v but found nil", k, v.expectedSupGroups)
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)
if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) {
t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext))
}
}
}
@ -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) {
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())
store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore()
@ -1360,9 +1649,11 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
store.Add(psp)
}
originalPod := pod.DeepCopy()
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)
if shouldPass && err != nil {
@ -1373,6 +1664,18 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP {
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 {
@ -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 {
pod *kapi.Pod
shouldValidate bool
@ -1445,24 +1744,6 @@ func TestAssignSecurityContext(t *testing.T) {
t.Errorf("%s expected validation errors but received none", k)
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 {
return &kapi.Namespace{
ObjectMeta: metav1.ObjectMeta{