Add sysctl PodSecurityPolicy support

This commit is contained in:
Dr. Stefan Schimanski 2016-08-19 10:33:56 +02:00
parent bea189e9c9
commit ed36baed20
12 changed files with 620 additions and 7 deletions

View File

@ -0,0 +1,37 @@
/*
Copyright 2016 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 extensions
import (
"strings"
)
// SysctlsFromPodSecurityPolicyAnnotation parses an annotation value of the key
// SysctlsSecurityPolocyAnnotationKey into a slice of sysctls. An empty slice
// is returned if annotation is the empty string.
func SysctlsFromPodSecurityPolicyAnnotation(annotation string) ([]string, error) {
if len(annotation) == 0 {
return []string{}, nil
}
return strings.Split(annotation, ","), nil
}
// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls.
func PodAnnotationsFromSysctls(sysctls []string) string {
return strings.Join(sysctls, ",")
}

View File

@ -0,0 +1,62 @@
/*
Copyright 2016 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 extensions
import (
"reflect"
"testing"
)
func TestPodAnnotationsFromSysctls(t *testing.T) {
type Test struct {
sysctls []string
expectedValue string
}
for _, test := range []Test{
{sysctls: []string{"a.b"}, expectedValue: "a.b"},
{sysctls: []string{"a.b", "c.d"}, expectedValue: "a.b,c.d"},
{sysctls: []string{"a.b", "a.b"}, expectedValue: "a.b,a.b"},
{sysctls: []string{}, expectedValue: ""},
{sysctls: nil, expectedValue: ""},
} {
a := PodAnnotationsFromSysctls(test.sysctls)
if a != test.expectedValue {
t.Errorf("wrong value for %v: got=%q wanted=%q", test.sysctls, a, test.expectedValue)
}
}
}
func TestSysctlsFromPodSecurityPolicyAnnotation(t *testing.T) {
type Test struct {
expectedValue []string
annotation string
}
for _, test := range []Test{
{annotation: "a.b", expectedValue: []string{"a.b"}},
{annotation: "a.b,c.d", expectedValue: []string{"a.b", "c.d"}},
{annotation: "a.b,a.b", expectedValue: []string{"a.b", "a.b"}},
{annotation: "", expectedValue: []string{}},
} {
sysctls, err := SysctlsFromPodSecurityPolicyAnnotation(test.annotation)
if err != nil {
t.Errorf("error for %q: %v", test.annotation, err)
}
if !reflect.DeepEqual(sysctls, test.expectedValue) {
t.Errorf("wrong value for %q: got=%v wanted=%v", test.annotation, sysctls, test.expectedValue)
}
}
}

View File

@ -35,6 +35,13 @@ import (
"k8s.io/kubernetes/pkg/util/intstr"
)
const (
// SysctlsPodSecurityPolicyAnnotationKey represents the key of a whitelist of
// allowed safe and unsafe sysctls in a pod spec. It's a comma-separated list of plain sysctl
// names or sysctl patterns (which end in *). The string "*" matches all sysctls.
SysctlsPodSecurityPolicyAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
)
// describes the attributes of a scale subresource
type ScaleSpec struct {
// desired number of instances for the scaled object.

View File

@ -574,6 +574,7 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa
func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if p := annotations[apparmor.DefaultProfileAnnotationKey]; p != "" {
if err := apparmor.ValidateProfileFormat(p); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.DefaultProfileAnnotationKey), p, err.Error()))
@ -586,6 +587,16 @@ func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string,
}
}
}
sysctlAnnotation := annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey]
sysctlFldPath := fldPath.Key(extensions.SysctlsPodSecurityPolicyAnnotationKey)
sysctls, err := extensions.SysctlsFromPodSecurityPolicyAnnotation(sysctlAnnotation)
if err != nil {
allErrs = append(allErrs, field.Invalid(sysctlFldPath, sysctlAnnotation, err.Error()))
} else {
allErrs = append(allErrs, validatePodSecurityPolicySysctls(sysctlFldPath, sysctls)...)
}
return allErrs
}
@ -674,6 +685,36 @@ func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []extensions.
return allErrs
}
const sysctlPatternSegmentFmt string = "([a-z0-9][-_a-z0-9]*)?[a-z0-9*]"
const SysctlPatternFmt string = "(" + apivalidation.SysctlSegmentFmt + "\\.)*" + sysctlPatternSegmentFmt
var sysctlPatternRegexp = regexp.MustCompile("^" + SysctlPatternFmt + "$")
func IsValidSysctlPattern(name string) bool {
if len(name) > apivalidation.SysctlMaxLength {
return false
}
return sysctlPatternRegexp.MatchString(name)
}
// validatePodSecurityPolicySysctls validates the sysctls fields of PodSecurityPolicy.
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
allErrs := field.ErrorList{}
for i, s := range sysctls {
if !IsValidSysctlPattern(string(s)) {
allErrs = append(
allErrs,
field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("must have at most %d characters and match regex %s",
apivalidation.SysctlMaxLength,
SysctlPatternFmt,
)),
)
}
}
return allErrs
}
// validateIDRanges ensures the range is valid.
func validateIDRanges(fldPath *field.Path, rng extensions.IDRange) field.ErrorList {
allErrs := field.ErrorList{}

View File

@ -1512,7 +1512,8 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
validPSP := func() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Name: "foo",
Annotations: map[string]string{},
},
Spec: extensions.PodSecurityPolicySpec{
SELinux: extensions.SELinuxStrategyOptions{
@ -1596,6 +1597,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
}
invalidSysctlPattern := validPSP()
invalidSysctlPattern.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "a.*.b"
errorCases := map[string]struct {
psp *extensions.PodSecurityPolicy
errorType field.ErrorType
@ -1686,6 +1690,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
errorType: field.ErrorTypeInvalid,
errorDetail: "invalid AppArmor profile name: \"not-good\"",
},
"invalid sysctl pattern": {
psp: invalidSysctlPattern,
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
},
}
for k, v := range errorCases {
@ -1728,6 +1737,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
}
withSysctl := validPSP()
withSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "net.*"
successCases := map[string]struct {
psp *extensions.PodSecurityPolicy
}{
@ -1749,6 +1761,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
"valid AppArmor annotations": {
psp: validAppArmor,
},
"with network sysctls": {
psp: withSysctl,
},
}
for k, v := range successCases {
@ -2031,6 +2046,58 @@ func TestValidateNetworkPolicyUpdate(t *testing.T) {
}
}
func TestIsValidSysctlPattern(t *testing.T) {
valid := []string{
"a.b.c.d",
"a",
"a_b",
"a-b",
"abc",
"abc.def",
"*",
"a.*",
"*",
"abc*",
"a.abc*",
"a.b.*",
}
invalid := []string{
"",
"ä",
"a_",
"_",
"_a",
"_a._b",
"__",
"-",
".",
"a.",
".a",
"a.b.",
"a*.b",
"a*b",
"*a",
"Abc",
func(n int) string {
x := make([]byte, n)
for i := range x {
x[i] = byte('a')
}
return string(x)
}(256),
}
for _, s := range valid {
if !IsValidSysctlPattern(s) {
t.Errorf("%q expected to be a valid sysctl pattern", s)
}
}
for _, s := range invalid {
if IsValidSysctlPattern(s) {
t.Errorf("%q expected to be an invalid sysctl pattern", s)
}
}
}
func newBool(val bool) *bool {
p := new(bool)
*p = val

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
"k8s.io/kubernetes/pkg/util/errors"
)
@ -70,6 +71,19 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
errs = append(errs, err)
}
var unsafeSysctls []string
if ann, found := psp.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey]; found {
var err error
unsafeSysctls, err = extensions.SysctlsFromPodSecurityPolicyAnnotation(ann)
if err != nil {
errs = append(errs, err)
}
}
sysctlsStrat, err := createSysctlsStrategy(unsafeSysctls)
if err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, errors.NewAggregate(errs)
}
@ -81,6 +95,7 @@ func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPoli
FSGroupStrategy: fsGroupStrat,
SupplementalGroupStrategy: supGroupStrat,
CapabilitiesStrategy: capStrat,
SysctlsStrategy: sysctlsStrat,
}
return strategies, nil
@ -145,3 +160,8 @@ func createSupplementalGroupStrategy(opts *extensions.SupplementalGroupsStrategy
func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []api.Capability) (capabilities.Strategy, error) {
return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
}
// createSysctlsStrategy creates a new unsafe sysctls strategy.
func createSysctlsStrategy(sysctlsPatterns []string) (sysctl.SysctlsStrategy, error) {
return sysctl.NewMustMatchPatterns(sysctlsPatterns)
}

View File

@ -210,6 +210,8 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), pod.Spec.SecurityContext.HostIPC, "Host IPC is not allowed to be used"))
}
allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...)
return allErrs
}

View File

@ -0,0 +1,92 @@
/*
Copyright 2016 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 sysctl
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// mustMatchPatterns implements the CapabilitiesStrategy interface
type mustMatchPatterns struct {
patterns []string
}
var (
_ SysctlsStrategy = &mustMatchPatterns{}
defaultSysctlsPatterns = []string{"*"}
)
// NewMustMatchPatterns creates a new mustMatchPattern strategy that will provide validation.
// Passing nil means the default pattern, passing an empty list means to disallow all sysctls.
func NewMustMatchPatterns(patterns []string) (SysctlsStrategy, error) {
if patterns == nil {
patterns = defaultSysctlsPatterns
}
return &mustMatchPatterns{
patterns: patterns,
}, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, s.validateAnnotation(pod, api.SysctlsPodAnnotationKey)...)
allErrs = append(allErrs, s.validateAnnotation(pod, api.UnsafeSysctlsPodAnnotationKey)...)
return allErrs
}
func (s *mustMatchPatterns) validateAnnotation(pod *api.Pod, key string) field.ErrorList {
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(key)
sysctls, err := api.SysctlsFromPodAnnotation(pod.Annotations[key])
if err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath, pod.Annotations[key], err.Error()))
}
if len(sysctls) > 0 {
if len(s.patterns) == 0 {
allErrs = append(allErrs, field.Invalid(fieldPath, pod.Annotations[key], "sysctls are not allowed"))
} else {
for i, sysctl := range sysctls {
allErrs = append(allErrs, s.ValidateSysctl(sysctl.Name, fieldPath.Index(i))...)
}
}
}
return allErrs
}
func (s *mustMatchPatterns) ValidateSysctl(sysctlName string, fldPath *field.Path) field.ErrorList {
for _, s := range s.patterns {
if s[len(s)-1] == '*' {
prefix := s[:len(s)-1]
if strings.HasPrefix(sysctlName, string(prefix)) {
return nil
}
} else if sysctlName == s {
return nil
}
}
return field.ErrorList{field.Forbidden(fldPath, fmt.Sprintf("sysctl %q is not allowed", sysctlName))}
}

View File

@ -0,0 +1,106 @@
/*
Copyright 2016 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 sysctl
import (
"testing"
"k8s.io/kubernetes/pkg/api"
)
func TestValidate(t *testing.T) {
tests := map[string]struct {
patterns []string
allowed []string
disallowed []string
}{
// no container requests
"nil": {
patterns: nil,
allowed: []string{"foo"},
},
"empty": {
patterns: []string{},
disallowed: []string{"foo"},
},
"without wildcard": {
patterns: []string{"a", "a.b"},
allowed: []string{"a", "a.b"},
disallowed: []string{"b"},
},
"with catch-all wildcard": {
patterns: []string{"*"},
allowed: []string{"a", "a.b"},
},
"with catch-all wildcard and non-wildcard": {
patterns: []string{"a.b.c", "*"},
allowed: []string{"a", "a.b", "a.b.c", "b"},
},
"without catch-all wildcard": {
patterns: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"},
allowed: []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"},
disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"},
},
}
for k, v := range tests {
strategy, err := NewMustMatchPatterns(v.patterns)
if err != nil {
t.Errorf("%s failed: %v", k, err)
continue
}
pod := &api.Pod{}
errs := strategy.Validate(pod)
if len(errs) != 0 {
t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs)
}
sysctls := []api.Sysctl{}
for _, s := range v.allowed {
sysctls = append(sysctls, api.Sysctl{
Name: s,
Value: "dummy",
})
}
testAllowed := func(key string, category string) {
pod.Annotations = map[string]string{
key: api.PodAnnotationsFromSysctls(sysctls),
}
errs = strategy.Validate(pod)
if len(errs) != 0 {
t.Errorf("%s: unexpected validaton errors for %s sysctls: %v", k, category, errs)
}
}
testDisallowed := func(key string, category string) {
for _, s := range v.disallowed {
pod.Annotations = map[string]string{
key: api.PodAnnotationsFromSysctls([]api.Sysctl{{s, "dummy"}}),
}
errs = strategy.Validate(pod)
if len(errs) == 0 {
t.Errorf("%s: expected error for %s sysctl %q", k, category, s)
}
}
}
testAllowed(api.SysctlsPodAnnotationKey, "safe")
testAllowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
testDisallowed(api.SysctlsPodAnnotationKey, "safe")
testDisallowed(api.UnsafeSysctlsPodAnnotationKey, "unsafe")
}
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2016 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 sysctl
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// SysctlsStrategy defines the interface for all sysctl strategies.
type SysctlsStrategy interface {
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod) field.ErrorList
}

View File

@ -23,6 +23,7 @@ import (
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
"k8s.io/kubernetes/pkg/util/validation/field"
)
@ -63,4 +64,5 @@ type ProviderStrategies struct {
FSGroupStrategy group.GroupStrategy
SupplementalGroupStrategy group.GroupStrategy
CapabilitiesStrategy capabilities.Strategy
SysctlsStrategy sysctl.SysctlsStrategy
}

View File

@ -26,7 +26,7 @@ import (
kadmission "k8s.io/kubernetes/pkg/admission"
kapi "k8s.io/kubernetes/pkg/api"
extensions "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/client/cache"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
@ -34,7 +34,7 @@ import (
"k8s.io/kubernetes/pkg/security/apparmor"
kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
diff "k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/diff"
)
const defaultContainerName = "test-c"
@ -1028,6 +1028,151 @@ func TestAdmitReadOnlyRootFilesystem(t *testing.T) {
}
}
func TestAdmitSysctls(t *testing.T) {
podWithSysctls := func(safeSysctls []string, unsafeSysctls []string) *kapi.Pod {
pod := goodPod()
dummySysctls := func(names []string) []kapi.Sysctl {
sysctls := make([]kapi.Sysctl, len(names))
for i, n := range names {
sysctls[i].Name = n
sysctls[i].Value = "dummy"
}
return sysctls
}
pod.Annotations[kapi.SysctlsPodAnnotationKey] = kapi.PodAnnotationsFromSysctls(dummySysctls(safeSysctls))
pod.Annotations[kapi.UnsafeSysctlsPodAnnotationKey] = kapi.PodAnnotationsFromSysctls(dummySysctls(unsafeSysctls))
return pod
}
noSysctls := restrictivePSP()
noSysctls.Name = "no sysctls"
emptySysctls := restrictivePSP()
emptySysctls.Name = "empty sysctls"
emptySysctls.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = ""
mixedSysctls := restrictivePSP()
mixedSysctls.Name = "wildcard sysctls"
mixedSysctls.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "a.*,b.*,c,d.e.f"
aSysctl := restrictivePSP()
aSysctl.Name = "a sysctl"
aSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "a"
bSysctl := restrictivePSP()
bSysctl.Name = "b sysctl"
bSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "b"
cSysctl := restrictivePSP()
cSysctl.Name = "c sysctl"
cSysctl.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "c"
catchallSysctls := restrictivePSP()
catchallSysctls.Name = "catchall sysctl"
catchallSysctls.Annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey] = "*"
tests := map[string]struct {
pod *kapi.Pod
psps []*extensions.PodSecurityPolicy
shouldPass bool
expectedPSP string
}{
"pod without unsafe sysctls request allowed under noSysctls PSP": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{noSysctls},
shouldPass: true,
expectedPSP: noSysctls.Name,
},
"pod without any sysctls request allowed under emptySysctls PSP": {
pod: goodPod(),
psps: []*extensions.PodSecurityPolicy{emptySysctls},
shouldPass: true,
expectedPSP: emptySysctls.Name,
},
"pod with safe sysctls request allowed under noSysctls PSP": {
pod: podWithSysctls([]string{"a", "b"}, []string{}),
psps: []*extensions.PodSecurityPolicy{noSysctls},
shouldPass: true,
expectedPSP: noSysctls.Name,
},
"pod with unsafe sysctls request allowed under noSysctls PSP": {
pod: podWithSysctls([]string{}, []string{"a", "b"}),
psps: []*extensions.PodSecurityPolicy{noSysctls},
shouldPass: true,
expectedPSP: noSysctls.Name,
},
"pod with safe sysctls request disallowed under emptySysctls PSP": {
pod: podWithSysctls([]string{"a", "b"}, []string{}),
psps: []*extensions.PodSecurityPolicy{emptySysctls},
shouldPass: false,
},
"pod with unsafe sysctls request disallowed under emptySysctls PSP": {
pod: podWithSysctls([]string{}, []string{"a", "b"}),
psps: []*extensions.PodSecurityPolicy{emptySysctls},
shouldPass: false,
},
"pod with matching sysctls request allowed under mixedSysctls PSP": {
pod: podWithSysctls([]string{"a.b", "b.c"}, []string{"c", "d.e.f"}),
psps: []*extensions.PodSecurityPolicy{mixedSysctls},
shouldPass: true,
expectedPSP: mixedSysctls.Name,
},
"pod with not-matching unsafe sysctls request allowed under mixedSysctls PSP": {
pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f"}, []string{"e"}),
psps: []*extensions.PodSecurityPolicy{mixedSysctls},
shouldPass: false,
},
"pod with not-matching safe sysctls request allowed under mixedSysctls PSP": {
pod: podWithSysctls([]string{"a.b", "b.c", "c", "d.e.f", "e"}, []string{}),
psps: []*extensions.PodSecurityPolicy{mixedSysctls},
shouldPass: false,
},
"pod with sysctls request allowed under catchallSysctls PSP": {
pod: podWithSysctls([]string{"e"}, []string{"f"}),
psps: []*extensions.PodSecurityPolicy{catchallSysctls},
shouldPass: true,
expectedPSP: catchallSysctls.Name,
},
"pod with sysctls request allowed under catchallSysctls PSP, not under mixedSysctls or emptySysctls PSP": {
pod: podWithSysctls([]string{"e"}, []string{"f"}),
psps: []*extensions.PodSecurityPolicy{mixedSysctls, catchallSysctls, emptySysctls},
shouldPass: true,
expectedPSP: catchallSysctls.Name,
},
"pod with safe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": {
pod: podWithSysctls([]string{}, []string{"c"}),
psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl},
shouldPass: true,
expectedPSP: cSysctl.Name,
},
"pod with unsafe c sysctl request allowed under cSysctl PSP, not under aSysctl or bSysctl PSP": {
pod: podWithSysctls([]string{"c"}, []string{}),
psps: []*extensions.PodSecurityPolicy{aSysctl, bSysctl, cSysctl},
shouldPass: true,
expectedPSP: cSysctl.Name,
},
}
for k, v := range tests {
origSafeSysctls, origUnsafeSysctls, err := kapi.SysctlsFromPodAnnotations(v.pod.Annotations)
if err != nil {
t.Fatalf("invalid sysctl annotation: %v", err)
}
testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t)
if v.shouldPass {
safeSysctls, unsafeSysctls, _ := kapi.SysctlsFromPodAnnotations(v.pod.Annotations)
if !reflect.DeepEqual(safeSysctls, origSafeSysctls) {
t.Errorf("%s: wrong safe sysctls: expected=%v, got=%v", k, origSafeSysctls, safeSysctls)
}
if !reflect.DeepEqual(unsafeSysctls, origUnsafeSysctls) {
t.Errorf("%s: wrong unsafe sysctls: expected=%v, got=%v", k, origSafeSysctls, safeSysctls)
}
}
}
}
func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) {
namespace := createNamespaceForTest()
serviceAccount := createSAForTest()
@ -1044,17 +1189,17 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod
err := plugin.Admit(attrs)
if shouldPass && err != nil {
t.Errorf("%s expected no errors but received %v", testCaseName, err)
t.Errorf("%s: expected no errors but received %v", testCaseName, err)
}
if shouldPass && err == nil {
if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP {
t.Errorf("%s expected to validate under %s but found %s", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation])
t.Errorf("%s: expected to validate under %s but found %s", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation])
}
}
if !shouldPass && err == nil {
t.Errorf("%s expected errors but received none", testCaseName)
t.Errorf("%s: expected errors but received none", testCaseName)
}
}
@ -1238,7 +1383,8 @@ func TestCreateProvidersFromConstraints(t *testing.T) {
func restrictivePSP() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: kapi.ObjectMeta{
Name: "restrictive",
Name: "restrictive",
Annotations: map[string]string{},
},
Spec: extensions.PodSecurityPolicySpec{
RunAsUser: extensions.RunAsUserStrategyOptions{
@ -1291,6 +1437,9 @@ func createSAForTest() *kapi.ServiceAccount {
// psp when defaults are filled in.
func goodPod() *kapi.Pod {
return &kapi.Pod{
ObjectMeta: kapi.ObjectMeta{
Annotations: map[string]string{},
},
Spec: kapi.PodSpec{
ServiceAccountName: "default",
SecurityContext: &kapi.PodSecurityContext{},