vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -11,11 +11,18 @@ go_library(
srcs = ["validation.go"],
importpath = "k8s.io/kubernetes/pkg/apis/policy/validation",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/apis/extensions/validation:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/features:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
@ -24,7 +31,12 @@ go_test(
srcs = ["validation_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],

View File

@ -17,13 +17,25 @@ limitations under the License.
package validation
import (
"fmt"
"path/filepath"
"reflect"
"regexp"
"strings"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
core "k8s.io/kubernetes/pkg/apis/core"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
extensionsvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
func ValidatePodDisruptionBudget(pdb *policy.PodDisruptionBudget) field.ErrorList {
@ -77,3 +89,355 @@ func ValidatePodDisruptionBudgetStatus(status policy.PodDisruptionBudgetStatus,
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ExpectedPods), fldPath.Child("expectedPods"))...)
return allErrs
}
// ValidatePodSecurityPolicyName can be used to check whether the given
// pod security policy name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
var ValidatePodSecurityPolicyName = apivalidation.NameIsDNSSubdomain
func ValidatePodSecurityPolicy(psp *policy.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(psp.Annotations, field.NewPath("metadata").Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
return allErrs
}
func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...)
allErrs = append(allErrs, validatePSPSELinux(fldPath.Child("seLinux"), &spec.SELinux)...)
allErrs = append(allErrs, validatePSPSupplementalGroup(fldPath.Child("supplementalGroups"), &spec.SupplementalGroups)...)
allErrs = append(allErrs, validatePSPFSGroup(fldPath.Child("fsGroup"), &spec.FSGroup)...)
allErrs = append(allErrs, validatePodSecurityPolicyVolumes(fldPath, spec.Volumes)...)
if len(spec.RequiredDropCapabilities) > 0 && hasCap(policy.AllowAllCapabilities, spec.AllowedCapabilities) {
allErrs = append(allErrs, field.Invalid(field.NewPath("requiredDropCapabilities"), spec.RequiredDropCapabilities,
"must be empty when all capabilities are allowed by a wildcard"))
}
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.DefaultAddCapabilities, field.NewPath("defaultAddCapabilities"))...)
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.AllowedCapabilities, field.NewPath("allowedCapabilities"))...)
allErrs = append(allErrs, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...)
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
allErrs = append(allErrs, validatePSPAllowedFlexVolumes(fldPath.Child("allowedFlexVolumes"), spec.AllowedFlexVolumes)...)
allErrs = append(allErrs, validatePodSecurityPolicySysctls(fldPath.Child("allowedUnsafeSysctls"), spec.AllowedUnsafeSysctls)...)
allErrs = append(allErrs, validatePodSecurityPolicySysctls(fldPath.Child("forbiddenSysctls"), spec.ForbiddenSysctls)...)
allErrs = append(allErrs, validatePodSecurityPolicySysctlListsDoNotOverlap(fldPath.Child("allowedUnsafeSysctls"), fldPath.Child("forbiddenSysctls"), spec.AllowedUnsafeSysctls, spec.ForbiddenSysctls)...)
return allErrs
}
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()))
}
}
if allowed := annotations[apparmor.AllowedProfilesAnnotationKey]; allowed != "" {
for _, p := range strings.Split(allowed, ",") {
if err := apparmor.ValidateProfileFormat(p); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.AllowedProfilesAnnotationKey), allowed, err.Error()))
}
}
}
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
}
if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" {
for _, p := range strings.Split(allowed, ",") {
if p == seccomp.AllowAny {
continue
}
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...)
}
}
return allErrs
}
// validatePSPAllowedHostPaths makes sure all allowed host paths follow:
// 1. path prefix is required
// 2. path prefix does not have any element which is ".."
func validatePSPAllowedHostPaths(fldPath *field.Path, allowedHostPaths []policy.AllowedHostPath) field.ErrorList {
allErrs := field.ErrorList{}
for i, target := range allowedHostPaths {
if target.PathPrefix == "" {
allErrs = append(allErrs, field.Required(fldPath.Index(i), "is required"))
break
}
parts := strings.Split(filepath.ToSlash(target.PathPrefix), "/")
for _, item := range parts {
if item == ".." {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), target.PathPrefix, "must not contain '..'"))
break // even for `../../..`, one error is sufficient to make the point
}
}
}
return allErrs
}
func validatePSPAllowedFlexVolumes(fldPath *field.Path, flexVolumes []policy.AllowedFlexVolume) field.ErrorList {
allErrs := field.ErrorList{}
if len(flexVolumes) > 0 {
for idx, fv := range flexVolumes {
if len(fv.Driver) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("allowedFlexVolumes").Index(idx).Child("driver"),
"must specify a driver"))
}
}
}
return allErrs
}
// validatePSPSELinux validates the SELinux fields of PodSecurityPolicy.
func validatePSPSELinux(fldPath *field.Path, seLinux *policy.SELinuxStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
// ensure the selinux strategy has a valid rule
supportedSELinuxRules := sets.NewString(
string(policy.SELinuxStrategyMustRunAs),
string(policy.SELinuxStrategyRunAsAny),
)
if !supportedSELinuxRules.Has(string(seLinux.Rule)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), seLinux.Rule, supportedSELinuxRules.List()))
}
return allErrs
}
// validatePSPRunAsUser validates the RunAsUser fields of PodSecurityPolicy.
func validatePSPRunAsUser(fldPath *field.Path, runAsUser *policy.RunAsUserStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
// ensure the user strategy has a valid rule
supportedRunAsUserRules := sets.NewString(
string(policy.RunAsUserStrategyMustRunAs),
string(policy.RunAsUserStrategyMustRunAsNonRoot),
string(policy.RunAsUserStrategyRunAsAny),
)
if !supportedRunAsUserRules.Has(string(runAsUser.Rule)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), runAsUser.Rule, supportedRunAsUserRules.List()))
}
// validate range settings
for idx, rng := range runAsUser.Ranges {
allErrs = append(allErrs, validateUserIDRange(fldPath.Child("ranges").Index(idx), rng)...)
}
return allErrs
}
// validatePSPFSGroup validates the FSGroupStrategyOptions fields of the PodSecurityPolicy.
func validatePSPFSGroup(fldPath *field.Path, groupOptions *policy.FSGroupStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
supportedRules := sets.NewString(
string(policy.FSGroupStrategyMustRunAs),
string(policy.FSGroupStrategyRunAsAny),
)
if !supportedRules.Has(string(groupOptions.Rule)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), groupOptions.Rule, supportedRules.List()))
}
for idx, rng := range groupOptions.Ranges {
allErrs = append(allErrs, validateGroupIDRange(fldPath.Child("ranges").Index(idx), rng)...)
}
return allErrs
}
// validatePSPSupplementalGroup validates the SupplementalGroupsStrategyOptions fields of the PodSecurityPolicy.
func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.SupplementalGroupsStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
supportedRules := sets.NewString(
string(policy.SupplementalGroupsStrategyRunAsAny),
string(policy.SupplementalGroupsStrategyMustRunAs),
)
if !supportedRules.Has(string(groupOptions.Rule)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), groupOptions.Rule, supportedRules.List()))
}
for idx, rng := range groupOptions.Ranges {
allErrs = append(allErrs, validateGroupIDRange(fldPath.Child("ranges").Index(idx), rng)...)
}
return allErrs
}
// validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy.
func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSType) field.ErrorList {
allErrs := field.ErrorList{}
allowed := psputil.GetAllFSTypesAsSet()
// add in the * value since that is a pseudo type that is not included by default
allowed.Insert(string(policy.All))
for _, v := range volumes {
if !allowed.Has(string(v)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List()))
}
}
return allErrs
}
// validatePSPDefaultAllowPrivilegeEscalation validates the DefaultAllowPrivilegeEscalation field against the AllowPrivilegeEscalation field of a PodSecurityPolicy.
func validatePSPDefaultAllowPrivilegeEscalation(fldPath *field.Path, defaultAllowPrivilegeEscalation *bool, allowPrivilegeEscalation bool) field.ErrorList {
allErrs := field.ErrorList{}
if defaultAllowPrivilegeEscalation != nil && *defaultAllowPrivilegeEscalation && !allowPrivilegeEscalation {
allErrs = append(allErrs, field.Invalid(fldPath, defaultAllowPrivilegeEscalation, "Cannot set DefaultAllowPrivilegeEscalation to true without also setting AllowPrivilegeEscalation to true"))
}
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)
}
func validatePodSecurityPolicySysctlListsDoNotOverlap(allowedSysctlsFldPath, forbiddenSysctlsFldPath *field.Path, allowedUnsafeSysctls, forbiddenSysctls []string) field.ErrorList {
allErrs := field.ErrorList{}
for i, allowedSysctl := range allowedUnsafeSysctls {
isAllowedSysctlPattern := false
allowedSysctlPrefix := ""
if strings.HasSuffix(allowedSysctl, "*") {
isAllowedSysctlPattern = true
allowedSysctlPrefix = strings.TrimSuffix(allowedSysctl, "*")
}
for j, forbiddenSysctl := range forbiddenSysctls {
isForbiddenSysctlPattern := false
forbiddenSysctlPrefix := ""
if strings.HasSuffix(forbiddenSysctl, "*") {
isForbiddenSysctlPattern = true
forbiddenSysctlPrefix = strings.TrimSuffix(forbiddenSysctl, "*")
}
switch {
case isAllowedSysctlPattern && isForbiddenSysctlPattern:
if strings.HasPrefix(allowedSysctlPrefix, forbiddenSysctlPrefix) {
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
} else if strings.HasPrefix(forbiddenSysctlPrefix, allowedSysctlPrefix) {
allErrs = append(allErrs, field.Invalid(forbiddenSysctlsFldPath.Index(j), forbiddenSysctls[j], fmt.Sprintf("sysctl overlaps with %v", allowedSysctl)))
}
case isAllowedSysctlPattern:
if strings.HasPrefix(forbiddenSysctl, allowedSysctlPrefix) {
allErrs = append(allErrs, field.Invalid(forbiddenSysctlsFldPath.Index(j), forbiddenSysctls[j], fmt.Sprintf("sysctl overlaps with %v", allowedSysctl)))
}
case isForbiddenSysctlPattern:
if strings.HasPrefix(allowedSysctl, forbiddenSysctlPrefix) {
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
}
default:
if allowedSysctl == forbiddenSysctl {
allErrs = append(allErrs, field.Invalid(allowedSysctlsFldPath.Index(i), allowedUnsafeSysctls[i], fmt.Sprintf("sysctl overlaps with %v", forbiddenSysctl)))
}
}
}
}
return allErrs
}
// validatePodSecurityPolicySysctls validates the sysctls fields of PodSecurityPolicy.
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
allErrs := field.ErrorList{}
if len(sysctls) == 0 {
return allErrs
}
if !utilfeature.DefaultFeatureGate.Enabled(features.Sysctls) {
return append(allErrs, field.Forbidden(fldPath, "Sysctls are disabled by Sysctls feature-gate"))
}
coversAll := false
for i, s := range sysctls {
if len(s) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("empty sysctl not allowed")))
} else 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,
)),
)
} else if s[0] == '*' {
coversAll = true
}
}
if coversAll && len(sysctls) > 1 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("items"), fmt.Sprintf("if '*' is present, must not specify other sysctls")))
}
return allErrs
}
func validateUserIDRange(fldPath *field.Path, rng policy.IDRange) field.ErrorList {
return validateIDRanges(fldPath, rng.Min, rng.Max)
}
func validateGroupIDRange(fldPath *field.Path, rng policy.IDRange) field.ErrorList {
return validateIDRanges(fldPath, rng.Min, rng.Max)
}
// validateIDRanges ensures the range is valid.
func validateIDRanges(fldPath *field.Path, min, max int64) field.ErrorList {
allErrs := field.ErrorList{}
// if 0 <= Min <= Max then we do not need to validate max. It is always greater than or
// equal to 0 and Min.
if min < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), min, "min cannot be negative"))
}
if max < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("max"), max, "max cannot be negative"))
}
if min > max {
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), min, "min cannot be greater than max"))
}
return allErrs
}
// validatePSPCapsAgainstDrops ensures an allowed cap is not listed in the required drops.
func validatePSPCapsAgainstDrops(requiredDrops []core.Capability, capsToCheck []core.Capability, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if requiredDrops == nil {
return allErrs
}
for _, cap := range capsToCheck {
if hasCap(cap, requiredDrops) {
allErrs = append(allErrs, field.Invalid(fldPath, cap,
fmt.Sprintf("capability is listed in %s and requiredDropCapabilities", fldPath.String())))
}
}
return allErrs
}
// ValidatePodSecurityPolicyUpdate validates a PSP for updates.
func ValidatePodSecurityPolicyUpdate(old *policy.PodSecurityPolicy, new *policy.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(new.Annotations, field.NewPath("metadata").Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, field.NewPath("spec"))...)
return allErrs
}
// hasCap checks for needle in haystack.
func hasCap(needle core.Capability, haystack []core.Capability) bool {
for _, c := range haystack {
if needle == c {
return true
}
}
return false
}

