mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +00:00
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:
commit
97808e5a86
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{}
|
||||
|
||||
}
|
||||
|
@ -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 = ©
|
||||
} 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 = ©
|
||||
} 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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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 ")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
|
407
pkg/securitycontext/accessors.go
Normal file
407
pkg/securitycontext/accessors.go
Normal 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)
|
||||
}
|
||||
}
|
726
pkg/securitycontext/accessors_test.go
Normal file
726
pkg/securitycontext/accessors_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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, ","))
|
||||
}
|
||||
|
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user