diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 79694aae5c8..e36972846bc 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -860,6 +860,11 @@ type PodSecurityPolicySpec struct { // AllowedHostPaths is a white list of allowed host paths. Empty indicates that all host paths may be used. // +optional AllowedHostPaths []AllowedHostPath + // AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all + // Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes + // is allowed in the "Volumes" field. + // +optional + AllowedFlexVolumes []AllowedFlexVolume } // AllowedHostPath defines the host volume conditions that will be enabled by a policy @@ -923,6 +928,12 @@ var ( All FSType = "*" ) +// AllowedFlexVolume represents a single Flexvolume that is allowed to be used. +type AllowedFlexVolume struct { + // Driver is the name of the Flexvolume driver. + Driver string +} + // SELinuxStrategyOptions defines the strategy type and any options used to create the strategy. type SELinuxStrategyOptions struct { // Rule is the strategy that will dictate the allowable labels that may be set. diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go index bab3fd2f4a4..20cc279e8fd 100644 --- a/pkg/apis/extensions/validation/validation.go +++ b/pkg/apis/extensions/validation/validation.go @@ -655,6 +655,7 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa 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)...) return allErrs } @@ -721,6 +722,20 @@ func validatePSPAllowedHostPaths(fldPath *field.Path, allowedHostPaths []extensi return allErrs } +// validatePSPAllowedFlexVolumes +func validatePSPAllowedFlexVolumes(fldPath *field.Path, flexVolumes []extensions.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 *extensions.SELinuxStrategyOptions) field.ErrorList { allErrs := field.ErrorList{} @@ -802,7 +817,6 @@ func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []extensions. allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List())) } } - return allErrs } diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index 2a432da9b4a..4a3818801ff 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -2450,6 +2450,13 @@ func TestValidatePodSecurityPolicy(t *testing.T) { pe := true invalidDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe + emptyFlexDriver := validPSP() + emptyFlexDriver.Spec.Volumes = []extensions.FSType{extensions.FlexVolume} + emptyFlexDriver.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{{}} + + nonEmptyFlexVolumes := validPSP() + nonEmptyFlexVolumes.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{{Driver: "example/driver"}} + type testCase struct { psp *extensions.PodSecurityPolicy errorType field.ErrorType @@ -2581,6 +2588,11 @@ func TestValidatePodSecurityPolicy(t *testing.T) { 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 { @@ -2660,6 +2672,17 @@ func TestValidatePodSecurityPolicy(t *testing.T) { validDefaultAllowPrivilegeEscalation.Spec.DefaultAllowPrivilegeEscalation = &pe validDefaultAllowPrivilegeEscalation.Spec.AllowPrivilegeEscalation = true + flexvolumeWhenFlexVolumesAllowed := validPSP() + flexvolumeWhenFlexVolumesAllowed.Spec.Volumes = []extensions.FSType{extensions.FlexVolume} + flexvolumeWhenFlexVolumesAllowed.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{ + {Driver: "example/driver1"}, + } + + flexvolumeWhenAllVolumesAllowed := validPSP() + flexvolumeWhenAllVolumesAllowed.Spec.Volumes = []extensions.FSType{extensions.All} + flexvolumeWhenAllVolumesAllowed.Spec.AllowedFlexVolumes = []extensions.AllowedFlexVolume{ + {Driver: "example/driver2"}, + } successCases := map[string]struct { psp *extensions.PodSecurityPolicy }{ @@ -2690,6 +2713,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) { "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 { diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index c876dd1271c..c33b1c636ce 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -3386,6 +3386,9 @@ func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error w.Write(LEVEL_1, "Allowed Capabilities:\t%s\n", capsToString(psp.Spec.AllowedCapabilities)) w.Write(LEVEL_1, "Allowed Volume Types:\t%s\n", fsTypeToString(psp.Spec.Volumes)) + if len(psp.Spec.AllowedFlexVolumes) > 0 { + w.Write(LEVEL_1, "Allowed FlexVolume Types:\t%s\n", flexVolumesToString(psp.Spec.AllowedFlexVolumes)) + } w.Write(LEVEL_1, "Allow Host Network:\t%t\n", psp.Spec.HostNetwork) w.Write(LEVEL_1, "Allow Host Ports:\t%s\n", hostPortRangeToString(psp.Spec.HostPorts)) w.Write(LEVEL_1, "Allow Host PID:\t%t\n", psp.Spec.HostPID) @@ -3419,10 +3422,14 @@ func describePodSecurityPolicy(psp *extensions.PodSecurityPolicy) (string, error } func stringOrNone(s string) string { + return stringOrDefaultValue(s, "") +} + +func stringOrDefaultValue(s, defaultValue string) string { if len(s) > 0 { return s } - return "" + return defaultValue } func fsTypeToString(volumes []extensions.FSType) string { @@ -3433,6 +3440,14 @@ func fsTypeToString(volumes []extensions.FSType) string { return stringOrNone(strings.Join(strVolumes, ",")) } +func flexVolumesToString(flexVolumes []extensions.AllowedFlexVolume) string { + volumes := []string{} + for _, flexVolume := range flexVolumes { + volumes = append(volumes, "driver="+flexVolume.Driver) + } + return stringOrDefaultValue(strings.Join(volumes, ","), "") +} + func hostPortRangeToString(ranges []extensions.HostPortRange) string { formattedString := "" if ranges != nil { diff --git a/pkg/security/podsecuritypolicy/provider.go b/pkg/security/podsecuritypolicy/provider.go index 139a453a9e2..b10aa15555c 100644 --- a/pkg/security/podsecuritypolicy/provider.go +++ b/pkg/security/podsecuritypolicy/provider.go @@ -233,9 +233,24 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field fmt.Sprintf("is not allowed to be used"))) } } + + if fsType == extensions.FlexVolume && len(s.psp.Spec.AllowedFlexVolumes) > 0 { + found := false + driver := v.FlexVolume.Driver + for _, allowedFlexVolume := range s.psp.Spec.AllowedFlexVolumes { + if driver == allowedFlexVolume.Driver { + found = true + break + } + } + if !found { + allErrs = append(allErrs, + field.Invalid(fldPath.Child("volumes").Index(i).Child("driver"), driver, + "Flexvolume driver is not allowed to be used")) + } + } } } - return allErrs } diff --git a/pkg/security/podsecuritypolicy/provider_test.go b/pkg/security/podsecuritypolicy/provider_test.go index b0389e33aac..6a112b67446 100644 --- a/pkg/security/podsecuritypolicy/provider_test.go +++ b/pkg/security/podsecuritypolicy/provider_test.go @@ -256,6 +256,18 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { failSeccompProfilePod := defaultPod() failSeccompProfilePod.Annotations = map[string]string{api.SeccompPodAnnotationKey: "foo"} + podWithInvalidFlexVolumeDriver := defaultPod() + podWithInvalidFlexVolumeDriver.Spec.Volumes = []api.Volume{ + { + Name: "flex-volume", + VolumeSource: api.VolumeSource{ + FlexVolume: &api.FlexVolumeSource{ + Driver: "example/unknown", + }, + }, + }, + } + errorCases := map[string]struct { pod *api.Pod psp *extensions.PodSecurityPolicy @@ -341,6 +353,16 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { psp: defaultPSP(), expectedError: "Forbidden: seccomp may not be set", }, + "fail pod with disallowed flexVolume when flex volumes are allowed": { + pod: podWithInvalidFlexVolumeDriver, + psp: allowFlexVolumesPSP(false, false), + expectedError: "Flexvolume driver is not allowed to be used", + }, + "fail pod with disallowed flexVolume when all volumes are allowed": { + pod: podWithInvalidFlexVolumeDriver, + psp: allowFlexVolumesPSP(false, true), + expectedError: "Flexvolume driver is not allowed to be used", + }, } for k, v := range errorCases { provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) @@ -358,6 +380,28 @@ func TestValidatePodSecurityContextFailures(t *testing.T) { } } +func allowFlexVolumesPSP(allowAllFlexVolumes, allowAllVolumes bool) *extensions.PodSecurityPolicy { + psp := defaultPSP() + + allowedVolumes := []extensions.AllowedFlexVolume{ + {Driver: "example/foo"}, + {Driver: "example/bar"}, + } + if allowAllFlexVolumes { + allowedVolumes = []extensions.AllowedFlexVolume{} + } + + allowedVolumeType := extensions.FlexVolume + if allowAllVolumes { + allowedVolumeType = extensions.All + } + + psp.Spec.AllowedFlexVolumes = allowedVolumes + psp.Spec.Volumes = []extensions.FSType{allowedVolumeType} + + return psp +} + func TestValidateContainerSecurityContextFailures(t *testing.T) { // fail user strat failUserPSP := defaultPSP() @@ -597,6 +641,18 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) { api.SeccompPodAnnotationKey: "foo", } + flexVolumePod := defaultPod() + flexVolumePod.Spec.Volumes = []api.Volume{ + { + Name: "flex-volume", + VolumeSource: api.VolumeSource{ + FlexVolume: &api.FlexVolumeSource{ + Driver: "example/bar", + }, + }, + }, + } + successCases := map[string]struct { pod *api.Pod psp *extensions.PodSecurityPolicy @@ -653,6 +709,22 @@ func TestValidatePodSecurityContextSuccess(t *testing.T) { pod: seccompPod, psp: seccompPSP, }, + "flex volume driver in a whitelist (all volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(false, true), + }, + "flex volume driver with empty whitelist (all volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(true, true), + }, + "flex volume driver in a whitelist (only flex volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(false, false), + }, + "flex volume driver with empty whitelist (only flex volumes volumes are allowed)": { + pod: flexVolumePod, + psp: allowFlexVolumesPSP(true, false), + }, } for k, v := range successCases { diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types.go b/staging/src/k8s.io/api/extensions/v1beta1/types.go index d4ca1832ca5..9be564ed88e 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types.go @@ -938,6 +938,11 @@ type PodSecurityPolicySpec struct { // is a white list of allowed host paths. Empty indicates that all host paths may be used. // +optional AllowedHostPaths []AllowedHostPath `json:"allowedHostPaths,omitempty" protobuf:"bytes,17,rep,name=allowedHostPaths"` + // AllowedFlexVolumes is a whitelist of allowed Flexvolumes. Empty or nil indicates that all + // Flexvolumes may be used. This parameter is effective only when the usage of the Flexvolumes + // is allowed in the "Volumes" field. + // +optional + AllowedFlexVolumes []AllowedFlexVolume `json:"allowedFlexVolumes,omitempty" protobuf:"bytes,18,rep,name=allowedFlexVolumes"` } // defines the host volume conditions that will be enabled by a policy @@ -981,6 +986,12 @@ var ( All FSType = "*" ) +// AllowedFlexVolume represents a single Flexvolume that is allowed to be used. +type AllowedFlexVolume struct { + // Driver is the name of the Flexvolume driver. + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` +} + // Host Port Range defines a range of host ports that will be enabled by a policy // for pods to use. It requires both the start and end to be defined. type HostPortRange struct {