View File

@ -17,11 +17,17 @@ limitations under the License.
package validation
import (
"fmt"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
@ -221,3 +227,559 @@ func TestValidatePodDisruptionBudgetUpdate(t *testing.T) {
}
}
}
func TestValidatePodSecurityPolicy(t *testing.T) {
validPSP := func() *policy.PodSecurityPolicy {
return &policy.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Annotations: map[string]string{},
},
Spec: policy.PodSecurityPolicySpec{
SELinux: policy.SELinuxStrategyOptions{
Rule: policy.SELinuxStrategyRunAsAny,
},
RunAsUser: policy.RunAsUserStrategyOptions{
Rule: policy.RunAsUserStrategyRunAsAny,
},
FSGroup: policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyRunAsAny,
},
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyRunAsAny,
},
AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo/bar"},
{PathPrefix: "/baz/"},
},
},
}
}
noUserOptions := validPSP()
noUserOptions.Spec.RunAsUser.Rule = ""
noSELinuxOptions := validPSP()
noSELinuxOptions.Spec.SELinux.Rule = ""
invalidUserStratType := validPSP()
invalidUserStratType.Spec.RunAsUser.Rule = "invalid"
invalidSELinuxStratType := validPSP()
invalidSELinuxStratType.Spec.SELinux.Rule = "invalid"
invalidUIDPSP := validPSP()
invalidUIDPSP.Spec.RunAsUser.Rule = policy.RunAsUserStrategyMustRunAs
invalidUIDPSP.Spec.RunAsUser.Ranges = []policy.IDRange{{Min: -1, Max: 1}}
missingObjectMetaName := validPSP()
missingObjectMetaName.ObjectMeta.Name = ""
noFSGroupOptions := validPSP()
noFSGroupOptions.Spec.FSGroup.Rule = ""
invalidFSGroupStratType := validPSP()
invalidFSGroupStratType.Spec.FSGroup.Rule = "invalid"
noSupplementalGroupsOptions := validPSP()
noSupplementalGroupsOptions.Spec.SupplementalGroups.Rule = ""
invalidSupGroupStratType := validPSP()
invalidSupGroupStratType.Spec.SupplementalGroups.Rule = "invalid"
invalidRangeMinGreaterThanMax := validPSP()
invalidRangeMinGreaterThanMax.Spec.FSGroup.Ranges = []policy.IDRange{
{Min: 2, Max: 1},
}
invalidRangeNegativeMin := validPSP()
invalidRangeNegativeMin.Spec.FSGroup.Ranges = []policy.IDRange{
{Min: -1, Max: 10},
}
invalidRangeNegativeMax := validPSP()
invalidRangeNegativeMax.Spec.FSGroup.Ranges = []policy.IDRange{
{Min: 1, Max: -10},
}
wildcardAllowedCapAndRequiredDrop := validPSP()
wildcardAllowedCapAndRequiredDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"}
wildcardAllowedCapAndRequiredDrop.Spec.AllowedCapabilities = []api.Capability{policy.AllowAllCapabilities}
requiredCapAddAndDrop := validPSP()
requiredCapAddAndDrop.Spec.DefaultAddCapabilities = []api.Capability{"foo"}
requiredCapAddAndDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"}
allowedCapListedInRequiredDrop := validPSP()
allowedCapListedInRequiredDrop.Spec.RequiredDropCapabilities = []api.Capability{"foo"}
allowedCapListedInRequiredDrop.Spec.AllowedCapabilities = []api.Capability{"foo"}
invalidAppArmorDefault := validPSP()
invalidAppArmorDefault.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: "not-good",
}
invalidAppArmorAllowed := validPSP()
invalidAppArmorAllowed.Annotations = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + ",not-good",
}
invalidAllowedUnsafeSysctlPattern := validPSP()
invalidAllowedUnsafeSysctlPattern.Spec.AllowedUnsafeSysctls = []string{"a.*.b"}
invalidForbiddenSysctlPattern := validPSP()
invalidForbiddenSysctlPattern.Spec.ForbiddenSysctls = []string{"a.*.b"}
invalidOverlappingSysctls := validPSP()
invalidOverlappingSysctls.Spec.ForbiddenSysctls = []string{"kernel.*", "net.ipv4.ip_local_port_range"}
invalidOverlappingSysctls.Spec.AllowedUnsafeSysctls = []string{"kernel.shmmax", "net.ipv4.ip_local_port_range"}
invalidDuplicatedSysctls := validPSP()
invalidDuplicatedSysctls.Spec.ForbiddenSysctls = []string{"net.ipv4.ip_local_port_range"}
invalidDuplicatedSysctls.Spec.AllowedUnsafeSysctls = []string{"net.ipv4.ip_local_port_range"}
invalidSeccompDefault := validPSP()
invalidSeccompDefault.Annotations = map[string]string{
seccomp.DefaultProfileAnnotationKey: "not-good",
}
invalidSeccompAllowAnyDefault := validPSP()
invalidSeccompAllowAnyDefault.Annotations = map[string]string{
seccomp.DefaultProfileAnnotationKey: "*",
}
invalidSeccompAllowed := validPSP()
invalidSeccompAllowed.Annotations = map[string]string{
seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",not-good",
}
invalidAllowedHostPathMissingPath := validPSP()
invalidAllowedHostPathMissingPath.Spec.AllowedHostPaths = []policy.AllowedHostPath{
{PathPrefix: ""},
}
invalidAllowedHostPathBacksteps := validPSP()
invalidAllowedHostPathBacksteps.Spec.AllowedHostPaths = []policy.AllowedHostPath{
{PathPrefix: "/dont/allow/backsteps/.."},
}
invalidDefaultAllowPrivilegeEscalation := validPSP()
pe := true
invalidDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe
emptyFlexDriver := validPSP()
emptyFlexDriver.Spec.Volumes = []policy.FSType{policy.FlexVolume}
emptyFlexDriver.Spec.AllowedFlexVolumes = []policy.AllowedFlexVolume{{}}
nonEmptyFlexVolumes := validPSP()
nonEmptyFlexVolumes.Spec.AllowedFlexVolumes = []policy.AllowedFlexVolume{{Driver: "example/driver"}}
type testCase struct {
psp *policy.PodSecurityPolicy
errorType field.ErrorType
errorDetail string
}
errorCases := map[string]testCase{
"no user options": {
psp: noUserOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "MustRunAsNonRoot", "RunAsAny"`,
},
"no selinux options": {
psp: noSELinuxOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"no fsgroup options": {
psp: noFSGroupOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"no sup group options": {
psp: noSupplementalGroupsOptions,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"invalid user strategy type": {
psp: invalidUserStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "MustRunAsNonRoot", "RunAsAny"`,
},
"invalid selinux strategy type": {
psp: invalidSELinuxStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"invalid sup group strategy type": {
psp: invalidSupGroupStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"invalid fs group strategy type": {
psp: invalidFSGroupStratType,
errorType: field.ErrorTypeNotSupported,
errorDetail: `supported values: "MustRunAs", "RunAsAny"`,
},
"invalid uid": {
psp: invalidUIDPSP,
errorType: field.ErrorTypeInvalid,
errorDetail: "min cannot be negative",
},
"missing object meta name": {
psp: missingObjectMetaName,
errorType: field.ErrorTypeRequired,
errorDetail: "name or generateName is required",
},
"invalid range min greater than max": {
psp: invalidRangeMinGreaterThanMax,
errorType: field.ErrorTypeInvalid,
errorDetail: "min cannot be greater than max",
},
"invalid range negative min": {
psp: invalidRangeNegativeMin,
errorType: field.ErrorTypeInvalid,
errorDetail: "min cannot be negative",
},
"invalid range negative max": {
psp: invalidRangeNegativeMax,
errorType: field.ErrorTypeInvalid,
errorDetail: "max cannot be negative",
},
"non-empty required drops and all caps are allowed by a wildcard": {
psp: wildcardAllowedCapAndRequiredDrop,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be empty when all capabilities are allowed by a wildcard",
},
"invalid required caps": {
psp: requiredCapAddAndDrop,
errorType: field.ErrorTypeInvalid,
errorDetail: "capability is listed in defaultAddCapabilities and requiredDropCapabilities",
},
"allowed cap listed in required drops": {
psp: allowedCapListedInRequiredDrop,
errorType: field.ErrorTypeInvalid,
errorDetail: "capability is listed in allowedCapabilities and requiredDropCapabilities",
},
"invalid AppArmor default profile": {
psp: invalidAppArmorDefault,
errorType: field.ErrorTypeInvalid,
errorDetail: "invalid AppArmor profile name: \"not-good\"",
},
"invalid AppArmor allowed profile": {
psp: invalidAppArmorAllowed,
errorType: field.ErrorTypeInvalid,
errorDetail: "invalid AppArmor profile name: \"not-good\"",
},
"invalid allowed unsafe sysctl pattern": {
psp: invalidAllowedUnsafeSysctlPattern,
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
},
"invalid forbidden sysctl pattern": {
psp: invalidForbiddenSysctlPattern,
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("must have at most 253 characters and match regex %s", SysctlPatternFmt),
},
"invalid overlapping sysctl pattern": {
psp: invalidOverlappingSysctls,
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("sysctl overlaps with %s", invalidOverlappingSysctls.Spec.ForbiddenSysctls[0]),
},
"invalid duplicated sysctls": {
psp: invalidDuplicatedSysctls,
errorType: field.ErrorTypeInvalid,
errorDetail: fmt.Sprintf("sysctl overlaps with %s", invalidDuplicatedSysctls.Spec.AllowedUnsafeSysctls[0]),
},
"invalid seccomp default profile": {
psp: invalidSeccompDefault,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be a valid seccomp profile",
},
"invalid seccomp allow any default profile": {
psp: invalidSeccompAllowAnyDefault,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be a valid seccomp profile",
},
"invalid seccomp allowed profile": {
psp: invalidSeccompAllowed,
errorType: field.ErrorTypeInvalid,
errorDetail: "must be a valid seccomp profile",
},
"invalid defaultAllowPrivilegeEscalation": {
psp: invalidDefaultAllowPrivilegeEscalation,
errorType: field.ErrorTypeInvalid,
errorDetail: "Cannot set DefaultAllowPrivilegeEscalation to true without also setting AllowPrivilegeEscalation to true",
},
"invalid allowed host path empty path": {
psp: invalidAllowedHostPathMissingPath,
errorType: field.ErrorTypeRequired,
errorDetail: "is required",
},
"invalid allowed host path with backsteps": {
psp: invalidAllowedHostPathBacksteps,
errorType: field.ErrorTypeInvalid,
errorDetail: "must not contain '..'",
},
"empty flex volume driver": {
psp: emptyFlexDriver,
errorType: field.ErrorTypeRequired,
errorDetail: "must specify a driver",
},
}
for k, v := range errorCases {
errs := ValidatePodSecurityPolicy(v.psp)
if len(errs) == 0 {
t.Errorf("%s expected errors but got none", k)
continue
}
if errs[0].Type != v.errorType {
t.Errorf("[%s] received an unexpected error type. Expected: '%s' got: '%s'", k, v.errorType, errs[0].Type)
}
if errs[0].Detail != v.errorDetail {
t.Errorf("[%s] received an unexpected error detail. Expected '%s' got: '%s'", k, v.errorDetail, errs[0].Detail)
}
}
// Update error is different for 'missing object meta name'.
errorCases["missing object meta name"] = testCase{
psp: errorCases["missing object meta name"].psp,
errorType: field.ErrorTypeInvalid,
errorDetail: "field is immutable",
}
// Should not be able to update to an invalid policy.
for k, v := range errorCases {
v.psp.ResourceVersion = "444" // Required for updates.
errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp)
if len(errs) == 0 {
t.Errorf("[%s] expected update errors but got none", k)
continue
}
if errs[0].Type != v.errorType {
t.Errorf("[%s] received an unexpected error type. Expected: '%s' got: '%s'", k, v.errorType, errs[0].Type)
}
if errs[0].Detail != v.errorDetail {
t.Errorf("[%s] received an unexpected error detail. Expected '%s' got: '%s'", k, v.errorDetail, errs[0].Detail)
}
}
mustRunAs := validPSP()
mustRunAs.Spec.FSGroup.Rule = policy.FSGroupStrategyMustRunAs
mustRunAs.Spec.SupplementalGroups.Rule = policy.SupplementalGroupsStrategyMustRunAs
mustRunAs.Spec.RunAsUser.Rule = policy.RunAsUserStrategyMustRunAs
mustRunAs.Spec.RunAsUser.Ranges = []policy.IDRange{
{Min: 1, Max: 1},
}
mustRunAs.Spec.SELinux.Rule = policy.SELinuxStrategyMustRunAs
runAsNonRoot := validPSP()
runAsNonRoot.Spec.RunAsUser.Rule = policy.RunAsUserStrategyMustRunAsNonRoot
caseInsensitiveAddDrop := validPSP()
caseInsensitiveAddDrop.Spec.DefaultAddCapabilities = []api.Capability{"foo"}
caseInsensitiveAddDrop.Spec.RequiredDropCapabilities = []api.Capability{"FOO"}
caseInsensitiveAllowedDrop := validPSP()
caseInsensitiveAllowedDrop.Spec.RequiredDropCapabilities = []api.Capability{"FOO"}
caseInsensitiveAllowedDrop.Spec.AllowedCapabilities = []api.Capability{"foo"}
validAppArmor := validPSP()
validAppArmor.Annotations = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," + apparmor.ProfileNamePrefix + "foo",
}
withForbiddenSysctl := validPSP()
withForbiddenSysctl.Spec.ForbiddenSysctls = []string{"net.*"}
withAllowedUnsafeSysctl := validPSP()
withAllowedUnsafeSysctl.Spec.AllowedUnsafeSysctls = []string{"net.ipv4.tcp_max_syn_backlog"}
validSeccomp := validPSP()
validSeccomp.Annotations = map[string]string{
seccomp.DefaultProfileAnnotationKey: api.SeccompProfileRuntimeDefault,
seccomp.AllowedProfilesAnnotationKey: api.SeccompProfileRuntimeDefault + ",unconfined,localhost/foo,*",
}
validDefaultAllowPrivilegeEscalation := validPSP()
pe = true
validDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe
validDefaultAllowPrivilegeEscalation.Spec.AllowPrivilegeEscalation = true
flexvolumeWhenFlexVolumesAllowed := validPSP()
flexvolumeWhenFlexVolumesAllowed.Spec.Volumes = []policy.FSType{policy.FlexVolume}
flexvolumeWhenFlexVolumesAllowed.Spec.AllowedFlexVolumes = []policy.AllowedFlexVolume{
{Driver: "example/driver1"},
}
flexvolumeWhenAllVolumesAllowed := validPSP()
flexvolumeWhenAllVolumesAllowed.Spec.Volumes = []policy.FSType{policy.All}
flexvolumeWhenAllVolumesAllowed.Spec.AllowedFlexVolumes = []policy.AllowedFlexVolume{
{Driver: "example/driver2"},
}
successCases := map[string]struct {
psp *policy.PodSecurityPolicy
}{
"must run as": {
psp: mustRunAs,
},
"run as any": {
psp: validPSP(),
},
"run as non-root (user only)": {
psp: runAsNonRoot,
},
"comparison for add -> drop is case sensitive": {
psp: caseInsensitiveAddDrop,
},
"comparison for allowed -> drop is case sensitive": {
psp: caseInsensitiveAllowedDrop,
},
"valid AppArmor annotations": {
psp: validAppArmor,
},
"with network sysctls forbidden": {
psp: withForbiddenSysctl,
},
"with unsafe net.ipv4.tcp_max_syn_backlog sysctl allowed": {
psp: withAllowedUnsafeSysctl,
},
"valid seccomp annotations": {
psp: validSeccomp,
},
"valid defaultAllowPrivilegeEscalation as true": {
psp: validDefaultAllowPrivilegeEscalation,
},
"allow white-listed flexVolume when flex volumes are allowed": {
psp: flexvolumeWhenFlexVolumesAllowed,
},
"allow white-listed flexVolume when all volumes are allowed": {
psp: flexvolumeWhenAllVolumesAllowed,
},
}
for k, v := range successCases {
if errs := ValidatePodSecurityPolicy(v.psp); len(errs) != 0 {
t.Errorf("Expected success for %s, got %v", k, errs)
}
// Should be able to update to a valid PSP.
v.psp.ResourceVersion = "444" // Required for updates.
if errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp); len(errs) != 0 {
t.Errorf("Expected success for %s update, got %v", k, errs)
}
}
}
func TestValidatePSPVolumes(t *testing.T) {
validPSP := func() *policy.PodSecurityPolicy {
return &policy.PodSecurityPolicy{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: policy.PodSecurityPolicySpec{
SELinux: policy.SELinuxStrategyOptions{
Rule: policy.SELinuxStrategyRunAsAny,
},
RunAsUser: policy.RunAsUserStrategyOptions{
Rule: policy.RunAsUserStrategyRunAsAny,
},
FSGroup: policy.FSGroupStrategyOptions{
Rule: policy.FSGroupStrategyRunAsAny,
},
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
Rule: policy.SupplementalGroupsStrategyRunAsAny,
},
},
}
}
volumes := psputil.GetAllFSTypesAsSet()
// add in the * value since that is a pseudo type that is not included by default
volumes.Insert(string(policy.All))
for _, strVolume := range volumes.List() {
psp := validPSP()
psp.Spec.Volumes = []policy.FSType{policy.FSType(strVolume)}
errs := ValidatePodSecurityPolicy(psp)
if len(errs) != 0 {
t.Errorf("%s validation expected no errors but received %v", strVolume, errs)
}
}
}
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 Test_validatePSPRunAsUser(t *testing.T) {
var testCases = []struct {
name string
runAsUserStrategy policy.RunAsUserStrategyOptions
fail bool
}{
{"Invalid RunAsUserStrategy", policy.RunAsUserStrategyOptions{Rule: policy.RunAsUserStrategy("someInvalidStrategy")}, true},
{"RunAsUserStrategyMustRunAs", policy.RunAsUserStrategyOptions{Rule: policy.RunAsUserStrategyMustRunAs}, false},
{"RunAsUserStrategyMustRunAsNonRoot", policy.RunAsUserStrategyOptions{Rule: policy.RunAsUserStrategyMustRunAsNonRoot}, false},
{"RunAsUserStrategyMustRunAsNonRoot With Valid Range", policy.RunAsUserStrategyOptions{Rule: policy.RunAsUserStrategyMustRunAs, Ranges: []policy.IDRange{{Min: 2, Max: 3}, {Min: 4, Max: 5}}}, false},
{"RunAsUserStrategyMustRunAsNonRoot With Invalid Range", policy.RunAsUserStrategyOptions{Rule: policy.RunAsUserStrategyMustRunAs, Ranges: []policy.IDRange{{Min: 2, Max: 3}, {Min: 5, Max: 4}}}, true},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
errList := validatePSPRunAsUser(field.NewPath("status"), &testCase.runAsUserStrategy)
actualErrors := len(errList)
expectedErrors := 1
if !testCase.fail {
expectedErrors = 0
}
if actualErrors != expectedErrors {
t.Errorf("In testCase %v, expected %v errors, got %v errors", testCase.name, expectedErrors, actualErrors)
}
})
}
}