PSP: validation errors for generic volume type
It's not enough to silently drop the volume type if the feature is disabled. Instead, the policy should fail validation, just as it would have if the API server didn't know about the feature at all.
This commit is contained in:
@@ -92,19 +92,26 @@ func ValidatePodDisruptionBudgetStatusUpdate(status, oldStatus policy.PodDisrupt
|
||||
// trailing dashes are allowed.
|
||||
var ValidatePodSecurityPolicyName = apimachineryvalidation.NameIsDNSSubdomain
|
||||
|
||||
// PodSecurityPolicyValidationOptions contains additional parameters for ValidatePodSecurityPolicy.
|
||||
type PodSecurityPolicyValidationOptions struct {
|
||||
// AllowEphemeralVolumeType determines whether Ephemeral is a valid entry
|
||||
// in PodSecurityPolicySpec.Volumes.
|
||||
AllowEphemeralVolumeType bool
|
||||
}
|
||||
|
||||
// ValidatePodSecurityPolicy validates a PodSecurityPolicy and returns an ErrorList
|
||||
// with any errors.
|
||||
func ValidatePodSecurityPolicy(psp *policy.PodSecurityPolicy) field.ErrorList {
|
||||
func ValidatePodSecurityPolicy(psp *policy.PodSecurityPolicy, opts PodSecurityPolicyValidationOptions) 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"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, opts, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidatePodSecurityPolicySpec validates a PodSecurityPolicySpec and returns an ErrorList
|
||||
// with any errors.
|
||||
func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, fldPath *field.Path) field.ErrorList {
|
||||
func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, opts PodSecurityPolicyValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...)
|
||||
@@ -112,7 +119,7 @@ func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, fldPath *
|
||||
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)...)
|
||||
allErrs = append(allErrs, validatePodSecurityPolicyVolumes(opts, 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"))
|
||||
@@ -320,11 +327,15 @@ func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.Supp
|
||||
}
|
||||
|
||||
// validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy.
|
||||
func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSType) field.ErrorList {
|
||||
func validatePodSecurityPolicyVolumes(opts PodSecurityPolicyValidationOptions, 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))
|
||||
// Ephemeral may or may not be allowed.
|
||||
if !opts.AllowEphemeralVolumeType {
|
||||
allowed.Delete(string(policy.Ephemeral))
|
||||
}
|
||||
for _, v := range volumes {
|
||||
if !allowed.Has(string(v)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List()))
|
||||
@@ -519,11 +530,11 @@ func validateRuntimeClassStrategy(fldPath *field.Path, rc *policy.RuntimeClassSt
|
||||
}
|
||||
|
||||
// ValidatePodSecurityPolicyUpdate validates a PSP for updates.
|
||||
func ValidatePodSecurityPolicyUpdate(old *policy.PodSecurityPolicy, new *policy.PodSecurityPolicy) field.ErrorList {
|
||||
func ValidatePodSecurityPolicyUpdate(old *policy.PodSecurityPolicy, new *policy.PodSecurityPolicy, opts PodSecurityPolicyValidationOptions) 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"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, opts, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@@ -590,7 +590,7 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidatePodSecurityPolicy(v.psp)
|
||||
errs := ValidatePodSecurityPolicy(v.psp, PodSecurityPolicyValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("%s expected errors but got none", k)
|
||||
continue
|
||||
@@ -613,7 +613,7 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
||||
// 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)
|
||||
errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp, PodSecurityPolicyValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("[%s] expected update errors but got none", k)
|
||||
continue
|
||||
@@ -743,13 +743,13 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
for k, v := range successCases {
|
||||
if errs := ValidatePodSecurityPolicy(v.psp); len(errs) != 0 {
|
||||
if errs := ValidatePodSecurityPolicy(v.psp, PodSecurityPolicyValidationOptions{}); 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 {
|
||||
if errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp, PodSecurityPolicyValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("Expected success for %s update, got %v", k, errs)
|
||||
}
|
||||
}
|
||||
@@ -786,7 +786,7 @@ func TestValidatePSPVolumes(t *testing.T) {
|
||||
for _, strVolume := range volumes.List() {
|
||||
psp := validPSP()
|
||||
psp.Spec.Volumes = []policy.FSType{policy.FSType(strVolume)}
|
||||
errs := ValidatePodSecurityPolicy(psp)
|
||||
errs := ValidatePodSecurityPolicy(psp, PodSecurityPolicyValidationOptions{AllowEphemeralVolumeType: true})
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("%s validation expected no errors but received %v", strVolume, errs)
|
||||
}
|
||||
@@ -1063,3 +1063,89 @@ func TestValidateRuntimeClassStrategy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowEphemeralVolumeType(t *testing.T) {
|
||||
pspWithoutGenericVolume := func() *policy.PodSecurityPolicy {
|
||||
return &policy.PodSecurityPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "psp",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: policy.PodSecurityPolicySpec{
|
||||
RunAsUser: policy.RunAsUserStrategyOptions{
|
||||
Rule: policy.RunAsUserStrategyMustRunAs,
|
||||
},
|
||||
SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
|
||||
Rule: policy.SupplementalGroupsStrategyMustRunAs,
|
||||
},
|
||||
SELinux: policy.SELinuxStrategyOptions{
|
||||
Rule: policy.SELinuxStrategyMustRunAs,
|
||||
},
|
||||
FSGroup: policy.FSGroupStrategyOptions{
|
||||
Rule: policy.FSGroupStrategyMustRunAs,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
pspWithGenericVolume := func() *policy.PodSecurityPolicy {
|
||||
psp := pspWithoutGenericVolume()
|
||||
psp.Spec.Volumes = append(psp.Spec.Volumes, policy.Ephemeral)
|
||||
return psp
|
||||
}
|
||||
pspNil := func() *policy.PodSecurityPolicy {
|
||||
return nil
|
||||
}
|
||||
|
||||
pspInfo := []struct {
|
||||
description string
|
||||
hasGenericVolume bool
|
||||
psp func() *policy.PodSecurityPolicy
|
||||
}{
|
||||
{
|
||||
description: "PodSecurityPolicySpec Without GenericVolume",
|
||||
hasGenericVolume: false,
|
||||
psp: pspWithoutGenericVolume,
|
||||
},
|
||||
{
|
||||
description: "PodSecurityPolicySpec With GenericVolume",
|
||||
hasGenericVolume: true,
|
||||
psp: pspWithGenericVolume,
|
||||
},
|
||||
{
|
||||
description: "is nil",
|
||||
hasGenericVolume: false,
|
||||
psp: pspNil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, allowed := range []bool{true, false} {
|
||||
for _, oldPSPInfo := range pspInfo {
|
||||
for _, newPSPInfo := range pspInfo {
|
||||
oldPSP := oldPSPInfo.psp()
|
||||
newPSP := newPSPInfo.psp()
|
||||
if newPSP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("feature enabled=%v, old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", allowed, oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
|
||||
opts := PodSecurityPolicyValidationOptions{
|
||||
AllowEphemeralVolumeType: allowed,
|
||||
}
|
||||
var errs field.ErrorList
|
||||
expectErrors := newPSPInfo.hasGenericVolume && !allowed
|
||||
if oldPSP == nil {
|
||||
errs = ValidatePodSecurityPolicy(newPSP, opts)
|
||||
} else {
|
||||
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts)
|
||||
}
|
||||
if expectErrors && len(errs) == 0 {
|
||||
t.Error("expected errors, got none")
|
||||
}
|
||||
if !expectErrors && len(errs) > 0 {
|
||||
t.Errorf("expected no errors, got: %v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user