From 04dc71f959a3a075de026e431a389950e11068f4 Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Thu, 5 May 2016 19:25:58 -0400 Subject: [PATCH 1/6] retain read only root file system in determineEffectiveSecurityContext --- pkg/securitycontext/provider.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/securitycontext/provider.go b/pkg/securitycontext/provider.go index f37aa4e0179..e31914960f9 100644 --- a/pkg/securitycontext/provider.go +++ b/pkg/securitycontext/provider.go @@ -159,6 +159,11 @@ func DetermineEffectiveSecurityContext(pod *api.Pod, container *api.Container) * *effectiveSc.RunAsNonRoot = *containerSc.RunAsNonRoot } + if containerSc.ReadOnlyRootFilesystem != nil { + effectiveSc.ReadOnlyRootFilesystem = new(bool) + *effectiveSc.ReadOnlyRootFilesystem = *containerSc.ReadOnlyRootFilesystem + } + return effectiveSc } From 56193b714058c633a818052936231167ad1cea1b Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Thu, 5 May 2016 15:43:54 -0400 Subject: [PATCH 2/6] PSP types --- pkg/apis/extensions/types.go | 74 +- pkg/apis/extensions/v1beta1/types.go | 84 +- pkg/apis/extensions/validation/validation.go | 85 +- .../extensions/validation/validation_test.go | 210 ++++- pkg/client/unversioned/podsecuritypolicy.go | 6 +- .../unversioned/podsecuritypolicy_test.go | 22 +- .../testclient/fake_podsecuritypolicy.go | 8 +- pkg/kubectl/resource_printer.go | 6 +- .../podsecuritypolicy/etcd/etcd_test.go | 12 +- pkg/security/doc.go | 18 + .../capabilities/mustrunas.go | 149 ++++ .../capabilities/mustrunas_test.go | 387 +++++++++ .../podsecuritypolicy/capabilities/types.go | 30 + pkg/security/podsecuritypolicy/factory.go | 135 +++ .../podsecuritypolicy/group/mustrunas.go | 93 ++ .../podsecuritypolicy/group/mustrunas_test.go | 193 ++++ .../podsecuritypolicy/group/runasany.go | 49 ++ .../podsecuritypolicy/group/runasany_test.go | 60 ++ pkg/security/podsecuritypolicy/group/types.go | 35 + pkg/security/podsecuritypolicy/provider.go | 297 +++++++ .../podsecuritypolicy/provider_test.go | 822 ++++++++++++++++++ .../podsecuritypolicy/selinux/mustrunas.go | 84 ++ .../selinux/mustrunas_test.go | 159 ++++ .../podsecuritypolicy/selinux/runasany.go | 43 + .../selinux/runasany_test.go | 73 ++ .../podsecuritypolicy/selinux/types.go | 30 + pkg/security/podsecuritypolicy/types.go | 62 ++ .../podsecuritypolicy/user/mustrunas.go | 84 ++ .../podsecuritypolicy/user/mustrunas_test.go | 152 ++++ .../podsecuritypolicy/user/nonroot.go | 59 ++ .../podsecuritypolicy/user/nonroot_test.go | 80 ++ .../podsecuritypolicy/user/runasany.go | 43 + .../podsecuritypolicy/user/runasany_test.go | 59 ++ pkg/security/podsecuritypolicy/user/types.go | 30 + pkg/security/podsecuritypolicy/util/util.go | 142 +++ .../podsecuritypolicy/util/util_test.go | 105 +++ plugin/pkg/admission/security/doc.go | 18 + 37 files changed, 3908 insertions(+), 90 deletions(-) create mode 100644 pkg/security/doc.go create mode 100644 pkg/security/podsecuritypolicy/capabilities/mustrunas.go create mode 100644 pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go create mode 100644 pkg/security/podsecuritypolicy/capabilities/types.go create mode 100644 pkg/security/podsecuritypolicy/factory.go create mode 100644 pkg/security/podsecuritypolicy/group/mustrunas.go create mode 100644 pkg/security/podsecuritypolicy/group/mustrunas_test.go create mode 100644 pkg/security/podsecuritypolicy/group/runasany.go create mode 100644 pkg/security/podsecuritypolicy/group/runasany_test.go create mode 100644 pkg/security/podsecuritypolicy/group/types.go create mode 100644 pkg/security/podsecuritypolicy/provider.go create mode 100644 pkg/security/podsecuritypolicy/provider_test.go create mode 100644 pkg/security/podsecuritypolicy/selinux/mustrunas.go create mode 100644 pkg/security/podsecuritypolicy/selinux/mustrunas_test.go create mode 100644 pkg/security/podsecuritypolicy/selinux/runasany.go create mode 100644 pkg/security/podsecuritypolicy/selinux/runasany_test.go create mode 100644 pkg/security/podsecuritypolicy/selinux/types.go create mode 100644 pkg/security/podsecuritypolicy/types.go create mode 100644 pkg/security/podsecuritypolicy/user/mustrunas.go create mode 100644 pkg/security/podsecuritypolicy/user/mustrunas_test.go create mode 100644 pkg/security/podsecuritypolicy/user/nonroot.go create mode 100644 pkg/security/podsecuritypolicy/user/nonroot_test.go create mode 100644 pkg/security/podsecuritypolicy/user/runasany.go create mode 100644 pkg/security/podsecuritypolicy/user/runasany_test.go create mode 100644 pkg/security/podsecuritypolicy/user/types.go create mode 100644 pkg/security/podsecuritypolicy/util/util.go create mode 100644 pkg/security/podsecuritypolicy/util/util_test.go create mode 100644 plugin/pkg/admission/security/doc.go diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 475abd5827c..7a307d2c7e8 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -624,6 +624,8 @@ type ReplicaSetStatus struct { ObservedGeneration int64 `json:"observedGeneration,omitempty"` } +// +genclient=true,nonNamespaced=true + // PodSecurityPolicy governs the ability to make requests that affect the SecurityContext // that will be applied to a pod and container. type PodSecurityPolicy struct { @@ -638,8 +640,17 @@ type PodSecurityPolicy struct { type PodSecurityPolicySpec struct { // Privileged determines if a pod can request to be run as privileged. Privileged bool `json:"privileged,omitempty"` - // Capabilities is a list of capabilities that can be added. - Capabilities []api.Capability `json:"capabilities,omitempty"` + // DefaultAddCapabilities is the default set of capabilities that will be added to the container + // unless the pod spec specifically drops the capability. You may not list a capabiility in both + // DefaultAddCapabilities and RequiredDropCapabilities. + DefaultAddCapabilities []api.Capability `json:"defaultAddCapabilities,omitempty"` + // RequiredDropCapabilities are the capabilities that will be dropped from the container. These + // are required to be dropped and cannot be added. + RequiredDropCapabilities []api.Capability `json:"requiredDropCapabilities,omitempty"` + // AllowedCapabilities is a list of capabilities that can be requested to add to the container. + // Capabilities in this field may be added at the pod author's discretion. + // You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities. + AllowedCapabilities []api.Capability `json:"allowedCapabilities,omitempty"` // Volumes is a white list of allowed volume plugins. Empty indicates that all plugins // may be used. Volumes []FSType `json:"volumes,omitempty"` @@ -652,9 +663,19 @@ type PodSecurityPolicySpec struct { // HostIPC determines if the policy allows the use of HostIPC in the pod spec. HostIPC bool `json:"hostIPC,omitempty"` // SELinux is the strategy that will dictate the allowable labels that may be set. - SELinux SELinuxStrategyOptions `json:"seLinux,omitempty"` + SELinux SELinuxStrategyOptions `json:"seLinux"` // RunAsUser is the strategy that will dictate the allowable RunAsUser values that may be set. - RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty"` + RunAsUser RunAsUserStrategyOptions `json:"runAsUser"` + // SupplementalGroups is the strategy that will dictate what supplemental groups are used by the SecurityContext. + SupplementalGroups SupplementalGroupsStrategyOptions `json:"supplementalGroups"` + // FSGroup is the strategy that will dictate what fs group is used by the SecurityContext. + FSGroup FSGroupStrategyOptions `json:"fsGroup"` + // ReadOnlyRootFilesystem when set to true will force containers to run with a read only root file + // system. If the container specifically requests to run with a non-read only root file system + // the PSP should deny the pod. + // If set to false the container may run with a read only root file system if it wishes but it + // will not be forced to. + ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"` } // HostPortRange defines a range of host ports that will be enabled by a policy @@ -670,6 +691,9 @@ type HostPortRange struct { type FSType string var ( + AzureFile FSType = "azureFile" + Flocker FSType = "flocker" + FlexVolume FSType = "flexVolume" HostPath FSType = "hostPath" EmptyDir FSType = "emptyDir" GCEPersistentDisk FSType = "gcePersistentDisk" @@ -685,6 +709,8 @@ var ( CephFS FSType = "cephFS" DownwardAPI FSType = "downwardAPI" FC FSType = "fc" + ConfigMap FSType = "configMap" + All FSType = "*" ) // SELinuxStrategyOptions defines the strategy type and any options used to create the strategy. @@ -736,6 +762,46 @@ const ( RunAsUserStrategyRunAsAny RunAsUserStrategy = "RunAsAny" ) +// FSGroupStrategyOptions defines the strategy type and options used to create the strategy. +type FSGroupStrategyOptions struct { + // Rule is the strategy that will dictate what FSGroup is used in the SecurityContext. + Rule FSGroupStrategyType `json:"rule,omitempty"` + // Ranges are the allowed ranges of fs groups. If you would like to force a single + // fs group then supply a single range with the same start and end. + Ranges []IDRange `json:"ranges,omitempty"` +} + +// FSGroupStrategyType denotes strategy types for generating FSGroup values for a +// SecurityContext +type FSGroupStrategyType string + +const ( + // container must have FSGroup of X applied. + FSGroupStrategyMustRunAs FSGroupStrategyType = "MustRunAs" + // container may make requests for any FSGroup labels. + FSGroupStrategyRunAsAny FSGroupStrategyType = "RunAsAny" +) + +// SupplementalGroupsStrategyOptions defines the strategy type and options used to create the strategy. +type SupplementalGroupsStrategyOptions struct { + // Rule is the strategy that will dictate what supplemental groups is used in the SecurityContext. + Rule SupplementalGroupsStrategyType `json:"rule,omitempty"` + // Ranges are the allowed ranges of supplemental groups. If you would like to force a single + // supplemental group then supply a single range with the same start and end. + Ranges []IDRange `json:"ranges,omitempty"` +} + +// SupplementalGroupsStrategyType denotes strategy types for determining valid supplemental +// groups for a SecurityContext. +type SupplementalGroupsStrategyType string + +const ( + // container must run as a particular gid. + SupplementalGroupsStrategyMustRunAs SupplementalGroupsStrategyType = "MustRunAs" + // container may make requests for any gid. + SupplementalGroupsStrategyRunAsAny SupplementalGroupsStrategyType = "RunAsAny" +) + // PodSecurityPolicyList is a list of PodSecurityPolicy objects. type PodSecurityPolicyList struct { unversioned.TypeMeta `json:",inline"` diff --git a/pkg/apis/extensions/v1beta1/types.go b/pkg/apis/extensions/v1beta1/types.go index af3b394d093..b7a255d6bf0 100644 --- a/pkg/apis/extensions/v1beta1/types.go +++ b/pkg/apis/extensions/v1beta1/types.go @@ -912,6 +912,8 @@ type ReplicaSetStatus struct { ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` } +// +genclient=true,nonNamespaced=true + // Pod Security Policy governs the ability to make requests that affect the Security Context // that will be applied to a pod and container. type PodSecurityPolicy struct { @@ -928,29 +930,51 @@ type PodSecurityPolicy struct { type PodSecurityPolicySpec struct { // privileged determines if a pod can request to be run as privileged. Privileged bool `json:"privileged,omitempty" protobuf:"varint,1,opt,name=privileged"` - // capabilities is a list of capabilities that can be added. - Capabilities []v1.Capability `json:"capabilities,omitempty" protobuf:"bytes,2,rep,name=capabilities,casttype=k8s.io/kubernetes/pkg/api/v1.Capability"` + // DefaultAddCapabilities is the default set of capabilities that will be added to the container + // unless the pod spec specifically drops the capability. You may not list a capabiility in both + // DefaultAddCapabilities and RequiredDropCapabilities. + DefaultAddCapabilities []v1.Capability `json:"defaultAddCapabilities,omitempty" protobuf:"bytes,2,rep,name=defaultAddCapabilities,casttype=k8s.io/kubernetes/pkg/api/v1.Capability"` + // RequiredDropCapabilities are the capabilities that will be dropped from the container. These + // are required to be dropped and cannot be added. + RequiredDropCapabilities []v1.Capability `json:"requiredDropCapabilities,omitempty" protobuf:"bytes,3,rep,name=requiredDropCapabilities,casttype=k8s.io/kubernetes/pkg/api/v1.Capability"` + // AllowedCapabilities is a list of capabilities that can be requested to add to the container. + // Capabilities in this field may be added at the pod author's discretion. + // You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities. + AllowedCapabilities []v1.Capability `json:"allowedCapabilities,omitempty" protobuf:"bytes,4,rep,name=allowedCapabilities,casttype=k8s.io/kubernetes/pkg/api/v1.Capability"` // volumes is a white list of allowed volume plugins. Empty indicates that all plugins // may be used. - Volumes []FSType `json:"volumes,omitempty" protobuf:"bytes,3,rep,name=volumes,casttype=FSType"` + Volumes []FSType `json:"volumes,omitempty" protobuf:"bytes,5,rep,name=volumes,casttype=FSType"` // hostNetwork determines if the policy allows the use of HostNetwork in the pod spec. - HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,4,opt,name=hostNetwork"` + HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,6,opt,name=hostNetwork"` // hostPorts determines which host port ranges are allowed to be exposed. - HostPorts []HostPortRange `json:"hostPorts,omitempty" protobuf:"bytes,5,rep,name=hostPorts"` + HostPorts []HostPortRange `json:"hostPorts,omitempty" protobuf:"bytes,7,rep,name=hostPorts"` // hostPID determines if the policy allows the use of HostPID in the pod spec. - HostPID bool `json:"hostPID,omitempty" protobuf:"varint,6,opt,name=hostPID"` + HostPID bool `json:"hostPID,omitempty" protobuf:"varint,8,opt,name=hostPID"` // hostIPC determines if the policy allows the use of HostIPC in the pod spec. - HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,7,opt,name=hostIPC"` + HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,9,opt,name=hostIPC"` // seLinux is the strategy that will dictate the allowable labels that may be set. - SELinux SELinuxStrategyOptions `json:"seLinux,omitempty" protobuf:"bytes,8,opt,name=seLinux"` + SELinux SELinuxStrategyOptions `json:"seLinux" protobuf:"bytes,10,opt,name=seLinux"` // runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set. - RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty" protobuf:"bytes,9,opt,name=runAsUser"` + RunAsUser RunAsUserStrategyOptions `json:"runAsUser" protobuf:"bytes,11,opt,name=runAsUser"` + // SupplementalGroups is the strategy that will dictate what supplemental groups are used by the SecurityContext. + SupplementalGroups SupplementalGroupsStrategyOptions `json:"supplementalGroups" protobuf:"bytes,12,opt,name=supplementalGroups"` + // FSGroup is the strategy that will dictate what fs group is used by the SecurityContext. + FSGroup FSGroupStrategyOptions `json:"fsGroup" protobuf:"bytes,13,opt,name=fsGroup"` + // ReadOnlyRootFilesystem when set to true will force containers to run with a read only root file + // system. If the container specifically requests to run with a non-read only root file system + // the PSP should deny the pod. + // If set to false the container may run with a read only root file system if it wishes but it + // will not be forced to. + ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty" protobuf:"varint,14,opt,name=readOnlyRootFilesystem"` } // FS Type gives strong typing to different file systems that are used by volumes. type FSType string var ( + AzureFile FSType = "azureFile" + Flocker FSType = "flocker" + FlexVolume FSType = "flexVolume" HostPath FSType = "hostPath" EmptyDir FSType = "emptyDir" GCEPersistentDisk FSType = "gcePersistentDisk" @@ -966,6 +990,8 @@ var ( CephFS FSType = "cephFS" DownwardAPI FSType = "downwardAPI" FC FSType = "fc" + ConfigMap FSType = "configMap" + All FSType = "*" ) // Host Port Range defines a range of host ports that will be enabled by a policy @@ -1026,6 +1052,46 @@ const ( RunAsUserStrategyRunAsAny RunAsUserStrategy = "RunAsAny" ) +// FSGroupStrategyOptions defines the strategy type and options used to create the strategy. +type FSGroupStrategyOptions struct { + // Rule is the strategy that will dictate what FSGroup is used in the SecurityContext. + Rule FSGroupStrategyType `json:"rule,omitempty" protobuf:"bytes,1,opt,name=rule,casttype=FSGroupStrategyType"` + // Ranges are the allowed ranges of fs groups. If you would like to force a single + // fs group then supply a single range with the same start and end. + Ranges []IDRange `json:"ranges,omitempty" protobuf:"bytes,2,rep,name=ranges"` +} + +// FSGroupStrategyType denotes strategy types for generating FSGroup values for a +// SecurityContext +type FSGroupStrategyType string + +const ( + // container must have FSGroup of X applied. + FSGroupStrategyMustRunAs FSGroupStrategyType = "MustRunAs" + // container may make requests for any FSGroup labels. + FSGroupStrategyRunAsAny FSGroupStrategyType = "RunAsAny" +) + +// SupplementalGroupsStrategyOptions defines the strategy type and options used to create the strategy. +type SupplementalGroupsStrategyOptions struct { + // Rule is the strategy that will dictate what supplemental groups is used in the SecurityContext. + Rule SupplementalGroupsStrategyType `json:"rule,omitempty" protobuf:"bytes,1,opt,name=rule,casttype=SupplementalGroupsStrategyType"` + // Ranges are the allowed ranges of supplemental groups. If you would like to force a single + // supplemental group then supply a single range with the same start and end. + Ranges []IDRange `json:"ranges,omitempty" protobuf:"bytes,2,rep,name=ranges"` +} + +// SupplementalGroupsStrategyType denotes strategy types for determining valid supplemental +// groups for a SecurityContext. +type SupplementalGroupsStrategyType string + +const ( + // container must run as a particular gid. + SupplementalGroupsStrategyMustRunAs SupplementalGroupsStrategyType = "MustRunAs" + // container may make requests for any gid. + SupplementalGroupsStrategyRunAsAny SupplementalGroupsStrategyType = "RunAsAny" +) + // Pod Security Policy List is a list of PodSecurityPolicy objects. type PodSecurityPolicyList struct { unversioned.TypeMeta `json:",inline"` diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go index ddb24505b92..4ef1e7ff009 100644 --- a/pkg/apis/extensions/validation/validation.go +++ b/pkg/apis/extensions/validation/validation.go @@ -17,6 +17,7 @@ limitations under the License. package validation import ( + "fmt" "net" "regexp" "strconv" @@ -28,6 +29,7 @@ import ( apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/labels" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" "k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/validation" @@ -529,7 +531,11 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa 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)...) + allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.DefaultAddCapabilities, field.NewPath("defaultAddCapabilities"))...) + allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.AllowedCapabilities, field.NewPath("allowedCapabilities"))...) return allErrs } @@ -568,24 +574,48 @@ func validatePSPRunAsUser(fldPath *field.Path, runAsUser *extensions.RunAsUserSt return allErrs } +// validatePSPFSGroup validates the FSGroupStrategyOptions fields of the PodSecurityPolicy. +func validatePSPFSGroup(fldPath *field.Path, groupOptions *extensions.FSGroupStrategyOptions) field.ErrorList { + allErrs := field.ErrorList{} + + supportedRules := sets.NewString( + string(extensions.FSGroupStrategyMustRunAs), + string(extensions.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, validateIDRanges(fldPath.Child("ranges").Index(idx), rng)...) + } + return allErrs +} + +// validatePSPSupplementalGroup validates the SupplementalGroupsStrategyOptions fields of the PodSecurityPolicy. +func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *extensions.SupplementalGroupsStrategyOptions) field.ErrorList { + allErrs := field.ErrorList{} + + supportedRules := sets.NewString( + string(extensions.SupplementalGroupsStrategyRunAsAny), + string(extensions.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, validateIDRanges(fldPath.Child("ranges").Index(idx), rng)...) + } + return allErrs +} + // validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy. func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []extensions.FSType) field.ErrorList { allErrs := field.ErrorList{} - allowed := sets.NewString(string(extensions.HostPath), - string(extensions.EmptyDir), - string(extensions.GCEPersistentDisk), - string(extensions.AWSElasticBlockStore), - string(extensions.GitRepo), - string(extensions.Secret), - string(extensions.NFS), - string(extensions.ISCSI), - string(extensions.Glusterfs), - string(extensions.PersistentVolumeClaim), - string(extensions.RBD), - string(extensions.Cinder), - string(extensions.CephFS), - string(extensions.DownwardAPI), - string(extensions.FC)) + allowed := psputil.GetAllFSTypesAsSet() + // add in the * value since that is a pseudo type that is not included by default + allowed.Insert(string(extensions.All)) for _, v := range volumes { if !allowed.Has(string(v)) { allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List())) @@ -614,6 +644,31 @@ func validateIDRanges(fldPath *field.Path, rng extensions.IDRange) field.ErrorLi return allErrs } +// validatePSPCapsAgainstDrops ensures an allowed cap is not listed in the required drops. +func validatePSPCapsAgainstDrops(requiredDrops []api.Capability, capsToCheck []api.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 +} + +// hasCap checks for needle in haystack. +func hasCap(needle api.Capability, haystack []api.Capability) bool { + for _, c := range haystack { + if needle == c { + return true + } + } + return false +} + // ValidatePodSecurityPolicyUpdate validates a PSP for updates. func ValidatePodSecurityPolicyUpdate(old *extensions.PodSecurityPolicy, new *extensions.PodSecurityPolicy) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index c1632fa6d81..c019f3ed1ac 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -24,7 +24,9 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" "k8s.io/kubernetes/pkg/util/intstr" + "k8s.io/kubernetes/pkg/util/validation/field" ) func TestValidateDaemonSetStatusUpdate(t *testing.T) { @@ -1418,7 +1420,7 @@ func TestValidateReplicaSet(t *testing.T) { } func TestValidatePodSecurityPolicy(t *testing.T) { - validSCC := func() *extensions.PodSecurityPolicy { + validPSP := func() *extensions.PodSecurityPolicy { return &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: extensions.PodSecurityPolicySpec{ @@ -1428,118 +1430,250 @@ func TestValidatePodSecurityPolicy(t *testing.T) { RunAsUser: extensions.RunAsUserStrategyOptions{ Rule: extensions.RunAsUserStrategyRunAsAny, }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, }, } } - noUserOptions := validSCC() + noUserOptions := validPSP() noUserOptions.Spec.RunAsUser.Rule = "" - noSELinuxOptions := validSCC() + noSELinuxOptions := validPSP() noSELinuxOptions.Spec.SELinux.Rule = "" - invalidUserStratRule := validSCC() - invalidUserStratRule.Spec.RunAsUser.Rule = "invalid" + invalidUserStratType := validPSP() + invalidUserStratType.Spec.RunAsUser.Rule = "invalid" - invalidSELinuxStratRule := validSCC() - invalidSELinuxStratRule.Spec.SELinux.Rule = "invalid" + invalidSELinuxStratType := validPSP() + invalidSELinuxStratType.Spec.SELinux.Rule = "invalid" - missingObjectMetaName := validSCC() + invalidUIDPSP := validPSP() + invalidUIDPSP.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs + invalidUIDPSP.Spec.RunAsUser.Ranges = []extensions.IDRange{ + {Min: -1, Max: 1}, + } + + missingObjectMetaName := validPSP() missingObjectMetaName.ObjectMeta.Name = "" - invalidRangeMinGreaterThanMax := validSCC() - invalidRangeMinGreaterThanMax.Spec.RunAsUser.Ranges = []extensions.IDRange{ + 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 = []extensions.IDRange{ {Min: 2, Max: 1}, } - invalidRangeNegativeMin := validSCC() - invalidRangeNegativeMin.Spec.RunAsUser.Ranges = []extensions.IDRange{ + invalidRangeNegativeMin := validPSP() + invalidRangeNegativeMin.Spec.FSGroup.Ranges = []extensions.IDRange{ {Min: -1, Max: 10}, } - invalidRangeNegativeMax := validSCC() - invalidRangeNegativeMax.Spec.RunAsUser.Ranges = []extensions.IDRange{ + invalidRangeNegativeMax := validPSP() + invalidRangeNegativeMax.Spec.FSGroup.Ranges = []extensions.IDRange{ {Min: 1, Max: -10}, } + 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"} + errorCases := map[string]struct { - scc *extensions.PodSecurityPolicy + psp *extensions.PodSecurityPolicy + errorType field.ErrorType errorDetail string }{ "no user options": { - scc: noUserOptions, + psp: noUserOptions, + errorType: field.ErrorTypeNotSupported, errorDetail: "supported values: MustRunAs, MustRunAsNonRoot, RunAsAny", }, "no selinux options": { - scc: noSELinuxOptions, + psp: noSELinuxOptions, + errorType: field.ErrorTypeNotSupported, errorDetail: "supported values: MustRunAs, RunAsAny", }, - "invalid user strategy rule": { - scc: invalidUserStratRule, + "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 rule": { - scc: invalidSELinuxStratRule, + "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": { - scc: missingObjectMetaName, + psp: missingObjectMetaName, + errorType: field.ErrorTypeRequired, errorDetail: "name or generateName is required", }, "invalid range min greater than max": { - scc: invalidRangeMinGreaterThanMax, + psp: invalidRangeMinGreaterThanMax, + errorType: field.ErrorTypeInvalid, errorDetail: "min cannot be greater than max", }, "invalid range negative min": { - scc: invalidRangeNegativeMin, + psp: invalidRangeNegativeMin, + errorType: field.ErrorTypeInvalid, errorDetail: "min cannot be negative", }, "invalid range negative max": { - scc: invalidRangeNegativeMax, + psp: invalidRangeNegativeMax, + errorType: field.ErrorTypeInvalid, errorDetail: "max cannot be negative", }, + "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", + }, } for k, v := range errorCases { - if errs := ValidatePodSecurityPolicy(v.scc); len(errs) == 0 || errs[0].Detail != v.errorDetail { - t.Errorf("Expected error with detail %s for %s, got %v", v.errorDetail, k, errs[0].Detail) + 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: %v got: %v", k, v.errorType, errs[0].Type) + } + if errs[0].Detail != v.errorDetail { + t.Errorf("%s received an unexpected error detail. Expected %v got: %v", k, v.errorDetail, errs[0].Detail) } } - mustRunAs := validSCC() + mustRunAs := validPSP() + mustRunAs.Spec.FSGroup.Rule = extensions.FSGroupStrategyMustRunAs + mustRunAs.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyMustRunAs mustRunAs.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs mustRunAs.Spec.RunAsUser.Ranges = []extensions.IDRange{ - { - Min: 1, - Max: 1, - }, + {Min: 1, Max: 1}, } mustRunAs.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs - runAsNonRoot := validSCC() + runAsNonRoot := validPSP() runAsNonRoot.Spec.RunAsUser.Rule = extensions.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"} + successCases := map[string]struct { - scc *extensions.PodSecurityPolicy + psp *extensions.PodSecurityPolicy }{ "must run as": { - scc: mustRunAs, + psp: mustRunAs, }, "run as any": { - scc: validSCC(), + psp: validPSP(), }, "run as non-root (user only)": { - scc: runAsNonRoot, + psp: runAsNonRoot, + }, + "comparison for add -> drop is case sensitive": { + psp: caseInsensitiveAddDrop, + }, + "comparison for allowed -> drop is case sensitive": { + psp: caseInsensitiveAllowedDrop, }, } for k, v := range successCases { - if errs := ValidatePodSecurityPolicy(v.scc); len(errs) != 0 { + if errs := ValidatePodSecurityPolicy(v.psp); len(errs) != 0 { t.Errorf("Expected success for %s, got %v", k, errs) } } } +func TestValidatePSPVolumes(t *testing.T) { + validPSP := func() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: extensions.PodSecurityPolicySpec{ + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, + }, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyRunAsAny, + }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, + }, + } + } + + volumes := psputil.GetAllFSTypesAsSet() + // add in the * value since that is a pseudo type that is not included by default + volumes.Insert(string(extensions.All)) + + for _, strVolume := range volumes.List() { + psp := validPSP() + psp.Spec.Volumes = []extensions.FSType{extensions.FSType(strVolume)} + errs := ValidatePodSecurityPolicy(psp) + if len(errs) != 0 { + t.Errorf("%s validation expected no errors but received %v", strVolume, errs) + } + } +} + func newBool(val bool) *bool { p := new(bool) *p = val diff --git a/pkg/client/unversioned/podsecuritypolicy.go b/pkg/client/unversioned/podsecuritypolicy.go index 64a34b4b23b..356d913dbea 100644 --- a/pkg/client/unversioned/podsecuritypolicy.go +++ b/pkg/client/unversioned/podsecuritypolicy.go @@ -28,7 +28,7 @@ type PodSecurityPoliciesInterface interface { type PodSecurityPolicyInterface interface { Get(name string) (result *extensions.PodSecurityPolicy, err error) - Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) + Create(psp *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) List(opts api.ListOptions) (*extensions.PodSecurityPolicyList, error) Delete(name string) error Update(*extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) @@ -45,11 +45,11 @@ func newPodSecurityPolicy(c *ExtensionsClient) *podSecurityPolicy { return &podSecurityPolicy{c} } -func (s *podSecurityPolicy) Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { +func (s *podSecurityPolicy) Create(psp *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { result := &extensions.PodSecurityPolicy{} err := s.client.Post(). Resource("podsecuritypolicies"). - Body(scc). + Body(psp). Do(). Into(result) diff --git a/pkg/client/unversioned/podsecuritypolicy_test.go b/pkg/client/unversioned/podsecuritypolicy_test.go index d51e2c5a04a..06fae477e3a 100644 --- a/pkg/client/unversioned/podsecuritypolicy_test.go +++ b/pkg/client/unversioned/podsecuritypolicy_test.go @@ -29,7 +29,7 @@ import ( func TestPodSecurityPolicyCreate(t *testing.T) { ns := api.NamespaceNone - scc := &extensions.PodSecurityPolicy{ + psp := &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{ Name: "abc", }, @@ -40,18 +40,18 @@ func TestPodSecurityPolicyCreate(t *testing.T) { Method: "POST", Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, ""), Query: simple.BuildQueryValues(nil), - Body: scc, + Body: psp, }, - Response: simple.Response{StatusCode: 200, Body: scc}, + Response: simple.Response{StatusCode: 200, Body: psp}, } - response, err := c.Setup(t).PodSecurityPolicies().Create(scc) + response, err := c.Setup(t).PodSecurityPolicies().Create(psp) c.Validate(t, response, err) } func TestPodSecurityPolicyGet(t *testing.T) { ns := api.NamespaceNone - scc := &extensions.PodSecurityPolicy{ + psp := &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{ Name: "abc", }, @@ -63,7 +63,7 @@ func TestPodSecurityPolicyGet(t *testing.T) { Query: simple.BuildQueryValues(nil), Body: nil, }, - Response: simple.Response{StatusCode: 200, Body: scc}, + Response: simple.Response{StatusCode: 200, Body: psp}, } response, err := c.Setup(t).PodSecurityPolicies().Get("abc") @@ -72,7 +72,7 @@ func TestPodSecurityPolicyGet(t *testing.T) { func TestPodSecurityPolicyList(t *testing.T) { ns := api.NamespaceNone - sccList := &extensions.PodSecurityPolicyList{ + pspList := &extensions.PodSecurityPolicyList{ Items: []extensions.PodSecurityPolicy{ { ObjectMeta: api.ObjectMeta{ @@ -88,7 +88,7 @@ func TestPodSecurityPolicyList(t *testing.T) { Query: simple.BuildQueryValues(nil), Body: nil, }, - Response: simple.Response{StatusCode: 200, Body: sccList}, + Response: simple.Response{StatusCode: 200, Body: pspList}, } response, err := c.Setup(t).PodSecurityPolicies().List(api.ListOptions{}) c.Validate(t, response, err) @@ -96,7 +96,7 @@ func TestPodSecurityPolicyList(t *testing.T) { func TestPodSecurityPolicyUpdate(t *testing.T) { ns := api.NamespaceNone - scc := &extensions.PodSecurityPolicy{ + psp := &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{ Name: "abc", ResourceVersion: "1", @@ -104,9 +104,9 @@ func TestPodSecurityPolicyUpdate(t *testing.T) { } c := &simple.Client{ Request: simple.Request{Method: "PUT", Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, "abc"), Query: simple.BuildQueryValues(nil)}, - Response: simple.Response{StatusCode: 200, Body: scc}, + Response: simple.Response{StatusCode: 200, Body: psp}, } - response, err := c.Setup(t).PodSecurityPolicies().Update(scc) + response, err := c.Setup(t).PodSecurityPolicies().Update(psp) c.Validate(t, response, err) } diff --git a/pkg/client/unversioned/testclient/fake_podsecuritypolicy.go b/pkg/client/unversioned/testclient/fake_podsecuritypolicy.go index bb611d322c5..06bd10991fb 100644 --- a/pkg/client/unversioned/testclient/fake_podsecuritypolicy.go +++ b/pkg/client/unversioned/testclient/fake_podsecuritypolicy.go @@ -47,16 +47,16 @@ func (c *FakePodSecurityPolicy) Get(name string) (*extensions.PodSecurityPolicy, return obj.(*extensions.PodSecurityPolicy), err } -func (c *FakePodSecurityPolicy) Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { - obj, err := c.Fake.Invokes(NewCreateAction("podsecuritypolicies", c.Namespace, scc), &extensions.PodSecurityPolicy{}) +func (c *FakePodSecurityPolicy) Create(psp *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { + obj, err := c.Fake.Invokes(NewCreateAction("podsecuritypolicies", c.Namespace, psp), &extensions.PodSecurityPolicy{}) if obj == nil { return nil, err } return obj.(*extensions.PodSecurityPolicy), err } -func (c *FakePodSecurityPolicy) Update(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { - obj, err := c.Fake.Invokes(NewUpdateAction("podsecuritypolicies", c.Namespace, scc), &extensions.PodSecurityPolicy{}) +func (c *FakePodSecurityPolicy) Update(psp *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) { + obj, err := c.Fake.Invokes(NewUpdateAction("podsecuritypolicies", c.Namespace, psp), &extensions.PodSecurityPolicy{}) if obj == nil { return nil, err } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 5c605877936..a649ac861a6 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -1689,9 +1689,9 @@ func printConfigMapList(list *api.ConfigMapList, w io.Writer, options PrintOptio } func printPodSecurityPolicy(item *extensions.PodSecurityPolicy, w io.Writer, options PrintOptions) error { - _, err := fmt.Fprintf(w, "%s\t%t\t%v\t%v\t%s\t%s\n", item.Name, item.Spec.Privileged, - item.Spec.Capabilities, item.Spec.Volumes, item.Spec.SELinux.Rule, - item.Spec.RunAsUser.Rule) + _, err := fmt.Fprintf(w, "%s\t%t\t%v\t%s\t%s\t%s\t%s\t%t\t%v\n", item.Name, item.Spec.Privileged, + item.Spec.AllowedCapabilities, item.Spec.SELinux.Rule, + item.Spec.RunAsUser.Rule, item.Spec.FSGroup.Rule, item.Spec.SupplementalGroups.Rule, item.Spec.ReadOnlyRootFilesystem, item.Spec.Volumes) return err } diff --git a/pkg/registry/podsecuritypolicy/etcd/etcd_test.go b/pkg/registry/podsecuritypolicy/etcd/etcd_test.go index 525bb9e19d2..b0c4371025b 100644 --- a/pkg/registry/podsecuritypolicy/etcd/etcd_test.go +++ b/pkg/registry/podsecuritypolicy/etcd/etcd_test.go @@ -49,6 +49,12 @@ func validNewPodSecurityPolicy() *extensions.PodSecurityPolicy { RunAsUser: extensions.RunAsUserStrategyOptions{ Rule: extensions.RunAsUserStrategyRunAsAny, }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, }, } } @@ -57,11 +63,11 @@ func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) test := registrytest.New(t, storage.Store).ClusterScope() - scc := validNewPodSecurityPolicy() - scc.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"} + psp := validNewPodSecurityPolicy() + psp.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"} test.TestCreate( // valid - scc, + psp, // invalid &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{Name: "name with spaces"}, diff --git a/pkg/security/doc.go b/pkg/security/doc.go new file mode 100644 index 00000000000..7638b29591b --- /dev/null +++ b/pkg/security/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 security contains security apis and implementations. +package security diff --git a/pkg/security/podsecuritypolicy/capabilities/mustrunas.go b/pkg/security/podsecuritypolicy/capabilities/mustrunas.go new file mode 100644 index 00000000000..1ffc55fdba7 --- /dev/null +++ b/pkg/security/podsecuritypolicy/capabilities/mustrunas.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 capabilities + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// defaultCapabilities implements the CapabilitiesStrategy interface +type defaultCapabilities struct { + defaultAddCapabilities []api.Capability + requiredDropCapabilities []api.Capability + allowedCaps []api.Capability +} + +var _ CapabilitiesStrategy = &defaultCapabilities{} + +// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation +// based on the configured initial caps and allowed caps. +func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []api.Capability) (CapabilitiesStrategy, error) { + return &defaultCapabilities{ + defaultAddCapabilities: defaultAddCapabilities, + requiredDropCapabilities: requiredDropCapabilities, + allowedCaps: allowedCaps, + }, nil +} + +// Generate creates the capabilities based on policy rules. Generate will produce the following: +// 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 +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() + + if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { + containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add) + containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop) + } + + // remove any default adds that the container is specifically dropping + defaultAdd = defaultAdd.Difference(containerDrop) + + combinedAdd := defaultAdd.Union(containerAdd).List() + combinedDrop := requiredDrop.Union(containerDrop).List() + + // nothing generated? return nil + if len(combinedAdd) == 0 && len(combinedDrop) == 0 { + return nil, nil + } + + return &api.Capabilities{ + Add: capabilityFromStringSlice(combinedAdd), + Drop: capabilityFromStringSlice(combinedDrop), + }, 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 { + 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 container.SC.Caps is nil then nothing was defaulted by the strat 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. + if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 { + return allErrs + } + + // 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, + "required capabilities are not set on the securityContext")) + return allErrs + } + + // validate that anything being added is in the default or allowed sets + defaultAdd := makeCapSet(s.defaultAddCapabilities) + allowedAdd := makeCapSet(s.allowedCaps) + + for _, cap := range container.SecurityContext.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")) + } + } + + // validate that anything that is required to be dropped is in the drop set + containerDrops := makeCapSet(container.SecurityContext.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, + fmt.Sprintf("%s is required to be dropped but was not found", sDrop))) + } + } + + return allErrs +} + +// capabilityFromStringSlice creates a capability slice from a string slice. +func capabilityFromStringSlice(slice []string) []api.Capability { + if len(slice) == 0 { + return nil + } + caps := []api.Capability{} + for _, c := range slice { + caps = append(caps, api.Capability(c)) + } + return caps +} + +// makeCapSet makes a string set from capabilities and normalizes them to be all lower case to help +// with comparisons. +func makeCapSet(caps []api.Capability) sets.String { + s := sets.NewString() + for _, c := range caps { + s.Insert(string(c)) + } + return s +} diff --git a/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go b/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go new file mode 100644 index 00000000000..46cd2a2a4ec --- /dev/null +++ b/pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go @@ -0,0 +1,387 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 capabilities + +import ( + "k8s.io/kubernetes/pkg/api" + "reflect" + "testing" +) + +func TestGenerateAdds(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + requiredDropCaps []api.Capability + containerCaps *api.Capabilities + expectedCaps *api.Capabilities + }{ + "no required, no container requests": { + expectedCaps: nil, + }, + "required, no container requests": { + defaultAddCaps: []api.Capability{"foo"}, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "required, container requests add required": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "multiple required, container requests add required": { + defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "baz", "foo"}, + }, + }, + "required, container requests add non-required": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "foo"}, + }, + }, + "generation dedupes": { + defaultAddCaps: []api.Capability{"foo", "foo", "foo", "foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo", "foo", "foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + }, + "generation is case sensitive - will not dedupe": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"FOO"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"FOO", "foo"}, + }, + }, + } + + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + generatedCaps, err := strategy.Generate(nil, container) + if err != nil { + t.Errorf("%s failed generating: %v", k, err) + continue + } + if v.expectedCaps == nil && generatedCaps != nil { + t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps) + continue + } + if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { + t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) + } + } +} + +func TestGenerateDrops(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + requiredDropCaps []api.Capability + containerCaps *api.Capabilities + expectedCaps *api.Capabilities + }{ + "no required, no container requests": { + expectedCaps: nil, + }, + "required drops are defaulted": { + requiredDropCaps: []api.Capability{"foo"}, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + }, + "required drops are defaulted when making container requests": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo", "bar"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"bar", "foo"}, + }, + }, + "can drop a required add": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + }, + "can drop non-required add": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + Drop: []api.Capability{"bar"}, + }, + }, + "defaulting adds and drops, dropping a required add": { + defaultAddCaps: []api.Capability{"foo", "bar", "baz"}, + requiredDropCaps: []api.Capability{"abc"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + expectedCaps: &api.Capabilities{ + Add: []api.Capability{"bar", "baz"}, + Drop: []api.Capability{"abc", "foo"}, + }, + }, + "generation dedupes": { + requiredDropCaps: []api.Capability{"bar", "bar", "bar", "bar"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar", "bar", "bar"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"bar"}, + }, + }, + "generation is case sensitive - will not dedupe": { + requiredDropCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"BAR"}, + }, + expectedCaps: &api.Capabilities{ + Drop: []api.Capability{"BAR", "bar"}, + }, + }, + } + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + generatedCaps, err := strategy.Generate(nil, container) + if err != nil { + t.Errorf("%s failed generating: %v", k, err) + continue + } + if v.expectedCaps == nil && generatedCaps != nil { + t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps) + continue + } + if !reflect.DeepEqual(v.expectedCaps, generatedCaps) { + t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps) + } + } +} + +func TestValidateAdds(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + requiredDropCaps []api.Capability + allowedCaps []api.Capability + containerCaps *api.Capabilities + shouldPass bool + }{ + // no container requests + "no required, no allowed, no container requests": { + shouldPass: true, + }, + "no required, allowed, no container requests": { + allowedCaps: []api.Capability{"foo"}, + shouldPass: true, + }, + "required, no allowed, no container requests": { + defaultAddCaps: []api.Capability{"foo"}, + shouldPass: false, + }, + + // container requests match required + "required, no allowed, container requests valid": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + shouldPass: true, + }, + "required, no allowed, container requests invalid": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + shouldPass: false, + }, + + // container requests match allowed + "no required, allowed, container requests valid": { + allowedCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + shouldPass: true, + }, + "no required, allowed, container requests invalid": { + allowedCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + shouldPass: false, + }, + + // required and allowed + "required, allowed, container requests valid required": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"foo"}, + }, + shouldPass: true, + }, + "required, allowed, container requests valid allowed": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"bar"}, + }, + shouldPass: true, + }, + "required, allowed, container requests invalid": { + defaultAddCaps: []api.Capability{"foo"}, + allowedCaps: []api.Capability{"bar"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"baz"}, + }, + shouldPass: false, + }, + "validation is case sensitive": { + defaultAddCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Add: []api.Capability{"FOO"}, + }, + shouldPass: false, + }, + } + + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, v.allowedCaps) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + errs := strategy.Validate(nil, container) + if v.shouldPass && len(errs) > 0 { + t.Errorf("%s should have passed but had errors %v", k, errs) + continue + } + if !v.shouldPass && len(errs) == 0 { + t.Errorf("%s should have failed but recieved no errors", k) + } + } +} + +func TestValidateDrops(t *testing.T) { + tests := map[string]struct { + defaultAddCaps []api.Capability + requiredDropCaps []api.Capability + containerCaps *api.Capabilities + shouldPass bool + }{ + // no container requests + "no required, no container requests": { + shouldPass: true, + }, + "required, no container requests": { + requiredDropCaps: []api.Capability{"foo"}, + shouldPass: false, + }, + + // container requests match required + "required, container requests valid": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"foo"}, + }, + shouldPass: true, + }, + "required, container requests invalid": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"bar"}, + }, + shouldPass: false, + }, + "validation is case sensitive": { + requiredDropCaps: []api.Capability{"foo"}, + containerCaps: &api.Capabilities{ + Drop: []api.Capability{"FOO"}, + }, + shouldPass: false, + }, + } + + for k, v := range tests { + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + Capabilities: v.containerCaps, + }, + } + + strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil) + if err != nil { + t.Errorf("%s failed: %v", k, err) + continue + } + errs := strategy.Validate(nil, container) + if v.shouldPass && len(errs) > 0 { + t.Errorf("%s should have passed but had errors %v", k, errs) + continue + } + if !v.shouldPass && len(errs) == 0 { + t.Errorf("%s should have failed but recieved no errors", k) + } + } +} diff --git a/pkg/security/podsecuritypolicy/capabilities/types.go b/pkg/security/podsecuritypolicy/capabilities/types.go new file mode 100644 index 00000000000..428f2455842 --- /dev/null +++ b/pkg/security/podsecuritypolicy/capabilities/types.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 capabilities + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// CapabilitiesStrategy defines the interface for all cap constraint strategies. +type CapabilitiesStrategy 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 +} diff --git a/pkg/security/podsecuritypolicy/factory.go b/pkg/security/podsecuritypolicy/factory.go new file mode 100644 index 00000000000..477845f1476 --- /dev/null +++ b/pkg/security/podsecuritypolicy/factory.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "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/user" + "k8s.io/kubernetes/pkg/util/errors" +) + +type simpleStrategyFactory struct{} + +var _ StrategyFactory = &simpleStrategyFactory{} + +func NewSimpleStrategyFactory() StrategyFactory { + return &simpleStrategyFactory{} +} + +func (f *simpleStrategyFactory) CreateStrategies(psp *extensions.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) { + errs := []error{} + + userStrat, err := createUserStrategy(&psp.Spec.RunAsUser) + if err != nil { + errs = append(errs, err) + } + + seLinuxStrat, err := createSELinuxStrategy(&psp.Spec.SELinux) + if err != nil { + errs = append(errs, err) + } + + fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup) + if err != nil { + errs = append(errs, err) + } + + supGroupStrat, err := createSupplementalGroupStrategy(&psp.Spec.SupplementalGroups) + if err != nil { + errs = append(errs, err) + } + + capStrat, err := createCapabilitiesStrategy(psp.Spec.DefaultAddCapabilities, psp.Spec.RequiredDropCapabilities, psp.Spec.AllowedCapabilities) + if err != nil { + errs = append(errs, err) + } + + if len(errs) > 0 { + return nil, errors.NewAggregate(errs) + } + + strategies := &ProviderStrategies{ + RunAsUserStrategy: userStrat, + SELinuxStrategy: seLinuxStrat, + FSGroupStrategy: fsGroupStrat, + SupplementalGroupStrategy: supGroupStrat, + CapabilitiesStrategy: capStrat, + } + + return strategies, nil +} + +// createUserStrategy creates a new user strategy. +func createUserStrategy(opts *extensions.RunAsUserStrategyOptions) (user.RunAsUserStrategy, error) { + switch opts.Rule { + case extensions.RunAsUserStrategyMustRunAs: + return user.NewMustRunAs(opts) + case extensions.RunAsUserStrategyMustRunAsNonRoot: + return user.NewRunAsNonRoot(opts) + case extensions.RunAsUserStrategyRunAsAny: + return user.NewRunAsAny(opts) + default: + return nil, fmt.Errorf("Unrecognized RunAsUser strategy type %s", opts.Rule) + } +} + +// createSELinuxStrategy creates a new selinux strategy. +func createSELinuxStrategy(opts *extensions.SELinuxStrategyOptions) (selinux.SELinuxStrategy, error) { + switch opts.Rule { + case extensions.SELinuxStrategyMustRunAs: + return selinux.NewMustRunAs(opts) + case extensions.SELinuxStrategyRunAsAny: + return selinux.NewRunAsAny(opts) + default: + return nil, fmt.Errorf("Unrecognized SELinuxContext strategy type %s", opts.Rule) + } +} + +// createFSGroupStrategy creates a new fsgroup strategy +func createFSGroupStrategy(opts *extensions.FSGroupStrategyOptions) (group.GroupStrategy, error) { + switch opts.Rule { + case extensions.FSGroupStrategyRunAsAny: + return group.NewRunAsAny() + case extensions.FSGroupStrategyMustRunAs: + return group.NewMustRunAs(opts.Ranges, fsGroupField) + default: + return nil, fmt.Errorf("Unrecognized FSGroup strategy type %s", opts.Rule) + } +} + +// createSupplementalGroupStrategy creates a new supplemental group strategy +func createSupplementalGroupStrategy(opts *extensions.SupplementalGroupsStrategyOptions) (group.GroupStrategy, error) { + switch opts.Rule { + case extensions.SupplementalGroupsStrategyRunAsAny: + return group.NewRunAsAny() + case extensions.SupplementalGroupsStrategyMustRunAs: + return group.NewMustRunAs(opts.Ranges, supplementalGroupsField) + default: + return nil, fmt.Errorf("Unrecognized SupplementalGroups strategy type %s", opts.Rule) + } +} + +// createCapabilitiesStrategy creates a new capabilities strategy. +func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []api.Capability) (capabilities.CapabilitiesStrategy, error) { + return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps) +} diff --git a/pkg/security/podsecuritypolicy/group/mustrunas.go b/pkg/security/podsecuritypolicy/group/mustrunas.go new file mode 100644 index 00000000000..bcb2edade9f --- /dev/null +++ b/pkg/security/podsecuritypolicy/group/mustrunas.go @@ -0,0 +1,93 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 group + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// mustRunAs implements the GroupStrategy interface +type mustRunAs struct { + ranges []extensions.IDRange + field string +} + +var _ GroupStrategy = &mustRunAs{} + +// NewMustRunAs provides a new MustRunAs strategy based on ranges. +func NewMustRunAs(ranges []extensions.IDRange, field string) (GroupStrategy, error) { + if len(ranges) == 0 { + return nil, fmt.Errorf("ranges must be supplied for MustRunAs") + } + return &mustRunAs{ + ranges: ranges, + field: field, + }, nil +} + +// 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) { + 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) { + single := new(int64) + *single = s.ranges[0].Min + return single, nil +} + +// 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 { + 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")) + } + + for _, group := range groups { + if !s.isGroupValid(group) { + detail := fmt.Sprintf("%d is not an allowed group", group) + allErrs = append(allErrs, field.Invalid(field.NewPath(s.field), groups, detail)) + } + } + + return allErrs +} + +func (s *mustRunAs) isGroupValid(group int64) bool { + for _, rng := range s.ranges { + if psputil.FallsInRange(group, rng) { + return true + } + } + return false +} diff --git a/pkg/security/podsecuritypolicy/group/mustrunas_test.go b/pkg/security/podsecuritypolicy/group/mustrunas_test.go new file mode 100644 index 00000000000..31c2c21098b --- /dev/null +++ b/pkg/security/podsecuritypolicy/group/mustrunas_test.go @@ -0,0 +1,193 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 group + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestMustRunAsOptions(t *testing.T) { + tests := map[string]struct { + ranges []extensions.IDRange + pass bool + }{ + "empty": { + ranges: []extensions.IDRange{}, + }, + "ranges": { + ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + pass: true, + }, + } + + for k, v := range tests { + _, err := NewMustRunAs(v.ranges, "") + if v.pass && err != nil { + t.Errorf("error creating strategy for %s: %v", k, err) + } + if !v.pass && err == nil { + t.Errorf("expected error for %s but got none", k) + } + } +} + +func TestGenerate(t *testing.T) { + tests := map[string]struct { + ranges []extensions.IDRange + expected []int64 + }{ + "multi value": { + ranges: []extensions.IDRange{ + {Min: 1, Max: 2}, + }, + expected: []int64{1}, + }, + "single value": { + ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + expected: []int64{1}, + }, + "multi range": { + ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + {Min: 2, Max: 500}, + }, + expected: []int64{1}, + }, + } + + for k, v := range tests { + s, err := NewMustRunAs(v.ranges, "") + if err != nil { + t.Errorf("error creating strategy for %s: %v", k, err) + } + actual, err := s.Generate(nil) + if err != nil { + t.Errorf("unexpected error for %s: %v", k, err) + } + if len(actual) != len(v.expected) { + t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual) + continue + } + if len(actual) > 0 && len(v.expected) > 0 { + if actual[0] != v.expected[0] { + t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual) + } + } + + single, err := s.GenerateSingle(nil) + if err != nil { + t.Errorf("unexpected error for %s: %v", k, err) + } + if single == nil { + t.Errorf("unexpected nil generated value for %s: %v", k, single) + } + if *single != v.expected[0] { + t.Errorf("unexpected generated single value. Expected %v, got %v", v.expected, actual) + } + } +} + +func TestValidate(t *testing.T) { + validPod := func() *api.Pod { + return &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{}, + }, + } + } + + tests := map[string]struct { + ranges []extensions.IDRange + pod *api.Pod + groups []int64 + pass bool + }{ + "nil security context": { + pod: &api.Pod{}, + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + }, + }, + "empty groups": { + pod: validPod(), + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + }, + }, + "not in range": { + pod: validPod(), + groups: []int64{5}, + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + {Min: 4, Max: 4}, + }, + }, + "in range 1": { + pod: validPod(), + groups: []int64{2}, + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + }, + pass: true, + }, + "in range boundry min": { + pod: validPod(), + groups: []int64{1}, + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + }, + pass: true, + }, + "in range boundry max": { + pod: validPod(), + groups: []int64{3}, + ranges: []extensions.IDRange{ + {Min: 1, Max: 3}, + }, + pass: true, + }, + "singular range": { + pod: validPod(), + groups: []int64{4}, + ranges: []extensions.IDRange{ + {Min: 4, Max: 4}, + }, + pass: true, + }, + } + + for k, v := range tests { + s, err := NewMustRunAs(v.ranges, "") + if err != nil { + t.Errorf("error creating strategy for %s: %v", k, err) + } + errs := s.Validate(v.pod, v.groups) + if v.pass && len(errs) > 0 { + t.Errorf("unexpected errors for %s: %v", k, errs) + } + if !v.pass && len(errs) == 0 { + t.Errorf("expected no errors for %s but got: %v", k, errs) + } + } +} diff --git a/pkg/security/podsecuritypolicy/group/runasany.go b/pkg/security/podsecuritypolicy/group/runasany.go new file mode 100644 index 00000000000..2398a2ab538 --- /dev/null +++ b/pkg/security/podsecuritypolicy/group/runasany.go @@ -0,0 +1,49 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 group + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// mustRunAs implements the GroupStrategy interface +type runAsAny struct { +} + +var _ GroupStrategy = &runAsAny{} + +// NewRunAsAny provides a new RunAsAny strategy. +func NewRunAsAny() (GroupStrategy, error) { + return &runAsAny{}, nil +} + +// 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 +} + +// 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) { + 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 { + return field.ErrorList{} + +} diff --git a/pkg/security/podsecuritypolicy/group/runasany_test.go b/pkg/security/podsecuritypolicy/group/runasany_test.go new file mode 100644 index 00000000000..be8b239b578 --- /dev/null +++ b/pkg/security/podsecuritypolicy/group/runasany_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 group + +import ( + "testing" +) + +func TestRunAsAnyGenerate(t *testing.T) { + s, err := NewRunAsAny() + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + groups, err := s.Generate(nil) + if len(groups) > 0 { + t.Errorf("expected empty but got %v", groups) + } + if err != nil { + t.Errorf("unexpected error generating groups: %v", err) + } +} + +func TestRunAsAnyGenerateSingle(t *testing.T) { + s, err := NewRunAsAny() + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + group, err := s.GenerateSingle(nil) + if group != nil { + t.Errorf("expected empty but got %v", group) + } + if err != nil { + t.Errorf("unexpected error generating groups: %v", err) + } +} + +func TestRunAsAnyValidte(t *testing.T) { + s, err := NewRunAsAny() + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + errs := s.Validate(nil, nil) + if len(errs) != 0 { + t.Errorf("unexpected errors: %v", errs) + } +} diff --git a/pkg/security/podsecuritypolicy/group/types.go b/pkg/security/podsecuritypolicy/group/types.go new file mode 100644 index 00000000000..be19fe9b9dc --- /dev/null +++ b/pkg/security/podsecuritypolicy/group/types.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 group + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// GroupStrategy defines the interface for all group constraint strategies. +type GroupStrategy interface { + // Generate creates the group based on policy rules. The underlying implementation can + // decide whether it will return a full range of values or a subset of values from the + // configured ranges. + Generate(pod *api.Pod) ([]int64, error) + // Generate a single value to be applied. The underlying implementation decides which + // value to return if configured with multiple ranges. This is used for FSGroup. + GenerateSingle(pod *api.Pod) (*int64, error) + // Validate ensures that the specified values fall within the range of the strategy. + Validate(pod *api.Pod, groups []int64) field.ErrorList +} diff --git a/pkg/security/podsecuritypolicy/provider.go b/pkg/security/podsecuritypolicy/provider.go new file mode 100644 index 00000000000..bf606a3384b --- /dev/null +++ b/pkg/security/podsecuritypolicy/provider.go @@ -0,0 +1,297 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// used to pass in the field being validated for reusable group strategies so they +// can create informative error messages. +const ( + fsGroupField = "fsGroup" + supplementalGroupsField = "supplementalGroups" +) + +// simpleProvider is the default implementation of Provider. +type simpleProvider struct { + psp *extensions.PodSecurityPolicy + strategies *ProviderStrategies +} + +// ensure we implement the interface correctly. +var _ Provider = &simpleProvider{} + +// NewSimpleProvider creates a new Provider instance. +func NewSimpleProvider(psp *extensions.PodSecurityPolicy, namespace string, strategyFactory StrategyFactory) (Provider, error) { + if psp == nil { + return nil, fmt.Errorf("NewSimpleProvider requires a PodSecurityPolicy") + } + if strategyFactory == nil { + return nil, fmt.Errorf("NewSimpleProvider requires a StrategyFactory") + } + + strategies, err := strategyFactory.CreateStrategies(psp, namespace) + if err != nil { + return nil, err + } + + return &simpleProvider{ + psp: psp, + strategies: strategies, + }, nil +} + +// 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, error) { + var sc *api.PodSecurityContext = nil + if pod.Spec.SecurityContext != nil { + // work with a copy + copy := *pod.Spec.SecurityContext + sc = © + } else { + sc = &api.PodSecurityContext{} + } + + if len(sc.SupplementalGroups) == 0 { + supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod) + if err != nil { + return nil, err + } + sc.SupplementalGroups = supGroups + } + + if sc.FSGroup == nil { + fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod) + if err != nil { + return nil, err + } + sc.FSGroup = fsGroup + } + + if sc.SELinuxOptions == nil { + seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil) + if err != nil { + return nil, err + } + sc.SELinuxOptions = seLinux + } + + return sc, 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, 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{} + } + if sc.RunAsUser == nil { + uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container) + if err != nil { + return nil, err + } + sc.RunAsUser = uid + } + + if sc.SELinuxOptions == nil { + seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container) + if err != nil { + return nil, err + } + sc.SELinuxOptions = seLinux + } + + 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 s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot { + nonRoot := true + sc.RunAsNonRoot = &nonRoot + } + + caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container) + if err != nil { + return nil, err + } + sc.Capabilities = 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 { + readOnlyRootFS := true + sc.ReadOnlyRootFilesystem = &readOnlyRootFS + } + + return sc, 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 + } + + fsGroups := []int64{} + if pod.Spec.SecurityContext.FSGroup != nil { + fsGroups = append(fsGroups, *pod.Spec.SecurityContext.FSGroup) + } + allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...) + allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, pod.Spec.SecurityContext.SupplementalGroups)...) + + // 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)...) + + 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.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.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")) + } + + return allErrs +} + +// Ensure a container's SecurityContext is in compliance with the given constraints +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 + } + + sc := container.SecurityContext + allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(pod, container)...) + allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(pod, container)...) + + if !s.psp.Spec.Privileged && *sc.Privileged { + allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *sc.Privileged, "Privileged containers are not allowed")) + } + + allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(pod, container)...) + + if len(pod.Spec.Volumes) > 0 && !psputil.PSPAllowsAllVolumes(s.psp) { + allowedVolumes := psputil.FSTypeToStringSet(s.psp.Spec.Volumes) + for i, v := range pod.Spec.Volumes { + fsType, err := psputil.GetVolumeFSType(v) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("volumes").Index(i), string(fsType), err.Error())) + continue + } + + if !allowedVolumes.Has(string(fsType)) { + allErrs = append(allErrs, field.Invalid( + fldPath.Child("volumes").Index(i), string(fsType), + fmt.Sprintf("%s volumes are not allowed to be used", string(fsType)))) + } + } + } + + 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")) + } + + containersPath := fldPath.Child("containers") + for idx, c := range pod.Spec.Containers { + idxPath := containersPath.Index(idx) + 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.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.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")) + } + } + + return allErrs +} + +// hasHostPort checks the port definitions on the container for HostPort > 0. +func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, cp := range container.Ports { + if cp.HostPort > 0 && !s.isValidHostPort(int(cp.HostPort)) { + detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: %v", cp.HostPort, s.psp.Spec.HostPorts) + allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPort"), cp.HostPort, detail)) + } + } + return allErrs +} + +// isValidHostPort returns true if the port falls in any range allowed by the PSP. +func (s *simpleProvider) isValidHostPort(port int) bool { + for _, hostPortRange := range s.psp.Spec.HostPorts { + if port >= hostPortRange.Min && port <= hostPortRange.Max { + return true + } + } + return false +} + +// Get the name of the PSP that this provider was initialized with. +func (s *simpleProvider) GetPSPName() string { + return s.psp.Name +} diff --git a/pkg/security/podsecuritypolicy/provider_test.go b/pkg/security/podsecuritypolicy/provider_test.go new file mode 100644 index 00000000000..70c89b7e54d --- /dev/null +++ b/pkg/security/podsecuritypolicy/provider_test.go @@ -0,0 +1,822 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +func TestCreatePodSecurityContextNonmutating(t *testing.T) { + // Create a pod with a security context that needs filling in + createPod := func() *api.Pod { + return &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{}, + }, + } + } + + // Create a PSP with strategies that will populate a blank psc + createPSP := func() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{ + Name: "psp-sa", + }, + Spec: extensions.PodSecurityPolicySpec{ + DefaultAddCapabilities: []api.Capability{"foo"}, + RequiredDropCapabilities: []api.Capability{"bar"}, + 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.IDRange{ + {Min: 1, Max: 1}, + }, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + }, + }, + } + } + + pod := createPod() + psp := createPSP() + + provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + sc, 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 + if !reflect.DeepEqual(createPod(), pod) { + diffs := diff.ObjectDiff(createPod(), pod) + t.Errorf("pod was mutated by CreatePodSecurityContext. diff:\n%s", diffs) + } + if !reflect.DeepEqual(createPSP(), psp) { + t.Error("psp was mutated by CreatePodSecurityContext") + } +} + +func TestCreateContainerSecurityContextNonmutating(t *testing.T) { + // Create a pod with a security context that needs filling in + createPod := func() *api.Pod { + return &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{{ + SecurityContext: &api.SecurityContext{}, + }}, + }, + } + } + + // Create a PSP with strategies that will populate a blank security context + createPSP := func() *extensions.PodSecurityPolicy { + var uid int64 = 1 + return &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{ + Name: "psp-sa", + }, + Spec: extensions.PodSecurityPolicySpec{ + DefaultAddCapabilities: []api.Capability{"foo"}, + RequiredDropCapabilities: []api.Capability{"bar"}, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyMustRunAs, + Ranges: []extensions.IDRange{{Min: uid, Max: uid}}, + }, + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyMustRunAs, + SELinuxOptions: &api.SELinuxOptions{User: "you"}, + }, + // 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, + }, + } + } + + pod := createPod() + psp := createPSP() + + provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + sc, 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 + if !reflect.DeepEqual(createPod(), pod) { + diffs := diff.ObjectDiff(createPod(), pod) + t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs) + } + if !reflect.DeepEqual(createPSP(), psp) { + t.Error("psp was mutated by CreateContainerSecurityContext") + } +} + +func TestValidatePodSecurityContextFailures(t *testing.T) { + failHostNetworkPod := defaultPod() + failHostNetworkPod.Spec.SecurityContext.HostNetwork = true + + failHostPIDPod := defaultPod() + failHostPIDPod.Spec.SecurityContext.HostPID = true + + failHostIPCPod := defaultPod() + failHostIPCPod.Spec.SecurityContext.HostIPC = true + + failSupplementalGroupPod := defaultPod() + failSupplementalGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{999} + failSupplementalGroupPSP := defaultPSP() + failSupplementalGroupPSP.Spec.SupplementalGroups = extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + } + + failFSGroupPod := defaultPod() + fsGroup := int64(999) + failFSGroupPod.Spec.SecurityContext.FSGroup = &fsGroup + failFSGroupPSP := defaultPSP() + failFSGroupPSP.Spec.FSGroup = extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + } + + failNilSELinuxPod := defaultPod() + failSELinuxPSP := defaultPSP() + failSELinuxPSP.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs + failSELinuxPSP.Spec.SELinux.SELinuxOptions = &api.SELinuxOptions{ + Level: "foo", + } + + failInvalidSELinuxPod := defaultPod() + failInvalidSELinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{ + Level: "bar", + } + + errorCases := map[string]struct { + pod *api.Pod + psp *extensions.PodSecurityPolicy + expectedError string + }{ + "failHostNetwork": { + pod: failHostNetworkPod, + psp: defaultPSP(), + expectedError: "Host network is not allowed to be used", + }, + "failHostPID": { + pod: failHostPIDPod, + psp: defaultPSP(), + expectedError: "Host PID is not allowed to be used", + }, + "failHostIPC": { + pod: failHostIPCPod, + psp: defaultPSP(), + expectedError: "Host IPC is not allowed to be used", + }, + "failSupplementalGroupOutOfRange": { + pod: failSupplementalGroupPod, + psp: failSupplementalGroupPSP, + expectedError: "999 is not an allowed group", + }, + "failSupplementalGroupEmpty": { + pod: defaultPod(), + psp: failSupplementalGroupPSP, + expectedError: "unable to validate empty groups against required ranges", + }, + "failFSGroupOutOfRange": { + pod: failFSGroupPod, + psp: failFSGroupPSP, + expectedError: "999 is not an allowed group", + }, + "failFSGroupEmpty": { + pod: defaultPod(), + psp: failFSGroupPSP, + expectedError: "unable to validate empty groups against required ranges", + }, + "failNilSELinux": { + pod: failNilSELinuxPod, + psp: failSELinuxPSP, + expectedError: "unable to validate nil seLinuxOptions", + }, + "failInvalidSELinux": { + pod: failInvalidSELinuxPod, + psp: failSELinuxPSP, + expectedError: "does not match required level. Found bar, wanted foo", + }, + } + for k, v := range errorCases { + provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + errs := provider.ValidatePodSecurityContext(v.pod, field.NewPath("")) + if len(errs) == 0 { + t.Errorf("%s expected validation failure but did not receive errors", k) + continue + } + if !strings.Contains(errs[0].Error(), v.expectedError) { + t.Errorf("%s received unexpected error %v", k, errs) + } + } +} + +func TestValidateContainerSecurityContextFailures(t *testing.T) { + // fail user strat + failUserPSP := defaultPSP() + var uid int64 = 999 + var badUID int64 = 1 + failUserPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyMustRunAs, + Ranges: []extensions.IDRange{{Min: uid, Max: uid}}, + } + failUserPod := defaultPod() + failUserPod.Spec.Containers[0].SecurityContext.RunAsUser = &badUID + + // fail selinux strat + failSELinuxPSP := defaultPSP() + failSELinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyMustRunAs, + SELinuxOptions: &api.SELinuxOptions{ + Level: "foo", + }, + } + failSELinuxPod := defaultPod() + failSELinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ + Level: "bar", + } + + failPrivPod := defaultPod() + var priv bool = true + failPrivPod.Spec.Containers[0].SecurityContext.Privileged = &priv + + failCapsPod := defaultPod() + failCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ + Add: []api.Capability{"foo"}, + } + + failHostDirPod := defaultPod() + failHostDirPod.Spec.Volumes = []api.Volume{ + { + Name: "bad volume", + VolumeSource: api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{}, + }, + }, + } + + failHostPortPod := defaultPod() + failHostPortPod.Spec.Containers[0].Ports = []api.ContainerPort{{HostPort: 1}} + + readOnlyRootFSPSP := defaultPSP() + readOnlyRootFSPSP.Spec.ReadOnlyRootFilesystem = true + + readOnlyRootFSPodFalse := defaultPod() + readOnlyRootFS := false + readOnlyRootFSPodFalse.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFS + + errorCases := map[string]struct { + pod *api.Pod + psp *extensions.PodSecurityPolicy + expectedError string + }{ + "failUserPSP": { + pod: failUserPod, + psp: failUserPSP, + expectedError: "does not match required range", + }, + "failSELinuxPSP": { + pod: failSELinuxPod, + psp: failSELinuxPSP, + expectedError: "does not match required level", + }, + "failPrivPSP": { + pod: failPrivPod, + psp: defaultPSP(), + expectedError: "Privileged containers are not allowed", + }, + "failCapsPSP": { + pod: failCapsPod, + psp: defaultPSP(), + expectedError: "capability may not be added", + }, + "failHostDirPSP": { + pod: failHostDirPod, + psp: defaultPSP(), + expectedError: "hostPath volumes are not allowed to be used", + }, + "failHostPortPSP": { + pod: failHostPortPod, + psp: defaultPSP(), + expectedError: "Host port 1 is not allowed to be used. Allowed ports: []", + }, + "failReadOnlyRootFS - nil": { + pod: defaultPod(), + psp: readOnlyRootFSPSP, + expectedError: "ReadOnlyRootFilesystem may not be nil and must be set to true", + }, + "failReadOnlyRootFS - false": { + pod: readOnlyRootFSPodFalse, + psp: readOnlyRootFSPSP, + expectedError: "ReadOnlyRootFilesystem must be set to true", + }, + } + + for k, v := range errorCases { + provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath("")) + if len(errs) == 0 { + t.Errorf("%s expected validation failure but did not receive errors", k) + continue + } + if !strings.Contains(errs[0].Error(), v.expectedError) { + t.Errorf("%s received unexpected error %v", k, errs) + } + } +} + +func TestValidatePodSecurityContextSuccess(t *testing.T) { + hostNetworkPSP := defaultPSP() + hostNetworkPSP.Spec.HostNetwork = true + hostNetworkPod := defaultPod() + hostNetworkPod.Spec.SecurityContext.HostNetwork = true + + hostPIDPSP := defaultPSP() + hostPIDPSP.Spec.HostPID = true + hostPIDPod := defaultPod() + hostPIDPod.Spec.SecurityContext.HostPID = true + + hostIPCPSP := defaultPSP() + hostIPCPSP.Spec.HostIPC = true + hostIPCPod := defaultPod() + hostIPCPod.Spec.SecurityContext.HostIPC = true + + supGroupPSP := defaultPSP() + supGroupPSP.Spec.SupplementalGroups = extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 1, Max: 5}, + }, + } + supGroupPod := defaultPod() + supGroupPod.Spec.SecurityContext.SupplementalGroups = []int64{3} + + fsGroupPSP := defaultPSP() + fsGroupPSP.Spec.FSGroup = extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 1, Max: 5}, + }, + } + fsGroupPod := defaultPod() + fsGroup := int64(3) + fsGroupPod.Spec.SecurityContext.FSGroup = &fsGroup + + seLinuxPod := defaultPod() + seLinuxPod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{ + User: "user", + Role: "role", + Type: "type", + Level: "level", + } + seLinuxPSP := defaultPSP() + seLinuxPSP.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs + seLinuxPSP.Spec.SELinux.SELinuxOptions = &api.SELinuxOptions{ + User: "user", + Role: "role", + Type: "type", + Level: "level", + } + + errorCases := map[string]struct { + pod *api.Pod + psp *extensions.PodSecurityPolicy + }{ + "pass hostNetwork validating PSP": { + pod: hostNetworkPod, + psp: hostNetworkPSP, + }, + "pass hostPID validating PSP": { + pod: hostPIDPod, + psp: hostPIDPSP, + }, + "pass hostIPC validating PSP": { + pod: hostIPCPod, + psp: hostIPCPSP, + }, + "pass supplemental group validating PSP": { + pod: supGroupPod, + psp: supGroupPSP, + }, + "pass fs group validating PSP": { + pod: fsGroupPod, + psp: fsGroupPSP, + }, + "pass selinux validating PSP": { + pod: seLinuxPod, + psp: seLinuxPSP, + }, + } + + for k, v := range errorCases { + provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + errs := provider.ValidatePodSecurityContext(v.pod, field.NewPath("")) + if len(errs) != 0 { + t.Errorf("%s expected validation pass but received errors %v", k, errs) + continue + } + } +} + +func TestValidateContainerSecurityContextSuccess(t *testing.T) { + var notPriv bool = false + defaultPod := func() *api.Pod { + return &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{}, + Containers: []api.Container{ + { + SecurityContext: &api.SecurityContext{ + // expected to be set by defaulting mechanisms + Privileged: ¬Priv, + // fill in the rest for test cases + }, + }, + }, + }, + } + } + + // fail user strat + userPSP := defaultPSP() + var uid int64 = 999 + userPSP.Spec.RunAsUser = extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyMustRunAs, + Ranges: []extensions.IDRange{{Min: uid, Max: uid}}, + } + userPod := defaultPod() + userPod.Spec.Containers[0].SecurityContext.RunAsUser = &uid + + // fail selinux strat + seLinuxPSP := defaultPSP() + seLinuxPSP.Spec.SELinux = extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyMustRunAs, + SELinuxOptions: &api.SELinuxOptions{ + Level: "foo", + }, + } + seLinuxPod := defaultPod() + seLinuxPod.Spec.Containers[0].SecurityContext.SELinuxOptions = &api.SELinuxOptions{ + Level: "foo", + } + + privPSP := defaultPSP() + privPSP.Spec.Privileged = true + privPod := defaultPod() + var priv bool = true + privPod.Spec.Containers[0].SecurityContext.Privileged = &priv + + capsPSP := defaultPSP() + capsPSP.Spec.AllowedCapabilities = []api.Capability{"foo"} + capsPod := defaultPod() + capsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ + Add: []api.Capability{"foo"}, + } + + // pod should be able to request caps that are in the required set even if not specified in the allowed set + requiredCapsPSP := defaultPSP() + requiredCapsPSP.Spec.DefaultAddCapabilities = []api.Capability{"foo"} + requiredCapsPod := defaultPod() + requiredCapsPod.Spec.Containers[0].SecurityContext.Capabilities = &api.Capabilities{ + Add: []api.Capability{"foo"}, + } + + hostDirPSP := defaultPSP() + hostDirPSP.Spec.Volumes = []extensions.FSType{extensions.HostPath} + hostDirPod := defaultPod() + hostDirPod.Spec.Volumes = []api.Volume{ + { + Name: "bad volume", + VolumeSource: api.VolumeSource{ + HostPath: &api.HostPathVolumeSource{}, + }, + }, + } + + hostPortPSP := defaultPSP() + hostPortPSP.Spec.HostPorts = []extensions.HostPortRange{{Min: 1, Max: 1}} + hostPortPod := defaultPod() + hostPortPod.Spec.Containers[0].Ports = []api.ContainerPort{{HostPort: 1}} + + readOnlyRootFSPodFalse := defaultPod() + readOnlyRootFSFalse := false + readOnlyRootFSPodFalse.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFSFalse + + readOnlyRootFSPodTrue := defaultPod() + readOnlyRootFSTrue := true + readOnlyRootFSPodTrue.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &readOnlyRootFSTrue + + errorCases := map[string]struct { + pod *api.Pod + psp *extensions.PodSecurityPolicy + }{ + "pass user must run as PSP": { + pod: userPod, + psp: userPSP, + }, + "pass seLinux must run as PSP": { + pod: seLinuxPod, + psp: seLinuxPSP, + }, + "pass priv validating PSP": { + pod: privPod, + psp: privPSP, + }, + "pass allowed caps validating PSP": { + pod: capsPod, + psp: capsPSP, + }, + "pass required caps validating PSP": { + pod: requiredCapsPod, + psp: requiredCapsPSP, + }, + "pass hostDir validating PSP": { + pod: hostDirPod, + psp: hostDirPSP, + }, + "pass hostPort validating PSP": { + pod: hostPortPod, + psp: hostPortPSP, + }, + "pass read only root fs - nil": { + pod: defaultPod(), + psp: defaultPSP(), + }, + "pass read only root fs - false": { + pod: readOnlyRootFSPodFalse, + psp: defaultPSP(), + }, + "pass read only root fs - true": { + pod: readOnlyRootFSPodTrue, + psp: defaultPSP(), + }, + } + + for k, v := range errorCases { + provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("unable to create provider %v", err) + } + errs := provider.ValidateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0], field.NewPath("")) + if len(errs) != 0 { + t.Errorf("%s expected validation pass but received errors %v", k, errs) + continue + } + } +} + +func TestGenerateContainerSecurityContextReadOnlyRootFS(t *testing.T) { + truePSP := defaultPSP() + truePSP.Spec.ReadOnlyRootFilesystem = true + + trueVal := true + expectTrue := &trueVal + falseVal := false + expectFalse := &falseVal + + falsePod := defaultPod() + falsePod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = expectFalse + + truePod := defaultPod() + truePod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = expectTrue + + tests := map[string]struct { + pod *api.Pod + psp *extensions.PodSecurityPolicy + expected *bool + }{ + "false psp, nil sc": { + psp: defaultPSP(), + pod: defaultPod(), + expected: nil, + }, + "false psp, false sc": { + psp: defaultPSP(), + pod: falsePod, + expected: expectFalse, + }, + "false psp, true sc": { + psp: defaultPSP(), + pod: truePod, + expected: expectTrue, + }, + "true psp, nil sc": { + psp: truePSP, + pod: defaultPod(), + expected: expectTrue, + }, + "true psp, false sc": { + psp: truePSP, + pod: falsePod, + // expect false even though it defaults to true to ensure it doesn't change set values + // validation catches the mismatch, not generation + expected: expectFalse, + }, + "true psp, true sc": { + psp: truePSP, + pod: truePod, + expected: expectTrue, + }, + } + + for k, v := range tests { + provider, err := NewSimpleProvider(v.psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Errorf("%s unable to create provider %v", k, err) + continue + } + sc, err := provider.CreateContainerSecurityContext(v.pod, &v.pod.Spec.Containers[0]) + if err != nil { + t.Errorf("%s unable to create container security context %v", k, err) + continue + } + + if v.expected == nil && sc.ReadOnlyRootFilesystem != nil { + t.Errorf("%s expected a nil ReadOnlyRootFilesystem but got %t", k, *sc.ReadOnlyRootFilesystem) + } + if v.expected != nil && sc.ReadOnlyRootFilesystem == nil { + t.Errorf("%s expected a non nil ReadOnlyRootFilesystem but recieved nil", k) + } + if v.expected != nil && sc.ReadOnlyRootFilesystem != nil && (*v.expected != *sc.ReadOnlyRootFilesystem) { + t.Errorf("%s expected a non nil ReadOnlyRootFilesystem set to %t but got %t", k, *v.expected, *sc.ReadOnlyRootFilesystem) + } + + } +} + +func defaultPSP() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{ + Name: "psp-sa", + }, + Spec: extensions.PodSecurityPolicySpec{ + 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 defaultPod() *api.Pod { + var notPriv bool = false + return &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + // fill in for test cases + }, + Containers: []api.Container{ + { + SecurityContext: &api.SecurityContext{ + // expected to be set by defaulting mechanisms + Privileged: ¬Priv, + // fill in the rest for test cases + }, + }, + }, + }, + } +} + +// TestValidateAllowedVolumes will test that for every field of VolumeSource we can create +// a pod with that type of volume and deny it, accept it explicitly, or accept it with +// the FSTypeAll wildcard. +func TestValidateAllowedVolumes(t *testing.T) { + val := reflect.ValueOf(api.VolumeSource{}) + + for i := 0; i < val.NumField(); i++ { + // reflectively create the volume source + fieldVal := val.Type().Field(i) + + volumeSource := api.VolumeSource{} + volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) + + reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) + volume := api.Volume{VolumeSource: volumeSource} + + // sanity check before moving on + fsType, err := psputil.GetVolumeFSType(volume) + if err != nil { + t.Errorf("error getting FSType for %s: %s", fieldVal.Name, err.Error()) + continue + } + + // add the volume to the pod + pod := defaultPod() + pod.Spec.Volumes = []api.Volume{volume} + + // create a PSP that allows no volumes + psp := defaultPSP() + + provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) + if err != nil { + t.Errorf("error creating provider for %s: %s", fieldVal.Name, err.Error()) + continue + } + + // expect a denial for this PSP and test the error message to ensure it's related to the volumesource + errs := provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[0], field.NewPath("")) + if len(errs) != 1 { + t.Errorf("expected exactly 1 error for %s but got %v", fieldVal.Name, errs) + } else { + if !strings.Contains(errs.ToAggregate().Error(), fmt.Sprintf("%s volumes are not allowed to be used", fsType)) { + t.Errorf("did not find the expected error, received: %v", errs) + } + } + + // now add the fstype directly to the psp and it should validate + psp.Spec.Volumes = []extensions.FSType{fsType} + errs = provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[0], field.NewPath("")) + if len(errs) != 0 { + t.Errorf("directly allowing volume expected no errors for %s but got %v", fieldVal.Name, errs) + } + + // now change the psp to allow any volumes and the pod should still validate + psp.Spec.Volumes = []extensions.FSType{extensions.All} + errs = provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[0], field.NewPath("")) + if len(errs) != 0 { + t.Errorf("wildcard volume expected no errors for %s but got %v", fieldVal.Name, errs) + } + } +} diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas.go b/pkg/security/podsecuritypolicy/selinux/mustrunas.go new file mode 100644 index 00000000000..4b59211bb4d --- /dev/null +++ b/pkg/security/podsecuritypolicy/selinux/mustrunas.go @@ -0,0 +1,84 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 selinux + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +type mustRunAs struct { + opts *extensions.SELinuxStrategyOptions +} + +var _ SELinuxStrategy = &mustRunAs{} + +func NewMustRunAs(options *extensions.SELinuxStrategyOptions) (SELinuxStrategy, error) { + if options == nil { + return nil, fmt.Errorf("MustRunAs requires SELinuxContextStrategyOptions") + } + if options.SELinuxOptions == nil { + return nil, fmt.Errorf("MustRunAs requires SELinuxOptions") + } + return &mustRunAs{ + opts: options, + }, nil +} + +// Generate creates the SELinuxOptions based on constraint rules. +func (s *mustRunAs) Generate(pod *api.Pod, container *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 { + 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)) + 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)) + } + 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)) + } + 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)) + } + 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)) + } + + return allErrs +} diff --git a/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go b/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go new file mode 100644 index 00000000000..153c3e5072c --- /dev/null +++ b/pkg/security/podsecuritypolicy/selinux/mustrunas_test.go @@ -0,0 +1,159 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 selinux + +import ( + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestMustRunAsOptions(t *testing.T) { + tests := map[string]struct { + opts *extensions.SELinuxStrategyOptions + pass bool + }{ + "invalid opts": { + opts: &extensions.SELinuxStrategyOptions{}, + pass: false, + }, + "valid opts": { + opts: &extensions.SELinuxStrategyOptions{SELinuxOptions: &api.SELinuxOptions{}}, + pass: true, + }, + } + for name, tc := range tests { + _, err := NewMustRunAs(tc.opts) + if err != nil && tc.pass { + t.Errorf("%s expected to pass but received error %#v", name, err) + } + if err == nil && !tc.pass { + t.Errorf("%s expected to fail but did not receive an error", name) + } + } +} + +func TestMustRunAsGenerate(t *testing.T) { + opts := &extensions.SELinuxStrategyOptions{ + SELinuxOptions: &api.SELinuxOptions{ + User: "user", + Role: "role", + Type: "type", + Level: "level", + }, + } + mustRunAs, err := NewMustRunAs(opts) + if err != nil { + t.Fatalf("unexpected error initializing NewMustRunAs %v", err) + } + generated, err := mustRunAs.Generate(nil, nil) + if err != nil { + t.Fatalf("unexpected error generating selinux %v", err) + } + if !reflect.DeepEqual(generated, opts.SELinuxOptions) { + t.Errorf("generated selinux does not equal configured selinux") + } +} + +func TestMustRunAsValidate(t *testing.T) { + newValidOpts := func() *api.SELinuxOptions { + return &api.SELinuxOptions{ + User: "user", + Role: "role", + Level: "level", + Type: "type", + } + } + + role := newValidOpts() + role.Role = "invalid" + + user := newValidOpts() + user.User = "invalid" + + level := newValidOpts() + level.Level = "invalid" + + seType := newValidOpts() + seType.Type = "invalid" + + tests := map[string]struct { + seLinux *api.SELinuxOptions + expectedMsg string + }{ + "invalid role": { + seLinux: role, + expectedMsg: "does not match required role", + }, + "invalid user": { + seLinux: user, + expectedMsg: "does not match required user", + }, + "invalid level": { + seLinux: level, + expectedMsg: "does not match required level", + }, + "invalid type": { + seLinux: seType, + expectedMsg: "does not match required type", + }, + "valid": { + seLinux: newValidOpts(), + expectedMsg: "", + }, + } + + opts := &extensions.SELinuxStrategyOptions{ + SELinuxOptions: newValidOpts(), + } + + for name, tc := range tests { + mustRunAs, err := NewMustRunAs(opts) + if err != nil { + 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) + //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) + } + //should've failed but didn't + if len(tc.expectedMsg) != 0 && len(errs) == 0 { + t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) + } + //failed with additional messages + if len(tc.expectedMsg) != 0 && len(errs) > 1 { + t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) + } + //check that we got the right message + if len(tc.expectedMsg) != 0 && len(errs) == 1 { + if !strings.Contains(errs[0].Error(), tc.expectedMsg) { + t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) + } + } + } +} diff --git a/pkg/security/podsecuritypolicy/selinux/runasany.go b/pkg/security/podsecuritypolicy/selinux/runasany.go new file mode 100644 index 00000000000..1418fc331c2 --- /dev/null +++ b/pkg/security/podsecuritypolicy/selinux/runasany.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 selinux + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// runAsAny implements the SELinuxStrategy interface. +type runAsAny struct{} + +var _ SELinuxStrategy = &runAsAny{} + +// NewRunAsAny provides a strategy that will return the configured se linux context or nil. +func NewRunAsAny(options *extensions.SELinuxStrategyOptions) (SELinuxStrategy, error) { + return &runAsAny{}, nil +} + +// Generate creates the SELinuxOptions based on constraint rules. +func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) { + return nil, nil +} + +// 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 { + return field.ErrorList{} +} diff --git a/pkg/security/podsecuritypolicy/selinux/runasany_test.go b/pkg/security/podsecuritypolicy/selinux/runasany_test.go new file mode 100644 index 00000000000..4f5db4e68fd --- /dev/null +++ b/pkg/security/podsecuritypolicy/selinux/runasany_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 selinux + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestRunAsAnyOptions(t *testing.T) { + _, err := NewRunAsAny(nil) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + _, err = NewRunAsAny(&extensions.SELinuxStrategyOptions{}) + if err != nil { + t.Errorf("unexpected error initializing NewRunAsAny %v", err) + } +} + +func TestRunAsAnyGenerate(t *testing.T) { + s, err := NewRunAsAny(&extensions.SELinuxStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + uid, err := s.Generate(nil, nil) + if uid != nil { + t.Errorf("expected nil uid but got %v", *uid) + } + if err != nil { + t.Errorf("unexpected error generating uid %v", err) + } +} + +func TestRunAsAnyValidate(t *testing.T) { + s, err := NewRunAsAny(&extensions.SELinuxStrategyOptions{ + SELinuxOptions: &api.SELinuxOptions{ + Level: "foo", + }, + }, + ) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + errs := s.Validate(nil, nil) + if len(errs) != 0 { + t.Errorf("unexpected errors validating with ") + } + s, err = NewRunAsAny(&extensions.SELinuxStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + errs = s.Validate(nil, nil) + if len(errs) != 0 { + t.Errorf("unexpected errors validating %v", errs) + } +} diff --git a/pkg/security/podsecuritypolicy/selinux/types.go b/pkg/security/podsecuritypolicy/selinux/types.go new file mode 100644 index 00000000000..25613d62a13 --- /dev/null +++ b/pkg/security/podsecuritypolicy/selinux/types.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 selinux + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// SELinuxStrategy defines the interface for all SELinux constraint strategies. +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 +} diff --git a/pkg/security/podsecuritypolicy/types.go b/pkg/security/podsecuritypolicy/types.go new file mode 100644 index 00000000000..64535ee4af7 --- /dev/null +++ b/pkg/security/podsecuritypolicy/types.go @@ -0,0 +1,62 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "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/user" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// Provider provides the implementation to generate a new security +// context based on constraints or validate an existing security context against constraints. +type Provider interface { + // Create a PodSecurityContext based on the given constraints. + CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurityContext, error) + // Create a container SecurityContext based on the given constraints + CreateContainerSecurityContext(pod *api.Pod, container *api.Container) (*api.SecurityContext, error) + // Ensure a pod's SecurityContext is in compliance with the given constraints. + ValidatePodSecurityContext(pod *api.Pod, fldPath *field.Path) field.ErrorList + // Ensure a container's SecurityContext is in compliance with the given constraints + ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList + // Get the name of the PSP that this provider was initialized with. + GetPSPName() string +} + +// StrategyFactory abstracts how the strategies are created from the provider so that you may +// implement your own custom strategies that may pull information from other resources as necessary. +// For example, if you would like to populate the strategies with values from namespace annotations +// you may create a factory with a client that can pull the namespace and populate the appropriate +// values. +type StrategyFactory interface { + // CreateStrategies creates the strategies that a provider will use. The namespace argument + // should be the namespace of the object being checked (the pod's namespace). + CreateStrategies(psp *extensions.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) +} + +// ProviderStrategies is a holder for all strategies that the provider requires to be populated. +type ProviderStrategies struct { + RunAsUserStrategy user.RunAsUserStrategy + SELinuxStrategy selinux.SELinuxStrategy + FSGroupStrategy group.GroupStrategy + SupplementalGroupStrategy group.GroupStrategy + CapabilitiesStrategy capabilities.CapabilitiesStrategy +} diff --git a/pkg/security/podsecuritypolicy/user/mustrunas.go b/pkg/security/podsecuritypolicy/user/mustrunas.go new file mode 100644 index 00000000000..f48c803c99e --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/mustrunas.go @@ -0,0 +1,84 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// mustRunAs implements the RunAsUserStrategy interface +type mustRunAs struct { + opts *extensions.RunAsUserStrategyOptions +} + +// NewMustRunAs provides a strategy that requires the container to run as a specific UID in a range. +func NewMustRunAs(options *extensions.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { + if options == nil { + return nil, fmt.Errorf("MustRunAsRange requires run as user options") + } + if len(options.Ranges) == 0 { + return nil, fmt.Errorf("MustRunAsRange requires at least one range") + } + return &mustRunAs{ + opts: options, + }, nil +} + +// Generate creates the uid based on policy rules. MustRunAs returns the first range's Min. +func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*int64, error) { + return &s.opts.Ranges[0].Min, 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 { + 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)) + 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)) + } + return allErrs +} + +func (s *mustRunAs) isValidUID(id int64) bool { + for _, rng := range s.opts.Ranges { + if psputil.FallsInRange(id, rng) { + return true + } + } + return false +} diff --git a/pkg/security/podsecuritypolicy/user/mustrunas_test.go b/pkg/security/podsecuritypolicy/user/mustrunas_test.go new file mode 100644 index 00000000000..1a8c7eb0262 --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/mustrunas_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestNewMustRunAs(t *testing.T) { + tests := map[string]struct { + opts *extensions.RunAsUserStrategyOptions + pass bool + }{ + "nil opts": { + opts: nil, + pass: false, + }, + "invalid opts": { + opts: &extensions.RunAsUserStrategyOptions{}, + pass: false, + }, + "valid opts": { + opts: &extensions.RunAsUserStrategyOptions{ + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + }, + pass: true, + }, + } + for name, tc := range tests { + _, err := NewMustRunAs(tc.opts) + if err != nil && tc.pass { + t.Errorf("%s expected to pass but received error %#v", name, err) + } + if err == nil && !tc.pass { + t.Errorf("%s expected to fail but did not receive an error", name) + } + } +} + +func TestGenerate(t *testing.T) { + opts := &extensions.RunAsUserStrategyOptions{ + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + }, + } + mustRunAs, err := NewMustRunAs(opts) + if err != nil { + t.Fatalf("unexpected error initializing NewMustRunAs %v", err) + } + generated, err := mustRunAs.Generate(nil, nil) + if err != nil { + t.Fatalf("unexpected error generating runAsUser %v", err) + } + if *generated != opts.Ranges[0].Min { + t.Errorf("generated runAsUser does not equal configured runAsUser") + } +} + +func TestValidate(t *testing.T) { + opts := &extensions.RunAsUserStrategyOptions{ + Ranges: []extensions.IDRange{ + {Min: 1, Max: 1}, + {Min: 10, Max: 20}, + }, + } + + tests := map[string]struct { + container *api.Container + expectedMsg string + }{ + "good container": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: int64Ptr(15), + }, + }, + }, + "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", + }, + "invalid id": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: int64Ptr(21), + }, + }, + expectedMsg: "does not match required range", + }, + } + + for name, tc := range tests { + mustRunAs, err := NewMustRunAs(opts) + if err != nil { + t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err) + continue + } + errs := mustRunAs.Validate(nil, tc.container) + //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) + } + //should've failed but didn't + if len(tc.expectedMsg) != 0 && len(errs) == 0 { + t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg) + } + //failed with additional messages + if len(tc.expectedMsg) != 0 && len(errs) > 1 { + t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs) + } + //check that we got the right message + if len(tc.expectedMsg) != 0 && len(errs) == 1 { + if !strings.Contains(errs[0].Error(), tc.expectedMsg) { + t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs) + } + } + } +} + +func int64Ptr(i int64) *int64 { + return &i +} diff --git a/pkg/security/podsecuritypolicy/user/nonroot.go b/pkg/security/podsecuritypolicy/user/nonroot.go new file mode 100644 index 00000000000..fc7c356a0b3 --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/nonroot.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +type nonRoot struct{} + +var _ RunAsUserStrategy = &nonRoot{} + +func NewRunAsNonRoot(options *extensions.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { + return &nonRoot{}, nil +} + +// Generate creates the uid based on policy rules. This strategy does return a UID. It assumes +// that the user will specify a UID or the container image specifies a UID. +func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*int64, error) { + return nil, nil +} + +// Validate ensures that the specified values fall within the range of the strategy. Validation +// of this will pass if either the UID is not set, assuming that the image will provided the UID +// or if the UID is set it is not root. 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 { + 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 && *container.SecurityContext.RunAsUser == 0 { + detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy %s", container.Name) + allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail)) + return allErrs + } + return allErrs +} diff --git a/pkg/security/podsecuritypolicy/user/nonroot_test.go b/pkg/security/podsecuritypolicy/user/nonroot_test.go new file mode 100644 index 00000000000..73e2b1abe7c --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/nonroot_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestNonRootOptions(t *testing.T) { + _, err := NewRunAsNonRoot(nil) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) + } + _, err = NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Errorf("unexpected error initializing NewRunAsNonRoot %v", err) + } +} + +func TestNonRootGenerate(t *testing.T) { + s, err := NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err) + } + uid, err := s.Generate(nil, nil) + if uid != nil { + t.Errorf("expected nil uid but got %d", *uid) + } + if err != nil { + t.Errorf("unexpected error generating uid %v", err) + } +} + +func TestNonRootValidate(t *testing.T) { + var uid int64 = 1 + var badUID int64 = 0 + s, err := NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewMustRunAs %v", err) + } + container := &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &badUID, + }, + } + + errs := s.Validate(nil, container) + if len(errs) == 0 { + t.Errorf("expected errors from root uid but got none") + } + + container.SecurityContext.RunAsUser = &uid + errs = s.Validate(nil, container) + if len(errs) != 0 { + t.Errorf("expected no errors from non-root uid but got %v", errs) + } + + container.SecurityContext.RunAsUser = nil + errs = s.Validate(nil, container) + if len(errs) != 0 { + t.Errorf("expected no errors from nil uid but got %v", errs) + } +} diff --git a/pkg/security/podsecuritypolicy/user/runasany.go b/pkg/security/podsecuritypolicy/user/runasany.go new file mode 100644 index 00000000000..6fbf1e03215 --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/runasany.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// runAsAny implements the interface RunAsUserStrategy. +type runAsAny struct{} + +var _ RunAsUserStrategy = &runAsAny{} + +// NewRunAsAny provides a strategy that will return nil. +func NewRunAsAny(options *extensions.RunAsUserStrategyOptions) (RunAsUserStrategy, error) { + return &runAsAny{}, nil +} + +// Generate creates the uid based on policy rules. +func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*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, container *api.Container) field.ErrorList { + return field.ErrorList{} +} diff --git a/pkg/security/podsecuritypolicy/user/runasany_test.go b/pkg/security/podsecuritypolicy/user/runasany_test.go new file mode 100644 index 00000000000..8da79fffc4e --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/runasany_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "testing" + + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestRunAsAnyOptions(t *testing.T) { + _, err := NewRunAsAny(nil) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + _, err = NewRunAsAny(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Errorf("unexpected error initializing NewRunAsAny %v", err) + } +} + +func TestRunAsAnyGenerate(t *testing.T) { + s, err := NewRunAsAny(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + uid, err := s.Generate(nil, nil) + if uid != nil { + t.Errorf("expected nil uid but got %d", *uid) + } + if err != nil { + t.Errorf("unexpected error generating uid %v", err) + } +} + +func TestRunAsAnyValidate(t *testing.T) { + s, err := NewRunAsAny(&extensions.RunAsUserStrategyOptions{}) + if err != nil { + t.Fatalf("unexpected error initializing NewRunAsAny %v", err) + } + errs := s.Validate(nil, nil) + if len(errs) != 0 { + t.Errorf("unexpected errors validating with ") + } +} diff --git a/pkg/security/podsecuritypolicy/user/types.go b/pkg/security/podsecuritypolicy/user/types.go new file mode 100644 index 00000000000..ee691c7becf --- /dev/null +++ b/pkg/security/podsecuritypolicy/user/types.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 user + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// RunAsUserStrategy defines the interface for all uid constraint strategies. +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 +} diff --git a/pkg/security/podsecuritypolicy/util/util.go b/pkg/security/podsecuritypolicy/util/util.go new file mode 100644 index 00000000000..4c2935b4cba --- /dev/null +++ b/pkg/security/podsecuritypolicy/util/util.go @@ -0,0 +1,142 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/sets" +) + +const ( + ValidatedPSPAnnotation = "kubernetes.io/psp" +) + +func GetAllFSTypesExcept(exceptions ...string) sets.String { + fstypes := GetAllFSTypesAsSet() + for _, e := range exceptions { + fstypes.Delete(e) + } + return fstypes +} + +func GetAllFSTypesAsSet() sets.String { + fstypes := sets.NewString() + fstypes.Insert( + string(extensions.HostPath), + string(extensions.AzureFile), + string(extensions.Flocker), + string(extensions.FlexVolume), + string(extensions.EmptyDir), + string(extensions.GCEPersistentDisk), + string(extensions.AWSElasticBlockStore), + string(extensions.GitRepo), + string(extensions.Secret), + string(extensions.NFS), + string(extensions.ISCSI), + string(extensions.Glusterfs), + string(extensions.PersistentVolumeClaim), + string(extensions.RBD), + string(extensions.Cinder), + string(extensions.CephFS), + string(extensions.DownwardAPI), + string(extensions.FC), + string(extensions.ConfigMap)) + return fstypes +} + +// getVolumeFSType gets the FSType for a volume. +func GetVolumeFSType(v api.Volume) (extensions.FSType, error) { + switch { + case v.HostPath != nil: + return extensions.HostPath, nil + case v.EmptyDir != nil: + return extensions.EmptyDir, nil + case v.GCEPersistentDisk != nil: + return extensions.GCEPersistentDisk, nil + case v.AWSElasticBlockStore != nil: + return extensions.AWSElasticBlockStore, nil + case v.GitRepo != nil: + return extensions.GitRepo, nil + case v.Secret != nil: + return extensions.Secret, nil + case v.NFS != nil: + return extensions.NFS, nil + case v.ISCSI != nil: + return extensions.ISCSI, nil + case v.Glusterfs != nil: + return extensions.Glusterfs, nil + case v.PersistentVolumeClaim != nil: + return extensions.PersistentVolumeClaim, nil + case v.RBD != nil: + return extensions.RBD, nil + case v.FlexVolume != nil: + return extensions.FlexVolume, nil + case v.Cinder != nil: + return extensions.Cinder, nil + case v.CephFS != nil: + return extensions.CephFS, nil + case v.Flocker != nil: + return extensions.Flocker, nil + case v.DownwardAPI != nil: + return extensions.DownwardAPI, nil + case v.FC != nil: + return extensions.FC, nil + case v.AzureFile != nil: + return extensions.AzureFile, nil + case v.ConfigMap != nil: + return extensions.ConfigMap, nil + } + + return "", fmt.Errorf("unknown volume type for volume: %#v", v) +} + +// fsTypeToStringSet converts an FSType slice to a string set. +func FSTypeToStringSet(fsTypes []extensions.FSType) sets.String { + set := sets.NewString() + for _, v := range fsTypes { + set.Insert(string(v)) + } + return set +} + +// PSPAllowsAllVolumes checks for FSTypeAll in the psp's allowed volumes. +func PSPAllowsAllVolumes(psp *extensions.PodSecurityPolicy) bool { + return PSPAllowsFSType(psp, extensions.All) +} + +// PSPAllowsFSType is a utility for checking if a PSP allows a particular FSType. +// If all volumes are allowed then this will return true for any FSType passed. +func PSPAllowsFSType(psp *extensions.PodSecurityPolicy, fsType extensions.FSType) bool { + if psp == nil { + return false + } + + for _, v := range psp.Spec.Volumes { + if v == fsType || v == extensions.All { + return true + } + } + return false +} + +// FallsInRange is a utility to determine it the id falls in the valid range. +func FallsInRange(id int64, rng extensions.IDRange) bool { + return id >= rng.Min && id <= rng.Max +} diff --git a/pkg/security/podsecuritypolicy/util/util_test.go b/pkg/security/podsecuritypolicy/util/util_test.go new file mode 100644 index 00000000000..5c32b7487f6 --- /dev/null +++ b/pkg/security/podsecuritypolicy/util/util_test.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +// TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on +// a VolumeSource object that GetVolumeFSType is returning a good value. This ensures both that we're +// returning an FSType for the VolumeSource field (protect the GetVolumeFSType method) and that we +// haven't drifted (ensure new fields in VolumeSource are covered). +func TestVolumeSourceFSTypeDrift(t *testing.T) { + allFSTypes := GetAllFSTypesAsSet() + val := reflect.ValueOf(api.VolumeSource{}) + + for i := 0; i < val.NumField(); i++ { + fieldVal := val.Type().Field(i) + + volumeSource := api.VolumeSource{} + volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) + + reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) + + fsType, err := GetVolumeFSType(api.Volume{VolumeSource: volumeSource}) + if err != nil { + t.Errorf("error getting fstype for field %s. This likely means that drift has occured between FSType and VolumeSource. Please update the api and getVolumeFSType", fieldVal.Name) + } + + if !allFSTypes.Has(string(fsType)) { + t.Errorf("%s was missing from GetFSTypesAsSet", fsType) + } + } +} + +func TestPSPAllowsFSType(t *testing.T) { + tests := map[string]struct { + psp *extensions.PodSecurityPolicy + fsType extensions.FSType + allows bool + }{ + "nil psp": { + psp: nil, + fsType: extensions.HostPath, + allows: false, + }, + "empty volumes": { + psp: &extensions.PodSecurityPolicy{}, + fsType: extensions.HostPath, + allows: false, + }, + "non-matching": { + psp: &extensions.PodSecurityPolicy{ + Spec: extensions.PodSecurityPolicySpec{ + Volumes: []extensions.FSType{extensions.AWSElasticBlockStore}, + }, + }, + fsType: extensions.HostPath, + allows: false, + }, + "match on FSTypeAll": { + psp: &extensions.PodSecurityPolicy{ + Spec: extensions.PodSecurityPolicySpec{ + Volumes: []extensions.FSType{extensions.All}, + }, + }, + fsType: extensions.HostPath, + allows: true, + }, + "match on direct match": { + psp: &extensions.PodSecurityPolicy{ + Spec: extensions.PodSecurityPolicySpec{ + Volumes: []extensions.FSType{extensions.HostPath}, + }, + }, + fsType: extensions.HostPath, + allows: true, + }, + } + + for k, v := range tests { + allows := PSPAllowsFSType(v.psp, v.fsType) + if v.allows != allows { + t.Errorf("%s expected PSPAllowsFSType to return %t but got %t", k, v.allows, allows) + } + } +} diff --git a/plugin/pkg/admission/security/doc.go b/plugin/pkg/admission/security/doc.go new file mode 100644 index 00000000000..846a4d55cf4 --- /dev/null +++ b/plugin/pkg/admission/security/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// security contains admission plugins specific to cluster security. +package security From 4970f0c02d5384763b9833a35c639cea95df1e0f Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Thu, 5 May 2016 20:05:27 -0400 Subject: [PATCH 3/6] PSP admission --- cmd/kube-apiserver/app/plugins.go | 1 + .../security/podsecuritypolicy/admission.go | 286 ++++ .../podsecuritypolicy/admission_test.go | 1191 +++++++++++++++++ 3 files changed, 1478 insertions(+) create mode 100644 plugin/pkg/admission/security/podsecuritypolicy/admission.go create mode 100644 plugin/pkg/admission/security/podsecuritypolicy/admission_test.go diff --git a/cmd/kube-apiserver/app/plugins.go b/cmd/kube-apiserver/app/plugins.go index c7fd8d60742..e8ccd0efbf9 100644 --- a/cmd/kube-apiserver/app/plugins.go +++ b/cmd/kube-apiserver/app/plugins.go @@ -36,6 +36,7 @@ import ( _ "k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle" _ "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label" _ "k8s.io/kubernetes/plugin/pkg/admission/resourcequota" + _ "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy" _ "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny" _ "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" ) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go new file mode 100644 index 00000000000..acf8602a200 --- /dev/null +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission.go @@ -0,0 +1,286 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "fmt" + "io" + "strings" + + "github.com/golang/glog" + + admission "k8s.io/kubernetes/pkg/admission" + api "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "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" + "k8s.io/kubernetes/pkg/runtime" + 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/validation/field" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + PluginName = "PodSecurityPolicy" +) + +func init() { + admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) { + plugin := NewPlugin(client, psp.NewSimpleStrategyFactory(), getMatchingPolicies, false) + plugin.Run() + return plugin, nil + }) +} + +// PSPMatchFn allows plugging in how PSPs are matched against user information. +type PSPMatchFn func(store cache.Store, user user.Info, sa user.Info) ([]*extensions.PodSecurityPolicy, error) + +// podSecurityPolicyPlugin holds state for and implements the admission plugin. +type podSecurityPolicyPlugin struct { + *admission.Handler + client clientset.Interface + strategyFactory psp.StrategyFactory + pspMatcher PSPMatchFn + failOnNoPolicies bool + + reflector *cache.Reflector + stopChan chan struct{} + store cache.Store +} + +var _ admission.Interface = &podSecurityPolicyPlugin{} + +// NewPlugin creates a new PSP admission plugin. +func NewPlugin(kclient clientset.Interface, strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *podSecurityPolicyPlugin { + store := cache.NewStore(cache.MetaNamespaceKeyFunc) + reflector := cache.NewReflector( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kclient.Extensions().PodSecurityPolicies().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kclient.Extensions().PodSecurityPolicies().Watch(options) + }, + }, + &extensions.PodSecurityPolicy{}, + store, + 0, + ) + + return &podSecurityPolicyPlugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + client: kclient, + strategyFactory: strategyFactory, + pspMatcher: pspMatcher, + failOnNoPolicies: failOnNoPolicies, + + store: store, + reflector: reflector, + } +} + +func (a *podSecurityPolicyPlugin) Run() { + if a.stopChan == nil { + a.stopChan = make(chan struct{}) + } + a.reflector.RunUntil(a.stopChan) +} +func (a *podSecurityPolicyPlugin) Stop() { + if a.stopChan != nil { + close(a.stopChan) + a.stopChan = nil + } +} + +// Admit determines if the pod should be admitted based on the requested security context +// and the available PSPs. +// +// 1. Find available PSPs. +// 2. Create the providers, includes setting pre-allocated values if necessary. +// 3. Try to generate and validate a PSP with providers. If we find one then admit the pod +// with the validated PSP. If we don't find any reject the pod and give all errors from the +// failed attempts. +func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { + if a.GetResource().GroupResource() != api.Resource("pods") { + return nil + } + + if len(a.GetSubresource()) != 0 { + return nil + } + + pod, ok := a.GetObject().(*api.Pod) + // if we can't convert then we don't handle this object so just return + if !ok { + return nil + } + + // get all constraints that are usable by the user + glog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) + var saInfo user.Info + if len(pod.Spec.ServiceAccountName) > 0 { + saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") + } + + matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo) + if err != nil { + return admission.NewForbidden(a, err) + } + + // if we have no policies and want to succeed then return. Otherwise we'll end up with no + // providers and fail with "unable to validate against any pod security policy" below. + if len(matchedPolicies) == 0 && !c.failOnNoPolicies { + return nil + } + + providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) + logProviders(pod, providers, errs) + + if len(providers) == 0 { + return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) + } + + // all containers in a single pod must validate under a single provider or we will reject the request + validationErrs := field.ErrorList{} + for _, provider := range providers { + if errs := assignSecurityContext(provider, pod, 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()) + if pod.ObjectMeta.Annotations == nil { + pod.ObjectMeta.Annotations = map[string]string{} + } + pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.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) + return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) +} + +// assignSecurityContext creates a security context for each container in the pod +// 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)) + + errs := field.ErrorList{} + + psc, 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 + 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.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.DetermineEffectiveSecurityContext(pod, &containerCopy) + + sc, err := provider.CreateContainerSecurityContext(pod, &containerCopy) + 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 + errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, 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 + 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 generatedSCs { + pod.Spec.Containers[i].SecurityContext = sc + } + return nil +} + +// createProvidersFromPolicies creates providers from the constraints supplied. +func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) { + var ( + // collected providers + providers []psp.Provider + // collected errors to return + errs []error + ) + + for _, constraint := range psps { + provider, err := psp.NewSimpleProvider(constraint, namespace, c.strategyFactory) + if err != nil { + errs = append(errs, fmt.Errorf("error creating provider for PSP %s: %v", constraint.Name, err)) + continue + } + providers = append(providers, provider) + } + return providers, errs +} + +// getMatchingPolicies returns policies from the store. For now this returns everything +// in the future it can filter based on UserInfo and permissions. +func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info) ([]*extensions.PodSecurityPolicy, error) { + matchedPolicies := make([]*extensions.PodSecurityPolicy, 0) + + for _, c := range store.List() { + constraint, ok := c.(*extensions.PodSecurityPolicy) + if !ok { + return nil, errors.NewInternalError(fmt.Errorf("error converting object from store to a pod security policy: %v", c)) + } + matchedPolicies = append(matchedPolicies, constraint) + } + + return matchedPolicies, nil +} + +// 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) { + names := make([]string, len(providers)) + 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, ",")) + + for _, err := range providerCreationErrs { + glog.V(4).Infof("provider creation error: %v", err) + } +} diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go new file mode 100644 index 00000000000..8292f24bae6 --- /dev/null +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go @@ -0,0 +1,1191 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "fmt" + "reflect" + "strings" + "testing" + + kadmission "k8s.io/kubernetes/pkg/admission" + kapi "k8s.io/kubernetes/pkg/api" + 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" + clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + kpsp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" + psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" + diff "k8s.io/kubernetes/pkg/util/diff" +) + +func NewTestAdmission(store cache.Store, kclient clientset.Interface) kadmission.Interface { + return &podSecurityPolicyPlugin{ + Handler: kadmission.NewHandler(kadmission.Create), + client: kclient, + store: store, + strategyFactory: kpsp.NewSimpleStrategyFactory(), + pspMatcher: getMatchingPolicies, + } +} + +func TestAdmitPrivileged(t *testing.T) { + createPodWithPriv := func(priv bool) *kapi.Pod { + pod := goodPod() + pod.Spec.Containers[0].SecurityContext.Privileged = &priv + return pod + } + + nonPrivilegedPSP := restrictivePSP() + nonPrivilegedPSP.Name = "non-priv" + nonPrivilegedPSP.Spec.Privileged = false + + privilegedPSP := restrictivePSP() + privilegedPSP.Name = "priv" + privilegedPSP.Spec.Privileged = true + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedPriv bool + expectedPSP string + }{ + "pod without priv request allowed under non priv PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, + shouldPass: true, + expectedPriv: false, + expectedPSP: nonPrivilegedPSP.Name, + }, + "pod without priv request allowed under priv PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{privilegedPSP}, + shouldPass: true, + expectedPriv: false, + expectedPSP: privilegedPSP.Name, + }, + "pod with priv request denied by non priv PSP": { + pod: createPodWithPriv(true), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP}, + shouldPass: false, + }, + "pod with priv request allowed by priv PSP": { + pod: createPodWithPriv(true), + psps: []*extensions.PodSecurityPolicy{nonPrivilegedPSP, privilegedPSP}, + shouldPass: true, + expectedPriv: true, + expectedPSP: privilegedPSP.Name, + }, + } + + for k, v := range tests { + 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) + } + } + } +} + +func TestAdmitCaps(t *testing.T) { + createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { + pod := goodPod() + pod.Spec.Containers[0].SecurityContext.Capabilities = caps + return pod + } + + restricted := restrictivePSP() + + allowsFooInAllowed := restrictivePSP() + allowsFooInAllowed.Name = "allowCapInAllowed" + allowsFooInAllowed.Spec.AllowedCapabilities = []kapi.Capability{"foo"} + + allowsFooInRequired := restrictivePSP() + allowsFooInRequired.Name = "allowCapInRequired" + allowsFooInRequired.Spec.DefaultAddCapabilities = []kapi.Capability{"foo"} + + requiresFooToBeDropped := restrictivePSP() + requiresFooToBeDropped.Name = "requireDrop" + requiresFooToBeDropped.Spec.RequiredDropCapabilities = []kapi.Capability{"foo"} + + tc := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedCapabilities *kapi.Capabilities + expectedPSP string + }{ + // UC 1: if a PSP does not define allowed or required caps then a pod requesting a cap + // should be rejected. + "should reject cap add when not allowed or required": { + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted}, + shouldPass: false, + }, + // UC 2: if a PSP allows a cap in the allowed field it should accept the pod request + // to add the cap. + "should accept cap add when in allowed": { + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInAllowed}, + shouldPass: true, + expectedPSP: allowsFooInAllowed.Name, + }, + // UC 3: if a PSP requires a cap then it should accept the pod request + // to add the cap. + "should accept cap add when in required": { + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, allowsFooInRequired}, + shouldPass: true, + expectedPSP: allowsFooInRequired.Name, + }, + // UC 4: if a PSP requires a cap to be dropped then it should fail both + // in the verification of adds and verification of drops + "should reject cap add when requested cap is required to be dropped": { + pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{restricted, requiresFooToBeDropped}, + shouldPass: false, + }, + // UC 5: if a PSP requires a cap to be dropped it should accept + // a manual request to drop the cap. + "should accept cap drop when cap is required to be dropped": { + pod: createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}), + psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, + shouldPass: true, + expectedPSP: requiresFooToBeDropped.Name, + }, + // UC 6: required add is defaulted + "required add is defaulted": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{allowsFooInRequired}, + shouldPass: true, + expectedCapabilities: &kapi.Capabilities{ + Add: []kapi.Capability{"foo"}, + }, + expectedPSP: allowsFooInRequired.Name, + }, + // UC 7: required drop is defaulted + "required drop is defaulted": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{requiresFooToBeDropped}, + shouldPass: true, + expectedCapabilities: &kapi.Capabilities{ + Drop: []kapi.Capability{"foo"}, + }, + expectedPSP: requiresFooToBeDropped.Name, + }, + } + + for k, v := range tc { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.expectedCapabilities != nil { + if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) { + t.Errorf("%s resulted in caps that were not expected - expected: %v, received: %v", k, v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) + } + } + } +} + +func TestAdmitVolumes(t *testing.T) { + val := reflect.ValueOf(kapi.VolumeSource{}) + + for i := 0; i < val.NumField(); i++ { + // reflectively create the volume source + fieldVal := val.Type().Field(i) + + volumeSource := kapi.VolumeSource{} + volumeSourceVolume := reflect.New(fieldVal.Type.Elem()) + + reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume) + volume := kapi.Volume{VolumeSource: volumeSource} + + // sanity check before moving on + fsType, err := psputil.GetVolumeFSType(volume) + if err != nil { + t.Errorf("error getting FSType for %s: %s", fieldVal.Name, err.Error()) + continue + } + + // add the volume to the pod + pod := goodPod() + pod.Spec.Volumes = []kapi.Volume{volume} + + // create a PSP that allows no volumes + psp := restrictivePSP() + + // expect a denial for this PSP + testPSPAdmit(fmt.Sprintf("%s denial", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, false, "", t) + + // now add the fstype directly to the psp and it should validate + psp.Spec.Volumes = []extensions.FSType{fsType} + testPSPAdmit(fmt.Sprintf("%s direct accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, psp.Name, t) + + // now change the psp to allow any volumes and the pod should still validate + psp.Spec.Volumes = []extensions.FSType{extensions.All} + testPSPAdmit(fmt.Sprintf("%s wildcard accept", string(fsType)), []*extensions.PodSecurityPolicy{psp}, pod, true, psp.Name, t) + } +} + +func TestAdmitHostNetwork(t *testing.T) { + createPodWithHostNetwork := func(hostNetwork bool) *kapi.Pod { + pod := goodPod() + pod.Spec.SecurityContext.HostNetwork = hostNetwork + return pod + } + + noHostNetwork := restrictivePSP() + noHostNetwork.Name = "no-hostnetwork" + noHostNetwork.Spec.HostNetwork = false + + hostNetwork := restrictivePSP() + hostNetwork.Name = "hostnetwork" + hostNetwork.Spec.HostNetwork = true + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedHostNetwork bool + expectedPSP string + }{ + "pod without hostnetwork request allowed under noHostNetwork PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostNetwork}, + shouldPass: true, + expectedHostNetwork: false, + expectedPSP: noHostNetwork.Name, + }, + "pod without hostnetwork request allowed under hostNetwork PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostNetwork}, + shouldPass: true, + expectedHostNetwork: false, + expectedPSP: hostNetwork.Name, + }, + "pod with hostnetwork request denied by noHostNetwork PSP": { + pod: createPodWithHostNetwork(true), + psps: []*extensions.PodSecurityPolicy{noHostNetwork}, + shouldPass: false, + }, + "pod with hostnetwork request allowed by hostNetwork PSP": { + pod: createPodWithHostNetwork(true), + psps: []*extensions.PodSecurityPolicy{noHostNetwork, hostNetwork}, + shouldPass: true, + expectedHostNetwork: true, + expectedPSP: hostNetwork.Name, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.shouldPass { + if v.pod.Spec.SecurityContext.HostNetwork != v.expectedHostNetwork { + t.Errorf("%s expected hostNetwork to be %t", k, v.expectedHostNetwork) + } + } + } +} + +func TestAdmitHostPorts(t *testing.T) { + createPodWithHostPorts := func(port int32) *kapi.Pod { + pod := goodPod() + pod.Spec.Containers[0].Ports = []kapi.ContainerPort{ + {HostPort: port}, + } + return pod + } + + noHostPorts := restrictivePSP() + noHostPorts.Name = "noHostPorts" + + hostPorts := restrictivePSP() + hostPorts.Name = "hostPorts" + hostPorts.Spec.HostPorts = []extensions.HostPortRange{ + {Min: 1, Max: 10}, + } + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedPSP string + }{ + "host port out of range": { + pod: createPodWithHostPorts(11), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPass: false, + }, + "host port in range": { + pod: createPodWithHostPorts(5), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPass: true, + expectedPSP: hostPorts.Name, + }, + "no host ports with range": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostPorts}, + shouldPass: true, + expectedPSP: hostPorts.Name, + }, + "no host ports without range": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostPorts}, + shouldPass: true, + expectedPSP: noHostPorts.Name, + }, + "host ports without range": { + pod: createPodWithHostPorts(5), + psps: []*extensions.PodSecurityPolicy{noHostPorts}, + shouldPass: false, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + } +} + +func TestAdmitHostPID(t *testing.T) { + createPodWithHostPID := func(hostPID bool) *kapi.Pod { + pod := goodPod() + pod.Spec.SecurityContext.HostPID = hostPID + return pod + } + + noHostPID := restrictivePSP() + noHostPID.Name = "no-hostpid" + noHostPID.Spec.HostPID = false + + hostPID := restrictivePSP() + hostPID.Name = "hostpid" + hostPID.Spec.HostPID = true + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedHostPID bool + expectedPSP string + }{ + "pod without hostpid request allowed under noHostPID PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostPID}, + shouldPass: true, + expectedHostPID: false, + expectedPSP: noHostPID.Name, + }, + "pod without hostpid request allowed under hostPID PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostPID}, + shouldPass: true, + expectedHostPID: false, + expectedPSP: hostPID.Name, + }, + "pod with hostpid request denied by noHostPID PSP": { + pod: createPodWithHostPID(true), + psps: []*extensions.PodSecurityPolicy{noHostPID}, + shouldPass: false, + }, + "pod with hostpid request allowed by hostPID PSP": { + pod: createPodWithHostPID(true), + psps: []*extensions.PodSecurityPolicy{noHostPID, hostPID}, + shouldPass: true, + expectedHostPID: true, + expectedPSP: hostPID.Name, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.shouldPass { + if v.pod.Spec.SecurityContext.HostPID != v.expectedHostPID { + t.Errorf("%s expected hostPID to be %t", k, v.expectedHostPID) + } + } + } +} + +func TestAdmitHostIPC(t *testing.T) { + createPodWithHostIPC := func(hostIPC bool) *kapi.Pod { + pod := goodPod() + pod.Spec.SecurityContext.HostIPC = hostIPC + return pod + } + + noHostIPC := restrictivePSP() + noHostIPC.Name = "no-hostIPC" + noHostIPC.Spec.HostIPC = false + + hostIPC := restrictivePSP() + hostIPC.Name = "hostIPC" + hostIPC.Spec.HostIPC = true + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedHostIPC bool + expectedPSP string + }{ + "pod without hostIPC request allowed under noHostIPC PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{noHostIPC}, + shouldPass: true, + expectedHostIPC: false, + expectedPSP: noHostIPC.Name, + }, + "pod without hostIPC request allowed under hostIPC PSP": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{hostIPC}, + shouldPass: true, + expectedHostIPC: false, + expectedPSP: hostIPC.Name, + }, + "pod with hostIPC request denied by noHostIPC PSP": { + pod: createPodWithHostIPC(true), + psps: []*extensions.PodSecurityPolicy{noHostIPC}, + shouldPass: false, + }, + "pod with hostIPC request allowed by hostIPC PSP": { + pod: createPodWithHostIPC(true), + psps: []*extensions.PodSecurityPolicy{noHostIPC, hostIPC}, + shouldPass: true, + expectedHostIPC: true, + expectedPSP: hostIPC.Name, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.shouldPass { + if v.pod.Spec.SecurityContext.HostIPC != v.expectedHostIPC { + t.Errorf("%s expected hostIPC to be %t", k, v.expectedHostIPC) + } + } + } +} + +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 + } + + runAsAny := restrictivePSP() + runAsAny.Name = "runAsAny" + runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny + + mustRunAs := restrictivePSP() + mustRunAs.Name = "mustRunAs" + 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 + }{ + "runAsAny with no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedSELinux: nil, + 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, + }, + "mustRunAs with bad pod request": { + pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), + 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 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, + }, + } + + for k, v := range tests { + 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 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) + } + } + } +} + +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 + pod.Spec.SecurityContext.RunAsUser = &user + return pod + } + + runAsAny := restrictivePSP() + runAsAny.Name = "runAsAny" + runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny + + mustRunAs := restrictivePSP() + mustRunAs.Name = "mustRunAs" + + runAsNonRoot := restrictivePSP() + runAsNonRoot.Name = "runAsNonRoot" + runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedRunAsUser *int + expectedPSP string + }{ + "runAsAny no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedRunAsUser: nil, + expectedPSP: runAsAny.Name, + }, + "runAsAny pod request": { + pod: createPodWithRunAsUser(1), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedRunAsUser: intPtr(1), + expectedPSP: runAsAny.Name, + }, + "mustRunAs pod request out of range": { + pod: createPodWithRunAsUser(1), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: false, + }, + "mustRunAs pod request in range": { + pod: createPodWithRunAsUser(999), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedRunAsUser: intPtr(int(mustRunAs.Spec.RunAsUser.Ranges[0].Min)), + expectedPSP: mustRunAs.Name, + }, + "mustRunAs no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedRunAsUser: intPtr(int(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, + }, + "runAsNonRoot pod request root": { + pod: createPodWithRunAsUser(0), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: false, + }, + "runAsNonRoot pod request non-root": { + pod: createPodWithRunAsUser(1), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: true, + expectedRunAsUser: intPtr(1), + expectedPSP: runAsNonRoot.Name, + }, + } + + for k, v := range tests { + 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 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 int64(*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) + } + } + } +} + +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 + pod.Spec.SecurityContext.SupplementalGroups = []int64{group} + return pod + } + + runAsAny := restrictivePSP() + runAsAny.Name = "runAsAny" + runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny + + mustRunAs := restrictivePSP() + mustRunAs.Name = "mustRunAs" + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedSupGroups []int64 + expectedPSP string + }{ + "runAsAny no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedSupGroups: []int64{}, + expectedPSP: runAsAny.Name, + }, + "runAsAny pod request": { + pod: createPodWithSupGroup(1), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedSupGroups: []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, + }, + "mustRunAs bad pod request": { + pod: createPodWithSupGroup(1), + 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, + }, + } + + for k, v := range tests { + 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) + } + } + } +} + +func TestAdmitFSGroup(t *testing.T) { + createPodWithFSGroup := 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 + pod.Spec.SecurityContext.FSGroup = &group + return pod + } + + runAsAny := restrictivePSP() + runAsAny.Name = "runAsAny" + runAsAny.Spec.FSGroup.Rule = extensions.FSGroupStrategyRunAsAny + + mustRunAs := restrictivePSP() + mustRunAs.Name = "mustRunAs" + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedFSGroup *int64 + expectedPSP string + }{ + "runAsAny no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedFSGroup: nil, + expectedPSP: runAsAny.Name, + }, + "runAsAny pod request": { + pod: createPodWithFSGroup(1), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedFSGroup: int64Ptr(1), + expectedPSP: runAsAny.Name, + }, + "mustRunAs no pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedFSGroup: &mustRunAs.Spec.SupplementalGroups.Ranges[0].Min, + expectedPSP: mustRunAs.Name, + }, + "mustRunAs bad pod request": { + pod: createPodWithFSGroup(1), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: false, + }, + "mustRunAs good pod request": { + pod: createPodWithFSGroup(999), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedFSGroup: int64Ptr(999), + expectedPSP: mustRunAs.Name, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.shouldPass { + if v.pod.Spec.SecurityContext.FSGroup == nil && v.expectedFSGroup == nil { + // ok, don't need to worry about identifying specific diffs + continue + } + if v.pod.Spec.SecurityContext.FSGroup == nil && v.expectedFSGroup != nil { + t.Errorf("%s expected FSGroup to be: %v but found nil", k, *v.expectedFSGroup) + continue + } + if v.pod.Spec.SecurityContext.FSGroup != nil && v.expectedFSGroup == nil { + t.Errorf("%s expected FSGroup to be nil but found: %v", k, *v.pod.Spec.SecurityContext.FSGroup) + continue + } + if *v.expectedFSGroup != *v.pod.Spec.SecurityContext.FSGroup { + t.Errorf("%s expected FSGroup to be: %v but found %v", k, *v.expectedFSGroup, *v.pod.Spec.SecurityContext.FSGroup) + } + } + } +} + +func TestAdmitReadOnlyRootFilesystem(t *testing.T) { + createPodWithRORFS := func(rorfs bool) *kapi.Pod { + pod := goodPod() + pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = &rorfs + return pod + } + + noRORFS := restrictivePSP() + noRORFS.Name = "no-rorfs" + noRORFS.Spec.ReadOnlyRootFilesystem = false + + rorfs := restrictivePSP() + rorfs.Name = "rorfs" + rorfs.Spec.ReadOnlyRootFilesystem = true + + tests := map[string]struct { + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedRORFS bool + expectedPSP string + }{ + "no-rorfs allows pod request with rorfs": { + pod: createPodWithRORFS(true), + psps: []*extensions.PodSecurityPolicy{noRORFS}, + shouldPass: true, + expectedRORFS: true, + expectedPSP: noRORFS.Name, + }, + "no-rorfs allows pod request without rorfs": { + pod: createPodWithRORFS(false), + psps: []*extensions.PodSecurityPolicy{noRORFS}, + shouldPass: true, + expectedRORFS: false, + expectedPSP: noRORFS.Name, + }, + "rorfs rejects pod request without rorfs": { + pod: createPodWithRORFS(false), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPass: false, + }, + "rorfs defaults nil pod request": { + pod: goodPod(), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPass: true, + expectedRORFS: true, + expectedPSP: rorfs.Name, + }, + "rorfs accepts pod request with rorfs": { + pod: createPodWithRORFS(true), + psps: []*extensions.PodSecurityPolicy{rorfs}, + shouldPass: true, + expectedRORFS: true, + expectedPSP: rorfs.Name, + }, + } + + for k, v := range tests { + testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) + + if v.shouldPass { + if v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem == nil || + *v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem != v.expectedRORFS { + t.Errorf("%s expected ReadOnlyRootFilesystem to be %t but found %#v", k, v.expectedRORFS, v.pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem) + } + } + } +} + +func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) { + namespace := createNamespaceForTest() + serviceAccount := createSAForTest() + tc := clientsetfake.NewSimpleClientset(namespace, serviceAccount) + store := cache.NewStore(cache.MetaNamespaceKeyFunc) + + for _, psp := range psps { + store.Add(psp) + } + + plugin := NewTestAdmission(store, tc) + + attrs := kadmission.NewAttributesRecord(pod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) + err := plugin.Admit(attrs) + + if shouldPass && err != nil { + 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]) + } + } + + if !shouldPass && err == nil { + t.Errorf("%s expected errors but received none", testCaseName) + } +} + +func TestAssignSecurityContext(t *testing.T) { + // psp that will deny privileged container requests and has a default value for a field (uid) + psp := restrictivePSP() + provider, err := kpsp.NewSimpleProvider(psp, "namespace", kpsp.NewSimpleStrategyFactory()) + if err != nil { + t.Fatalf("failed to create provider: %v", err) + } + + createContainer := func(priv bool) kapi.Container { + return kapi.Container{ + SecurityContext: &kapi.SecurityContext{ + Privileged: &priv, + }, + } + } + + // 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 + expectedUID *int64 + }{ + "pod and container SC is not changed when invalid": { + pod: &kapi.Pod{ + Spec: kapi.PodSpec{ + SecurityContext: &kapi.PodSecurityContext{}, + Containers: []kapi.Container{createContainer(true)}, + }, + }, + shouldValidate: false, + }, + "must validate all containers": { + pod: &kapi.Pod{ + Spec: kapi.PodSpec{ + // good container and bad container + SecurityContext: &kapi.PodSecurityContext{}, + Containers: []kapi.Container{createContainer(false), createContainer(true)}, + }, + }, + shouldValidate: false, + }, + "pod validates": { + pod: &kapi.Pod{ + Spec: kapi.PodSpec{ + SecurityContext: &kapi.PodSecurityContext{}, + Containers: []kapi.Container{createContainer(false)}, + }, + }, + shouldValidate: true, + }, + } + + for k, v := range testCases { + errs := assignSecurityContext(provider, v.pod, nil) + if v.shouldValidate && len(errs) > 0 { + t.Errorf("%s expected to validate but received errors %v", k, errs) + continue + } + if !v.shouldValidate && len(errs) == 0 { + 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) + } + } + } + } +} + +func TestCreateProvidersFromConstraints(t *testing.T) { + testCases := map[string]struct { + // use a generating function so we can test for non-mutation + psp func() *extensions.PodSecurityPolicy + expectedErr string + }{ + "valid psp": { + psp: func() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: kapi.ObjectMeta{ + Name: "valid psp", + }, + Spec: extensions.PodSecurityPolicySpec{ + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, + }, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyRunAsAny, + }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, + }, + } + }, + }, + "bad psp strategy options": { + psp: func() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: kapi.ObjectMeta{ + Name: "bad psp user options", + }, + Spec: extensions.PodSecurityPolicySpec{ + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, + }, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyMustRunAs, + }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, + }, + } + }, + expectedErr: "MustRunAsRange requires at least one range", + }, + } + + for k, v := range testCases { + store := cache.NewStore(cache.MetaNamespaceKeyFunc) + + tc := clientsetfake.NewSimpleClientset() + admit := &podSecurityPolicyPlugin{ + Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), + client: tc, + store: store, + strategyFactory: kpsp.NewSimpleStrategyFactory(), + } + + psp := v.psp() + _, errs := admit.createProvidersFromPolicies([]*extensions.PodSecurityPolicy{psp}, "namespace") + + if !reflect.DeepEqual(psp, v.psp()) { + diff := diff.ObjectDiff(psp, v.psp()) + t.Errorf("%s createProvidersFromPolicies mutated policy. diff:\n%s", k, diff) + } + if len(v.expectedErr) > 0 && len(errs) != 1 { + t.Errorf("%s expected a single error '%s' but received %v", k, v.expectedErr, errs) + continue + } + if len(v.expectedErr) == 0 && len(errs) != 0 { + t.Errorf("%s did not expect an error but received %v", k, errs) + continue + } + + // check that we got the error we expected + if len(v.expectedErr) > 0 { + if !strings.Contains(errs[0].Error(), v.expectedErr) { + t.Errorf("%s expected error '%s' but received %v", k, v.expectedErr, errs[0]) + } + } + } +} + +func restrictivePSP() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: kapi.ObjectMeta{ + Name: "restrictive", + }, + Spec: extensions.PodSecurityPolicySpec{ + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 999, Max: 999}, + }, + }, + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyMustRunAs, + SELinuxOptions: &kapi.SELinuxOptions{ + Level: "s9:z0,z1", + }, + }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 999, Max: 999}, + }, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyMustRunAs, + Ranges: []extensions.IDRange{ + {Min: 999, Max: 999}, + }, + }, + }, + } +} + +func createNamespaceForTest() *kapi.Namespace { + return &kapi.Namespace{ + ObjectMeta: kapi.ObjectMeta{ + Name: "default", + }, + } +} + +func createSAForTest() *kapi.ServiceAccount { + return &kapi.ServiceAccount{ + ObjectMeta: kapi.ObjectMeta{ + Name: "default", + }, + } +} + +// goodPod is empty and should not be used directly for testing since we're providing +// two different PSPs. Since no values are specified it would be allowed to match any +// psp when defaults are filled in. +func goodPod() *kapi.Pod { + return &kapi.Pod{ + Spec: kapi.PodSpec{ + ServiceAccountName: "default", + SecurityContext: &kapi.PodSecurityContext{}, + Containers: []kapi.Container{ + { + SecurityContext: &kapi.SecurityContext{}, + }, + }, + }, + } +} + +func intPtr(i int) *int { + return &i +} + +func int64Ptr(i int) *int64 { + i64 := int64(i) + return &i64 +} From 53b1a9da90995368c2c02e525ace176bb133de6e Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Thu, 5 May 2016 15:44:13 -0400 Subject: [PATCH 4/6] PSP manual generator changes --- .../typed/extensions/unversioned/generated_expansion.go | 2 ++ .../generated/extensions/unversioned/generated_expansion.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go index 0690e0c8c33..7a1999454e6 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go @@ -24,6 +24,8 @@ type IngressExpansion interface{} type JobExpansion interface{} +type PodSecurityPolicyExpansion interface{} + type ThirdPartyResourceExpansion interface{} type ReplicaSetExpansion interface{} diff --git a/pkg/client/typed/generated/extensions/unversioned/generated_expansion.go b/pkg/client/typed/generated/extensions/unversioned/generated_expansion.go index 0690e0c8c33..9fe003bb0f2 100644 --- a/pkg/client/typed/generated/extensions/unversioned/generated_expansion.go +++ b/pkg/client/typed/generated/extensions/unversioned/generated_expansion.go @@ -27,3 +27,5 @@ type JobExpansion interface{} type ThirdPartyResourceExpansion interface{} type ReplicaSetExpansion interface{} + +type PodSecurityPolicyExpansion interface{} From f11a4ab9a7dbf079c3d0bf026172253640f3726d Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Thu, 5 May 2016 15:44:30 -0400 Subject: [PATCH 5/6] generated code --- docs/admin/kube-apiserver.md | 4 +- pkg/apis/extensions/deep_copy_generated.go | 65 +- pkg/apis/extensions/types.generated.go | 1050 ++++++++++++++--- .../v1beta1/conversion_generated.go | 146 ++- .../extensions/v1beta1/deep_copy_generated.go | 65 +- pkg/apis/extensions/v1beta1/generated.pb.go | 683 +++++++++-- pkg/apis/extensions/v1beta1/generated.proto | 62 +- .../extensions/v1beta1/types.generated.go | 1050 ++++++++++++++--- .../v1beta1/types_swagger_doc_generated.go | 45 +- .../unversioned/extensions_client.go | 5 + .../fake/fake_extensions_client.go | 4 + .../fake/fake_podsecuritypolicy.go | 99 ++ .../unversioned/podsecuritypolicy.go | 127 ++ 13 files changed, 2995 insertions(+), 410 deletions(-) create mode 100644 pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_podsecuritypolicy.go create mode 100644 pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/podsecuritypolicy.go diff --git a/docs/admin/kube-apiserver.md b/docs/admin/kube-apiserver.md index 7b7b94d3148..b858f3603e1 100644 --- a/docs/admin/kube-apiserver.md +++ b/docs/admin/kube-apiserver.md @@ -51,7 +51,7 @@ kube-apiserver ### Options ``` - --admission-control="AlwaysAdmit": Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DenyEscalatingExec, DenyExecOnPrivileged, InitialResources, LimitPodHardAntiAffinityTopology, LimitRanger, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, PersistentVolumeLabel, ResourceQuota, SecurityContextDeny, ServiceAccount + --admission-control="AlwaysAdmit": Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DenyEscalatingExec, DenyExecOnPrivileged, InitialResources, LimitPodHardAntiAffinityTopology, LimitRanger, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, PersistentVolumeLabel, PodSecurityPolicy, ResourceQuota, SecurityContextDeny, ServiceAccount --admission-control-config-file="": File with admission control configuration. --advertise-address=: The IP address on which to advertise the apiserver to members of the cluster. This address must be reachable by the rest of the cluster. If blank, the --bind-address will be used. If --bind-address is unspecified, the host's default interface will be used. --allow-privileged[=false]: If true, allow privileged containers. @@ -119,7 +119,7 @@ kube-apiserver --watch-cache-sizes=[]: List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. The individual override format: resource#size, where size is a number. It takes effect when watch-cache is enabled. ``` -###### Auto generated by spf13/cobra on 9-May-2016 +###### Auto generated by spf13/cobra on 10-May-2016 diff --git a/pkg/apis/extensions/deep_copy_generated.go b/pkg/apis/extensions/deep_copy_generated.go index 1f9608ee36f..835765e6d19 100644 --- a/pkg/apis/extensions/deep_copy_generated.go +++ b/pkg/apis/extensions/deep_copy_generated.go @@ -45,6 +45,7 @@ func init() { DeepCopy_extensions_DeploymentSpec, DeepCopy_extensions_DeploymentStatus, DeepCopy_extensions_DeploymentStrategy, + DeepCopy_extensions_FSGroupStrategyOptions, DeepCopy_extensions_HTTPIngressPath, DeepCopy_extensions_HTTPIngressRuleValue, DeepCopy_extensions_HostPortRange, @@ -72,6 +73,7 @@ func init() { DeepCopy_extensions_Scale, DeepCopy_extensions_ScaleSpec, DeepCopy_extensions_ScaleStatus, + DeepCopy_extensions_SupplementalGroupsStrategyOptions, DeepCopy_extensions_ThirdPartyResource, DeepCopy_extensions_ThirdPartyResourceData, DeepCopy_extensions_ThirdPartyResourceDataList, @@ -311,6 +313,22 @@ func DeepCopy_extensions_DeploymentStrategy(in DeploymentStrategy, out *Deployme return nil } +func DeepCopy_extensions_FSGroupStrategyOptions(in FSGroupStrategyOptions, out *FSGroupStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.Ranges != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_extensions_IDRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + func DeepCopy_extensions_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error { out.Path = in.Path if err := DeepCopy_extensions_IngressBackend(in.Backend, &out.Backend, c); err != nil { @@ -502,14 +520,32 @@ func DeepCopy_extensions_PodSecurityPolicyList(in PodSecurityPolicyList, out *Po func DeepCopy_extensions_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error { out.Privileged = in.Privileged - if in.Capabilities != nil { - in, out := in.Capabilities, &out.Capabilities + if in.DefaultAddCapabilities != nil { + in, out := in.DefaultAddCapabilities, &out.DefaultAddCapabilities *out = make([]api.Capability, len(in)) for i := range in { (*out)[i] = in[i] } } else { - out.Capabilities = nil + out.DefaultAddCapabilities = nil + } + if in.RequiredDropCapabilities != nil { + in, out := in.RequiredDropCapabilities, &out.RequiredDropCapabilities + *out = make([]api.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.RequiredDropCapabilities = nil + } + if in.AllowedCapabilities != nil { + in, out := in.AllowedCapabilities, &out.AllowedCapabilities + *out = make([]api.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.AllowedCapabilities = nil } if in.Volumes != nil { in, out := in.Volumes, &out.Volumes @@ -540,6 +576,13 @@ func DeepCopy_extensions_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *Po if err := DeepCopy_extensions_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil { return err } + if err := DeepCopy_extensions_SupplementalGroupsStrategyOptions(in.SupplementalGroups, &out.SupplementalGroups, c); err != nil { + return err + } + if err := DeepCopy_extensions_FSGroupStrategyOptions(in.FSGroup, &out.FSGroup, c); err != nil { + return err + } + out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem return nil } @@ -691,6 +734,22 @@ func DeepCopy_extensions_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conver return nil } +func DeepCopy_extensions_SupplementalGroupsStrategyOptions(in SupplementalGroupsStrategyOptions, out *SupplementalGroupsStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.Ranges != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_extensions_IDRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + func DeepCopy_extensions_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err diff --git a/pkg/apis/extensions/types.generated.go b/pkg/apis/extensions/types.generated.go index 9b20d0d7cd0..992cf3f4348 100644 --- a/pkg/apis/extensions/types.generated.go +++ b/pkg/apis/extensions/types.generated.go @@ -11221,23 +11221,24 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [9]bool + var yyq2 [14]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Privileged != false - yyq2[1] = len(x.Capabilities) != 0 - yyq2[2] = len(x.Volumes) != 0 - yyq2[3] = x.HostNetwork != false - yyq2[4] = len(x.HostPorts) != 0 - yyq2[5] = x.HostPID != false - yyq2[6] = x.HostIPC != false - yyq2[7] = true - yyq2[8] = true + yyq2[1] = len(x.DefaultAddCapabilities) != 0 + yyq2[2] = len(x.RequiredDropCapabilities) != 0 + yyq2[3] = len(x.AllowedCapabilities) != 0 + yyq2[4] = len(x.Volumes) != 0 + yyq2[5] = x.HostNetwork != false + yyq2[6] = len(x.HostPorts) != 0 + yyq2[7] = x.HostPID != false + yyq2[8] = x.HostIPC != false + yyq2[13] = x.ReadOnlyRootFilesystem != false var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(9) + r.EncodeArrayStart(14) } else { - yynn2 = 0 + yynn2 = 4 for _, b := range yyq2 { if b { yynn2++ @@ -11274,14 +11275,14 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - if x.Capabilities == nil { + if x.DefaultAddCapabilities == nil { r.EncodeNil() } else { yym7 := z.EncBinary() _ = yym7 if false { } else { - h.encSliceapi_Capability(([]pkg2_api.Capability)(x.Capabilities), e) + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.DefaultAddCapabilities), e) } } } else { @@ -11290,16 +11291,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("capabilities")) + r.EncodeString(codecSelferC_UTF81234, string("defaultAddCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Capabilities == nil { + if x.DefaultAddCapabilities == nil { r.EncodeNil() } else { yym8 := z.EncBinary() _ = yym8 if false { } else { - h.encSliceapi_Capability(([]pkg2_api.Capability)(x.Capabilities), e) + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.DefaultAddCapabilities), e) } } } @@ -11307,14 +11308,14 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[2] { - if x.Volumes == nil { + if x.RequiredDropCapabilities == nil { r.EncodeNil() } else { yym10 := z.EncBinary() _ = yym10 if false { } else { - h.encSliceFSType(([]FSType)(x.Volumes), e) + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.RequiredDropCapabilities), e) } } } else { @@ -11323,16 +11324,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("volumes")) + r.EncodeString(codecSelferC_UTF81234, string("requiredDropCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Volumes == nil { + if x.RequiredDropCapabilities == nil { r.EncodeNil() } else { yym11 := z.EncBinary() _ = yym11 if false { } else { - h.encSliceFSType(([]FSType)(x.Volumes), e) + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.RequiredDropCapabilities), e) } } } @@ -11340,39 +11341,47 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[3] { - yym13 := z.EncBinary() - _ = yym13 - if false { + if x.AllowedCapabilities == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(x.HostNetwork)) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.AllowedCapabilities), e) + } } } else { - r.EncodeBool(false) + r.EncodeNil() } } else { if yyq2[3] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostNetwork")) + r.EncodeString(codecSelferC_UTF81234, string("allowedCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym14 := z.EncBinary() - _ = yym14 - if false { + if x.AllowedCapabilities == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(x.HostNetwork)) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + h.encSliceapi_Capability(([]pkg2_api.Capability)(x.AllowedCapabilities), e) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[4] { - if x.HostPorts == nil { + if x.Volumes == nil { r.EncodeNil() } else { yym16 := z.EncBinary() _ = yym16 if false { } else { - h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + h.encSliceFSType(([]FSType)(x.Volumes), e) } } } else { @@ -11381,16 +11390,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[4] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostPorts")) + r.EncodeString(codecSelferC_UTF81234, string("volumes")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.HostPorts == nil { + if x.Volumes == nil { r.EncodeNil() } else { yym17 := z.EncBinary() _ = yym17 if false { } else { - h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + h.encSliceFSType(([]FSType)(x.Volumes), e) } } } @@ -11402,7 +11411,7 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym19 if false { } else { - r.EncodeBool(bool(x.HostPID)) + r.EncodeBool(bool(x.HostNetwork)) } } else { r.EncodeBool(false) @@ -11410,11 +11419,69 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[5] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostPID")) + r.EncodeString(codecSelferC_UTF81234, string("hostNetwork")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym20 := z.EncBinary() _ = yym20 if false { + } else { + r.EncodeBool(bool(x.HostNetwork)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[6] { + if x.HostPorts == nil { + r.EncodeNil() + } else { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[6] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("hostPorts")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.HostPorts == nil { + r.EncodeNil() + } else { + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[7] { + yym25 := z.EncBinary() + _ = yym25 + if false { + } else { + r.EncodeBool(bool(x.HostPID)) + } + } else { + r.EncodeBool(false) + } + } else { + if yyq2[7] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("hostPID")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym26 := z.EncBinary() + _ = yym26 + if false { } else { r.EncodeBool(bool(x.HostPID)) } @@ -11422,9 +11489,9 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[6] { - yym22 := z.EncBinary() - _ = yym22 + if yyq2[8] { + yym28 := z.EncBinary() + _ = yym28 if false { } else { r.EncodeBool(bool(x.HostIPC)) @@ -11433,12 +11500,12 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeBool(false) } } else { - if yyq2[6] { + if yyq2[8] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("hostIPC")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym23 := z.EncBinary() - _ = yym23 + yym29 := z.EncBinary() + _ = yym29 if false { } else { r.EncodeBool(bool(x.HostIPC)) @@ -11447,36 +11514,71 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[7] { - yy25 := &x.SELinux - yy25.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } + yy31 := &x.SELinux + yy31.CodecEncodeSelf(e) } else { - if yyq2[7] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("seLinux")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy27 := &x.SELinux - yy27.CodecEncodeSelf(e) - } + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("seLinux")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy33 := &x.SELinux + yy33.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[8] { - yy30 := &x.RunAsUser - yy30.CodecEncodeSelf(e) + yy36 := &x.RunAsUser + yy36.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("runAsUser")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy38 := &x.RunAsUser + yy38.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy41 := &x.SupplementalGroups + yy41.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("supplementalGroups")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy43 := &x.SupplementalGroups + yy43.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy46 := &x.FSGroup + yy46.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("fsGroup")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy48 := &x.FSGroup + yy48.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[13] { + yym51 := z.EncBinary() + _ = yym51 + if false { + } else { + r.EncodeBool(bool(x.ReadOnlyRootFilesystem)) + } } else { - r.EncodeNil() + r.EncodeBool(false) } } else { - if yyq2[8] { + if yyq2[13] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("runAsUser")) + r.EncodeString(codecSelferC_UTF81234, string("readOnlyRootFilesystem")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy32 := &x.RunAsUser - yy32.CodecEncodeSelf(e) + yym52 := z.EncBinary() + _ = yym52 + if false { + } else { + r.EncodeBool(bool(x.ReadOnlyRootFilesystem)) + } } } if yyr2 || yy2arr2 { @@ -11546,11 +11648,11 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod } else { x.Privileged = bool(r.DecodeBool()) } - case "capabilities": + case "defaultAddCapabilities": if r.TryDecodeAsNil() { - x.Capabilities = nil + x.DefaultAddCapabilities = nil } else { - yyv5 := &x.Capabilities + yyv5 := &x.DefaultAddCapabilities yym6 := z.DecBinary() _ = yym6 if false { @@ -11558,16 +11660,40 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv5), d) } } - case "volumes": + case "requiredDropCapabilities": if r.TryDecodeAsNil() { - x.Volumes = nil + x.RequiredDropCapabilities = nil } else { - yyv7 := &x.Volumes + yyv7 := &x.RequiredDropCapabilities yym8 := z.DecBinary() _ = yym8 if false { } else { - h.decSliceFSType((*[]FSType)(yyv7), d) + h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv7), d) + } + } + case "allowedCapabilities": + if r.TryDecodeAsNil() { + x.AllowedCapabilities = nil + } else { + yyv9 := &x.AllowedCapabilities + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv9), d) + } + } + case "volumes": + if r.TryDecodeAsNil() { + x.Volumes = nil + } else { + yyv11 := &x.Volumes + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + h.decSliceFSType((*[]FSType)(yyv11), d) } } case "hostNetwork": @@ -11580,12 +11706,12 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod if r.TryDecodeAsNil() { x.HostPorts = nil } else { - yyv10 := &x.HostPorts - yym11 := z.DecBinary() - _ = yym11 + yyv14 := &x.HostPorts + yym15 := z.DecBinary() + _ = yym15 if false { } else { - h.decSliceHostPortRange((*[]HostPortRange)(yyv10), d) + h.decSliceHostPortRange((*[]HostPortRange)(yyv14), d) } } case "hostPID": @@ -11604,15 +11730,35 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod if r.TryDecodeAsNil() { x.SELinux = SELinuxStrategyOptions{} } else { - yyv14 := &x.SELinux - yyv14.CodecDecodeSelf(d) + yyv18 := &x.SELinux + yyv18.CodecDecodeSelf(d) } case "runAsUser": if r.TryDecodeAsNil() { x.RunAsUser = RunAsUserStrategyOptions{} } else { - yyv15 := &x.RunAsUser - yyv15.CodecDecodeSelf(d) + yyv19 := &x.RunAsUser + yyv19.CodecDecodeSelf(d) + } + case "supplementalGroups": + if r.TryDecodeAsNil() { + x.SupplementalGroups = SupplementalGroupsStrategyOptions{} + } else { + yyv20 := &x.SupplementalGroups + yyv20.CodecDecodeSelf(d) + } + case "fsGroup": + if r.TryDecodeAsNil() { + x.FSGroup = FSGroupStrategyOptions{} + } else { + yyv21 := &x.FSGroup + yyv21.CodecDecodeSelf(d) + } + case "readOnlyRootFilesystem": + if r.TryDecodeAsNil() { + x.ReadOnlyRootFilesystem = false + } else { + x.ReadOnlyRootFilesystem = bool(r.DecodeBool()) } default: z.DecStructFieldNotFound(-1, yys3) @@ -11625,16 +11771,16 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj16 int - var yyb16 bool - var yyhl16 bool = l >= 0 - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + var yyj23 int + var yyb23 bool + var yyhl23 bool = l >= 0 + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11644,35 +11790,79 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.Privileged = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Capabilities = nil + x.DefaultAddCapabilities = nil } else { - yyv18 := &x.Capabilities - yym19 := z.DecBinary() - _ = yym19 + yyv25 := &x.DefaultAddCapabilities + yym26 := z.DecBinary() + _ = yym26 if false { } else { - h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv18), d) + h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv25), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.RequiredDropCapabilities = nil + } else { + yyv27 := &x.RequiredDropCapabilities + yym28 := z.DecBinary() + _ = yym28 + if false { + } else { + h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv27), d) + } + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.AllowedCapabilities = nil + } else { + yyv29 := &x.AllowedCapabilities + yym30 := z.DecBinary() + _ = yym30 + if false { + } else { + h.decSliceapi_Capability((*[]pkg2_api.Capability)(yyv29), d) + } + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11680,21 +11870,21 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.Volumes = nil } else { - yyv20 := &x.Volumes - yym21 := z.DecBinary() - _ = yym21 + yyv31 := &x.Volumes + yym32 := z.DecBinary() + _ = yym32 if false { } else { - h.decSliceFSType((*[]FSType)(yyv20), d) + h.decSliceFSType((*[]FSType)(yyv31), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11704,13 +11894,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostNetwork = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11718,21 +11908,21 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.HostPorts = nil } else { - yyv23 := &x.HostPorts - yym24 := z.DecBinary() - _ = yym24 + yyv34 := &x.HostPorts + yym35 := z.DecBinary() + _ = yym35 if false { } else { - h.decSliceHostPortRange((*[]HostPortRange)(yyv23), d) + h.decSliceHostPortRange((*[]HostPortRange)(yyv34), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11742,13 +11932,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostPID = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11758,13 +11948,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostIPC = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11772,16 +11962,16 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.SELinux = SELinuxStrategyOptions{} } else { - yyv27 := &x.SELinux - yyv27.CodecDecodeSelf(d) + yyv38 := &x.SELinux + yyv38.CodecDecodeSelf(d) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -11789,21 +11979,71 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.RunAsUser = RunAsUserStrategyOptions{} } else { - yyv28 := &x.RunAsUser - yyv28.CodecDecodeSelf(d) + yyv39 := &x.RunAsUser + yyv39.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.SupplementalGroups = SupplementalGroupsStrategyOptions{} + } else { + yyv40 := &x.SupplementalGroups + yyv40.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FSGroup = FSGroupStrategyOptions{} + } else { + yyv41 := &x.FSGroup + yyv41.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ReadOnlyRootFilesystem = false + } else { + x.ReadOnlyRootFilesystem = bool(r.DecodeBool()) } for { - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj16-1, "") + z.DecStructFieldNotFound(yyj23-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -12720,6 +12960,512 @@ func (x *RunAsUserStrategy) CodecDecodeSelf(d *codec1978.Decoder) { } } +func (x *FSGroupStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Rule != "" + yyq2[1] = len(x.Ranges) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + x.Rule.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rule")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Rule.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Ranges == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("ranges")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Ranges == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *FSGroupStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *FSGroupStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "rule": + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = FSGroupStrategyType(r.DecodeString()) + } + case "ranges": + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv5 := &x.Ranges + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv5), d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *FSGroupStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = FSGroupStrategyType(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv9 := &x.Ranges + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv9), d) + } + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x FSGroupStrategyType) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *FSGroupStrategyType) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x *SupplementalGroupsStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Rule != "" + yyq2[1] = len(x.Ranges) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + x.Rule.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rule")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Rule.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Ranges == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("ranges")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Ranges == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *SupplementalGroupsStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *SupplementalGroupsStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "rule": + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = SupplementalGroupsStrategyType(r.DecodeString()) + } + case "ranges": + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv5 := &x.Ranges + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv5), d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *SupplementalGroupsStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = SupplementalGroupsStrategyType(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv9 := &x.Ranges + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv9), d) + } + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x SupplementalGroupsStrategyType) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *SupplementalGroupsStrategyType) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *PodSecurityPolicyList) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -14990,7 +15736,7 @@ func (x codecSelfer1234) decSlicePodSecurityPolicy(v *[]PodSecurityPolicy, d *co yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 400) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 536) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/pkg/apis/extensions/v1beta1/conversion_generated.go b/pkg/apis/extensions/v1beta1/conversion_generated.go index 3e61862544b..4b8506a9d52 100644 --- a/pkg/apis/extensions/v1beta1/conversion_generated.go +++ b/pkg/apis/extensions/v1beta1/conversion_generated.go @@ -62,6 +62,8 @@ func init() { Convert_extensions_DeploymentStatus_To_v1beta1_DeploymentStatus, Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy, Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy, + Convert_v1beta1_FSGroupStrategyOptions_To_extensions_FSGroupStrategyOptions, + Convert_extensions_FSGroupStrategyOptions_To_v1beta1_FSGroupStrategyOptions, Convert_v1beta1_HTTPIngressPath_To_extensions_HTTPIngressPath, Convert_extensions_HTTPIngressPath_To_v1beta1_HTTPIngressPath, Convert_v1beta1_HTTPIngressRuleValue_To_extensions_HTTPIngressRuleValue, @@ -138,6 +140,8 @@ func init() { Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec, Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions, + Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions, Convert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource, Convert_extensions_ThirdPartyResource_To_v1beta1_ThirdPartyResource, Convert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResourceData, @@ -646,6 +650,46 @@ func autoConvert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy(in return nil } +func autoConvert_v1beta1_FSGroupStrategyOptions_To_extensions_FSGroupStrategyOptions(in *FSGroupStrategyOptions, out *extensions.FSGroupStrategyOptions, s conversion.Scope) error { + out.Rule = extensions.FSGroupStrategyType(in.Rule) + if in.Ranges != nil { + in, out := &in.Ranges, &out.Ranges + *out = make([]extensions.IDRange, len(*in)) + for i := range *in { + if err := Convert_v1beta1_IDRange_To_extensions_IDRange(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + +func Convert_v1beta1_FSGroupStrategyOptions_To_extensions_FSGroupStrategyOptions(in *FSGroupStrategyOptions, out *extensions.FSGroupStrategyOptions, s conversion.Scope) error { + return autoConvert_v1beta1_FSGroupStrategyOptions_To_extensions_FSGroupStrategyOptions(in, out, s) +} + +func autoConvert_extensions_FSGroupStrategyOptions_To_v1beta1_FSGroupStrategyOptions(in *extensions.FSGroupStrategyOptions, out *FSGroupStrategyOptions, s conversion.Scope) error { + out.Rule = FSGroupStrategyType(in.Rule) + if in.Ranges != nil { + in, out := &in.Ranges, &out.Ranges + *out = make([]IDRange, len(*in)) + for i := range *in { + if err := Convert_extensions_IDRange_To_v1beta1_IDRange(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + +func Convert_extensions_FSGroupStrategyOptions_To_v1beta1_FSGroupStrategyOptions(in *extensions.FSGroupStrategyOptions, out *FSGroupStrategyOptions, s conversion.Scope) error { + return autoConvert_extensions_FSGroupStrategyOptions_To_v1beta1_FSGroupStrategyOptions(in, out, s) +} + func autoConvert_v1beta1_HTTPIngressPath_To_extensions_HTTPIngressPath(in *HTTPIngressPath, out *extensions.HTTPIngressPath, s conversion.Scope) error { out.Path = in.Path if err := Convert_v1beta1_IngressBackend_To_extensions_IngressBackend(&in.Backend, &out.Backend, s); err != nil { @@ -1602,14 +1646,32 @@ func Convert_extensions_PodSecurityPolicyList_To_v1beta1_PodSecurityPolicyList(i func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySpec(in *PodSecurityPolicySpec, out *extensions.PodSecurityPolicySpec, s conversion.Scope) error { out.Privileged = in.Privileged - if in.Capabilities != nil { - in, out := &in.Capabilities, &out.Capabilities + if in.DefaultAddCapabilities != nil { + in, out := &in.DefaultAddCapabilities, &out.DefaultAddCapabilities *out = make([]api.Capability, len(*in)) for i := range *in { (*out)[i] = api.Capability((*in)[i]) } } else { - out.Capabilities = nil + out.DefaultAddCapabilities = nil + } + if in.RequiredDropCapabilities != nil { + in, out := &in.RequiredDropCapabilities, &out.RequiredDropCapabilities + *out = make([]api.Capability, len(*in)) + for i := range *in { + (*out)[i] = api.Capability((*in)[i]) + } + } else { + out.RequiredDropCapabilities = nil + } + if in.AllowedCapabilities != nil { + in, out := &in.AllowedCapabilities, &out.AllowedCapabilities + *out = make([]api.Capability, len(*in)) + for i := range *in { + (*out)[i] = api.Capability((*in)[i]) + } + } else { + out.AllowedCapabilities = nil } if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes @@ -1640,6 +1702,13 @@ func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySp if err := Convert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrategyOptions(&in.RunAsUser, &out.RunAsUser, s); err != nil { return err } + if err := Convert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil { + return err + } + if err := Convert_v1beta1_FSGroupStrategyOptions_To_extensions_FSGroupStrategyOptions(&in.FSGroup, &out.FSGroup, s); err != nil { + return err + } + out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem return nil } @@ -1649,14 +1718,32 @@ func Convert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySpec(i func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySpec(in *extensions.PodSecurityPolicySpec, out *PodSecurityPolicySpec, s conversion.Scope) error { out.Privileged = in.Privileged - if in.Capabilities != nil { - in, out := &in.Capabilities, &out.Capabilities + if in.DefaultAddCapabilities != nil { + in, out := &in.DefaultAddCapabilities, &out.DefaultAddCapabilities *out = make([]v1.Capability, len(*in)) for i := range *in { (*out)[i] = v1.Capability((*in)[i]) } } else { - out.Capabilities = nil + out.DefaultAddCapabilities = nil + } + if in.RequiredDropCapabilities != nil { + in, out := &in.RequiredDropCapabilities, &out.RequiredDropCapabilities + *out = make([]v1.Capability, len(*in)) + for i := range *in { + (*out)[i] = v1.Capability((*in)[i]) + } + } else { + out.RequiredDropCapabilities = nil + } + if in.AllowedCapabilities != nil { + in, out := &in.AllowedCapabilities, &out.AllowedCapabilities + *out = make([]v1.Capability, len(*in)) + for i := range *in { + (*out)[i] = v1.Capability((*in)[i]) + } + } else { + out.AllowedCapabilities = nil } if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes @@ -1687,6 +1774,13 @@ func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySp if err := Convert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOptions(&in.RunAsUser, &out.RunAsUser, s); err != nil { return err } + if err := Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(&in.SupplementalGroups, &out.SupplementalGroups, s); err != nil { + return err + } + if err := Convert_extensions_FSGroupStrategyOptions_To_v1beta1_FSGroupStrategyOptions(&in.FSGroup, &out.FSGroup, s); err != nil { + return err + } + out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem return nil } @@ -1987,6 +2081,46 @@ func Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, return autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) } +func autoConvert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions(in *SupplementalGroupsStrategyOptions, out *extensions.SupplementalGroupsStrategyOptions, s conversion.Scope) error { + out.Rule = extensions.SupplementalGroupsStrategyType(in.Rule) + if in.Ranges != nil { + in, out := &in.Ranges, &out.Ranges + *out = make([]extensions.IDRange, len(*in)) + for i := range *in { + if err := Convert_v1beta1_IDRange_To_extensions_IDRange(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + +func Convert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions(in *SupplementalGroupsStrategyOptions, out *extensions.SupplementalGroupsStrategyOptions, s conversion.Scope) error { + return autoConvert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions(in, out, s) +} + +func autoConvert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(in *extensions.SupplementalGroupsStrategyOptions, out *SupplementalGroupsStrategyOptions, s conversion.Scope) error { + out.Rule = SupplementalGroupsStrategyType(in.Rule) + if in.Ranges != nil { + in, out := &in.Ranges, &out.Ranges + *out = make([]IDRange, len(*in)) + for i := range *in { + if err := Convert_extensions_IDRange_To_v1beta1_IDRange(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + +func Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(in *extensions.SupplementalGroupsStrategyOptions, out *SupplementalGroupsStrategyOptions, s conversion.Scope) error { + return autoConvert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions(in, out, s) +} + func autoConvert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource(in *ThirdPartyResource, out *extensions.ThirdPartyResource, s conversion.Scope) error { if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { return err diff --git a/pkg/apis/extensions/v1beta1/deep_copy_generated.go b/pkg/apis/extensions/v1beta1/deep_copy_generated.go index 4ffbb088917..7b359190e7e 100644 --- a/pkg/apis/extensions/v1beta1/deep_copy_generated.go +++ b/pkg/apis/extensions/v1beta1/deep_copy_generated.go @@ -48,6 +48,7 @@ func init() { DeepCopy_v1beta1_DeploymentStatus, DeepCopy_v1beta1_DeploymentStrategy, DeepCopy_v1beta1_ExportOptions, + DeepCopy_v1beta1_FSGroupStrategyOptions, DeepCopy_v1beta1_HTTPIngressPath, DeepCopy_v1beta1_HTTPIngressRuleValue, DeepCopy_v1beta1_HorizontalPodAutoscaler, @@ -88,6 +89,7 @@ func init() { DeepCopy_v1beta1_ScaleSpec, DeepCopy_v1beta1_ScaleStatus, DeepCopy_v1beta1_SubresourceReference, + DeepCopy_v1beta1_SupplementalGroupsStrategyOptions, DeepCopy_v1beta1_ThirdPartyResource, DeepCopy_v1beta1_ThirdPartyResourceData, DeepCopy_v1beta1_ThirdPartyResourceDataList, @@ -347,6 +349,22 @@ func DeepCopy_v1beta1_ExportOptions(in ExportOptions, out *ExportOptions, c *con return nil } +func DeepCopy_v1beta1_FSGroupStrategyOptions(in FSGroupStrategyOptions, out *FSGroupStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.Ranges != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_IDRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + func DeepCopy_v1beta1_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error { out.Path = in.Path if err := DeepCopy_v1beta1_IngressBackend(in.Backend, &out.Backend, c); err != nil { @@ -814,14 +832,32 @@ func DeepCopy_v1beta1_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSe func DeepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error { out.Privileged = in.Privileged - if in.Capabilities != nil { - in, out := in.Capabilities, &out.Capabilities + if in.DefaultAddCapabilities != nil { + in, out := in.DefaultAddCapabilities, &out.DefaultAddCapabilities *out = make([]v1.Capability, len(in)) for i := range in { (*out)[i] = in[i] } } else { - out.Capabilities = nil + out.DefaultAddCapabilities = nil + } + if in.RequiredDropCapabilities != nil { + in, out := in.RequiredDropCapabilities, &out.RequiredDropCapabilities + *out = make([]v1.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.RequiredDropCapabilities = nil + } + if in.AllowedCapabilities != nil { + in, out := in.AllowedCapabilities, &out.AllowedCapabilities + *out = make([]v1.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.AllowedCapabilities = nil } if in.Volumes != nil { in, out := in.Volumes, &out.Volumes @@ -852,6 +888,13 @@ func DeepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSe if err := DeepCopy_v1beta1_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil { return err } + if err := DeepCopy_v1beta1_SupplementalGroupsStrategyOptions(in.SupplementalGroups, &out.SupplementalGroups, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_FSGroupStrategyOptions(in.FSGroup, &out.FSGroup, c); err != nil { + return err + } + out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem return nil } @@ -1030,6 +1073,22 @@ func DeepCopy_v1beta1_SubresourceReference(in SubresourceReference, out *Subreso return nil } +func DeepCopy_v1beta1_SupplementalGroupsStrategyOptions(in SupplementalGroupsStrategyOptions, out *SupplementalGroupsStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.Ranges != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_IDRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + func DeepCopy_v1beta1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err diff --git a/pkg/apis/extensions/v1beta1/generated.pb.go b/pkg/apis/extensions/v1beta1/generated.pb.go index 7e231b95e4f..70ea18bd5cd 100644 --- a/pkg/apis/extensions/v1beta1/generated.pb.go +++ b/pkg/apis/extensions/v1beta1/generated.pb.go @@ -42,6 +42,7 @@ limitations under the License. DeploymentStatus DeploymentStrategy ExportOptions + FSGroupStrategyOptions HTTPIngressPath HTTPIngressRuleValue HorizontalPodAutoscaler @@ -82,6 +83,7 @@ limitations under the License. ScaleSpec ScaleStatus SubresourceReference + SupplementalGroupsStrategyOptions ThirdPartyResource ThirdPartyResourceData ThirdPartyResourceDataList @@ -173,6 +175,10 @@ func (m *ExportOptions) Reset() { *m = ExportOptions{} } func (m *ExportOptions) String() string { return proto.CompactTextString(m) } func (*ExportOptions) ProtoMessage() {} +func (m *FSGroupStrategyOptions) Reset() { *m = FSGroupStrategyOptions{} } +func (m *FSGroupStrategyOptions) String() string { return proto.CompactTextString(m) } +func (*FSGroupStrategyOptions) ProtoMessage() {} + func (m *HTTPIngressPath) Reset() { *m = HTTPIngressPath{} } func (m *HTTPIngressPath) String() string { return proto.CompactTextString(m) } func (*HTTPIngressPath) ProtoMessage() {} @@ -333,6 +339,10 @@ func (m *SubresourceReference) Reset() { *m = SubresourceReference{} } func (m *SubresourceReference) String() string { return proto.CompactTextString(m) } func (*SubresourceReference) ProtoMessage() {} +func (m *SupplementalGroupsStrategyOptions) Reset() { *m = SupplementalGroupsStrategyOptions{} } +func (m *SupplementalGroupsStrategyOptions) String() string { return proto.CompactTextString(m) } +func (*SupplementalGroupsStrategyOptions) ProtoMessage() {} + func (m *ThirdPartyResource) Reset() { *m = ThirdPartyResource{} } func (m *ThirdPartyResource) String() string { return proto.CompactTextString(m) } func (*ThirdPartyResource) ProtoMessage() {} @@ -367,6 +377,7 @@ func init() { proto.RegisterType((*DeploymentStatus)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStatus") proto.RegisterType((*DeploymentStrategy)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.DeploymentStrategy") proto.RegisterType((*ExportOptions)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ExportOptions") + proto.RegisterType((*FSGroupStrategyOptions)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.FSGroupStrategyOptions") proto.RegisterType((*HTTPIngressPath)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressPath") proto.RegisterType((*HTTPIngressRuleValue)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.HTTPIngressRuleValue") proto.RegisterType((*HorizontalPodAutoscaler)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.HorizontalPodAutoscaler") @@ -407,6 +418,7 @@ func init() { proto.RegisterType((*ScaleSpec)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec") proto.RegisterType((*ScaleStatus)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus") proto.RegisterType((*SubresourceReference)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.SubresourceReference") + proto.RegisterType((*SupplementalGroupsStrategyOptions)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.SupplementalGroupsStrategyOptions") proto.RegisterType((*ThirdPartyResource)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ThirdPartyResource") proto.RegisterType((*ThirdPartyResourceData)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ThirdPartyResourceData") proto.RegisterType((*ThirdPartyResourceDataList)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ThirdPartyResourceDataList") @@ -1019,6 +1031,40 @@ func (m *ExportOptions) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *FSGroupStrategyOptions) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *FSGroupStrategyOptions) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Rule))) + i += copy(data[i:], m.Rule) + if len(m.Ranges) > 0 { + for _, msg := range m.Ranges { + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *HTTPIngressPath) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -2054,8 +2100,8 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { data[i] = 0 } i++ - if len(m.Capabilities) > 0 { - for _, s := range m.Capabilities { + if len(m.DefaultAddCapabilities) > 0 { + for _, s := range m.DefaultAddCapabilities { data[i] = 0x12 i++ l = len(s) @@ -2069,9 +2115,39 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { i += copy(data[i:], s) } } + if len(m.RequiredDropCapabilities) > 0 { + for _, s := range m.RequiredDropCapabilities { + data[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + if len(m.AllowedCapabilities) > 0 { + for _, s := range m.AllowedCapabilities { + data[i] = 0x22 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } if len(m.Volumes) > 0 { for _, s := range m.Volumes { - data[i] = 0x1a + data[i] = 0x2a i++ l = len(s) for l >= 1<<7 { @@ -2084,7 +2160,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { i += copy(data[i:], s) } } - data[i] = 0x20 + data[i] = 0x30 i++ if m.HostNetwork { data[i] = 1 @@ -2094,7 +2170,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { i++ if len(m.HostPorts) > 0 { for _, msg := range m.HostPorts { - data[i] = 0x2a + data[i] = 0x3a i++ i = encodeVarintGenerated(data, i, uint64(msg.Size())) n, err := msg.MarshalTo(data[i:]) @@ -2104,7 +2180,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { i += n } } - data[i] = 0x30 + data[i] = 0x40 i++ if m.HostPID { data[i] = 1 @@ -2112,7 +2188,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { data[i] = 0 } i++ - data[i] = 0x38 + data[i] = 0x48 i++ if m.HostIPC { data[i] = 1 @@ -2120,7 +2196,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { data[i] = 0 } i++ - data[i] = 0x42 + data[i] = 0x52 i++ i = encodeVarintGenerated(data, i, uint64(m.SELinux.Size())) n49, err := m.SELinux.MarshalTo(data[i:]) @@ -2128,7 +2204,7 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { return 0, err } i += n49 - data[i] = 0x4a + data[i] = 0x5a i++ i = encodeVarintGenerated(data, i, uint64(m.RunAsUser.Size())) n50, err := m.RunAsUser.MarshalTo(data[i:]) @@ -2136,6 +2212,30 @@ func (m *PodSecurityPolicySpec) MarshalTo(data []byte) (int, error) { return 0, err } i += n50 + data[i] = 0x62 + i++ + i = encodeVarintGenerated(data, i, uint64(m.SupplementalGroups.Size())) + n51, err := m.SupplementalGroups.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n51 + data[i] = 0x6a + i++ + i = encodeVarintGenerated(data, i, uint64(m.FSGroup.Size())) + n52, err := m.FSGroup.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n52 + data[i] = 0x70 + i++ + if m.ReadOnlyRootFilesystem { + data[i] = 1 + } else { + data[i] = 0 + } + i++ return i, nil } @@ -2157,27 +2257,27 @@ func (m *ReplicaSet) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n51, err := m.ObjectMeta.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n51 - data[i] = 0x12 - i++ - i = encodeVarintGenerated(data, i, uint64(m.Spec.Size())) - n52, err := m.Spec.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n52 - data[i] = 0x1a - i++ - i = encodeVarintGenerated(data, i, uint64(m.Status.Size())) - n53, err := m.Status.MarshalTo(data[i:]) + n53, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } i += n53 + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(m.Spec.Size())) + n54, err := m.Spec.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n54 + data[i] = 0x1a + i++ + i = encodeVarintGenerated(data, i, uint64(m.Status.Size())) + n55, err := m.Status.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n55 return i, nil } @@ -2199,11 +2299,11 @@ func (m *ReplicaSetList) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) - n54, err := m.ListMeta.MarshalTo(data[i:]) + n56, err := m.ListMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n54 + i += n56 if len(m.Items) > 0 { for _, msg := range m.Items { data[i] = 0x12 @@ -2243,20 +2343,20 @@ func (m *ReplicaSetSpec) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintGenerated(data, i, uint64(m.Selector.Size())) - n55, err := m.Selector.MarshalTo(data[i:]) + n57, err := m.Selector.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n55 + i += n57 } data[i] = 0x1a i++ i = encodeVarintGenerated(data, i, uint64(m.Template.Size())) - n56, err := m.Template.MarshalTo(data[i:]) + n58, err := m.Template.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n56 + i += n58 return i, nil } @@ -2345,21 +2445,21 @@ func (m *RollingUpdateDeployment) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.MaxUnavailable.Size())) - n57, err := m.MaxUnavailable.MarshalTo(data[i:]) + n59, err := m.MaxUnavailable.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n57 + i += n59 } if m.MaxSurge != nil { data[i] = 0x12 i++ i = encodeVarintGenerated(data, i, uint64(m.MaxSurge.Size())) - n58, err := m.MaxSurge.MarshalTo(data[i:]) + n60, err := m.MaxSurge.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n58 + i += n60 } return i, nil } @@ -2421,11 +2521,11 @@ func (m *SELinuxStrategyOptions) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintGenerated(data, i, uint64(m.SELinuxOptions.Size())) - n59, err := m.SELinuxOptions.MarshalTo(data[i:]) + n61, err := m.SELinuxOptions.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n59 + i += n61 } return i, nil } @@ -2448,27 +2548,27 @@ func (m *Scale) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n60, err := m.ObjectMeta.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n60 - data[i] = 0x12 - i++ - i = encodeVarintGenerated(data, i, uint64(m.Spec.Size())) - n61, err := m.Spec.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n61 - data[i] = 0x1a - i++ - i = encodeVarintGenerated(data, i, uint64(m.Status.Size())) - n62, err := m.Status.MarshalTo(data[i:]) + n62, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } i += n62 + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(m.Spec.Size())) + n63, err := m.Spec.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n63 + data[i] = 0x1a + i++ + i = encodeVarintGenerated(data, i, uint64(m.Status.Size())) + n64, err := m.Status.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n64 return i, nil } @@ -2569,6 +2669,40 @@ func (m *SubresourceReference) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *SupplementalGroupsStrategyOptions) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SupplementalGroupsStrategyOptions) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Rule))) + i += copy(data[i:], m.Rule) + if len(m.Ranges) > 0 { + for _, msg := range m.Ranges { + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *ThirdPartyResource) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -2587,11 +2721,11 @@ func (m *ThirdPartyResource) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n63, err := m.ObjectMeta.MarshalTo(data[i:]) + n65, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n63 + i += n65 data[i] = 0x12 i++ i = encodeVarintGenerated(data, i, uint64(len(m.Description))) @@ -2629,11 +2763,11 @@ func (m *ThirdPartyResourceData) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n64, err := m.ObjectMeta.MarshalTo(data[i:]) + n66, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n64 + i += n66 if m.Data != nil { data[i] = 0x12 i++ @@ -2661,11 +2795,11 @@ func (m *ThirdPartyResourceDataList) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) - n65, err := m.ListMeta.MarshalTo(data[i:]) + n67, err := m.ListMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n65 + i += n67 if len(m.Items) > 0 { for _, msg := range m.Items { data[i] = 0x12 @@ -2699,11 +2833,11 @@ func (m *ThirdPartyResourceList) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) - n66, err := m.ListMeta.MarshalTo(data[i:]) + n68, err := m.ListMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n66 + i += n68 if len(m.Items) > 0 { for _, msg := range m.Items { data[i] = 0x12 @@ -2953,6 +3087,20 @@ func (m *ExportOptions) Size() (n int) { return n } +func (m *FSGroupStrategyOptions) Size() (n int) { + var l int + _ = l + l = len(m.Rule) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Ranges) > 0 { + for _, e := range m.Ranges { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *HTTPIngressPath) Size() (n int) { var l int _ = l @@ -3322,8 +3470,20 @@ func (m *PodSecurityPolicySpec) Size() (n int) { var l int _ = l n += 2 - if len(m.Capabilities) > 0 { - for _, s := range m.Capabilities { + if len(m.DefaultAddCapabilities) > 0 { + for _, s := range m.DefaultAddCapabilities { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.RequiredDropCapabilities) > 0 { + for _, s := range m.RequiredDropCapabilities { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.AllowedCapabilities) > 0 { + for _, s := range m.AllowedCapabilities { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } @@ -3347,6 +3507,11 @@ func (m *PodSecurityPolicySpec) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = m.RunAsUser.Size() n += 1 + l + sovGenerated(uint64(l)) + l = m.SupplementalGroups.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.FSGroup.Size() + n += 1 + l + sovGenerated(uint64(l)) + n += 2 return n } @@ -3503,6 +3668,20 @@ func (m *SubresourceReference) Size() (n int) { return n } +func (m *SupplementalGroupsStrategyOptions) Size() (n int) { + var l int + _ = l + l = len(m.Rule) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Ranges) > 0 { + for _, e := range m.Ranges { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *ThirdPartyResource) Size() (n int) { var l int _ = l @@ -5644,6 +5823,116 @@ func (m *ExportOptions) Unmarshal(data []byte) error { } return nil } +func (m *FSGroupStrategyOptions) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FSGroupStrategyOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FSGroupStrategyOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rule", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Rule = FSGroupStrategyType(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ranges", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ranges = append(m.Ranges, IDRange{}) + if err := m.Ranges[len(m.Ranges)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *HTTPIngressPath) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -9112,7 +9401,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { m.Privileged = bool(v != 0) case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Capabilities", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DefaultAddCapabilities", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -9137,9 +9426,67 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Capabilities = append(m.Capabilities, k8s_io_kubernetes_pkg_api_v1.Capability(data[iNdEx:postIndex])) + m.DefaultAddCapabilities = append(m.DefaultAddCapabilities, k8s_io_kubernetes_pkg_api_v1.Capability(data[iNdEx:postIndex])) iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequiredDropCapabilities", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequiredDropCapabilities = append(m.RequiredDropCapabilities, k8s_io_kubernetes_pkg_api_v1.Capability(data[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedCapabilities", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedCapabilities = append(m.AllowedCapabilities, k8s_io_kubernetes_pkg_api_v1.Capability(data[iNdEx:postIndex])) + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Volumes", wireType) } @@ -9168,7 +9515,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { } m.Volumes = append(m.Volumes, FSType(data[iNdEx:postIndex])) iNdEx = postIndex - case 4: + case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostNetwork", wireType) } @@ -9188,7 +9535,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { } } m.HostNetwork = bool(v != 0) - case 5: + case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field HostPorts", wireType) } @@ -9219,7 +9566,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 6: + case 8: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostPID", wireType) } @@ -9239,7 +9586,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { } } m.HostPID = bool(v != 0) - case 7: + case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostIPC", wireType) } @@ -9259,7 +9606,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { } } m.HostIPC = bool(v != 0) - case 8: + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SELinux", wireType) } @@ -9289,7 +9636,7 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 9: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RunAsUser", wireType) } @@ -9319,6 +9666,86 @@ func (m *PodSecurityPolicySpec) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SupplementalGroups", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SupplementalGroups.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FSGroup", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.FSGroup.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadOnlyRootFilesystem", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.ReadOnlyRootFilesystem = bool(v != 0) default: iNdEx = preIndex skippy, err := skipGenerated(data[iNdEx:]) @@ -10872,6 +11299,116 @@ func (m *SubresourceReference) Unmarshal(data []byte) error { } return nil } +func (m *SupplementalGroupsStrategyOptions) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SupplementalGroupsStrategyOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SupplementalGroupsStrategyOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rule", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Rule = SupplementalGroupsStrategyType(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ranges", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ranges = append(m.Ranges, IDRange{}) + if err := m.Ranges[len(m.Ranges)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ThirdPartyResource) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 diff --git a/pkg/apis/extensions/v1beta1/generated.proto b/pkg/apis/extensions/v1beta1/generated.proto index e8af4854ccd..a16b98b2fd5 100644 --- a/pkg/apis/extensions/v1beta1/generated.proto +++ b/pkg/apis/extensions/v1beta1/generated.proto @@ -235,6 +235,16 @@ message ExportOptions { optional bool exact = 2; } +// FSGroupStrategyOptions defines the strategy type and options used to create the strategy. +message FSGroupStrategyOptions { + // Rule is the strategy that will dictate what FSGroup is used in the SecurityContext. + optional string rule = 1; + + // Ranges are the allowed ranges of fs groups. If you would like to force a single + // fs group then supply a single range with the same start and end. + repeated IDRange ranges = 2; +} + // HTTPIngressPath associates a path regex with a backend. Incoming urls matching // the path are forwarded to the backend. message HTTPIngressPath { @@ -641,30 +651,54 @@ message PodSecurityPolicySpec { // privileged determines if a pod can request to be run as privileged. optional bool privileged = 1; - // capabilities is a list of capabilities that can be added. - repeated string capabilities = 2; + // DefaultAddCapabilities is the default set of capabilities that will be added to the container + // unless the pod spec specifically drops the capability. You may not list a capabiility in both + // DefaultAddCapabilities and RequiredDropCapabilities. + repeated string defaultAddCapabilities = 2; + + // RequiredDropCapabilities are the capabilities that will be dropped from the container. These + // are required to be dropped and cannot be added. + repeated string requiredDropCapabilities = 3; + + // AllowedCapabilities is a list of capabilities that can be requested to add to the container. + // Capabilities in this field may be added at the pod author's discretion. + // You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities. + repeated string allowedCapabilities = 4; // volumes is a white list of allowed volume plugins. Empty indicates that all plugins // may be used. - repeated string volumes = 3; + repeated string volumes = 5; // hostNetwork determines if the policy allows the use of HostNetwork in the pod spec. - optional bool hostNetwork = 4; + optional bool hostNetwork = 6; // hostPorts determines which host port ranges are allowed to be exposed. - repeated HostPortRange hostPorts = 5; + repeated HostPortRange hostPorts = 7; // hostPID determines if the policy allows the use of HostPID in the pod spec. - optional bool hostPID = 6; + optional bool hostPID = 8; // hostIPC determines if the policy allows the use of HostIPC in the pod spec. - optional bool hostIPC = 7; + optional bool hostIPC = 9; // seLinux is the strategy that will dictate the allowable labels that may be set. - optional SELinuxStrategyOptions seLinux = 8; + optional SELinuxStrategyOptions seLinux = 10; // runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set. - optional RunAsUserStrategyOptions runAsUser = 9; + optional RunAsUserStrategyOptions runAsUser = 11; + + // SupplementalGroups is the strategy that will dictate what supplemental groups are used by the SecurityContext. + optional SupplementalGroupsStrategyOptions supplementalGroups = 12; + + // FSGroup is the strategy that will dictate what fs group is used by the SecurityContext. + optional FSGroupStrategyOptions fsGroup = 13; + + // ReadOnlyRootFilesystem when set to true will force containers to run with a read only root file + // system. If the container specifically requests to run with a non-read only root file system + // the PSP should deny the pod. + // If set to false the container may run with a read only root file system if it wishes but it + // will not be forced to. + optional bool readOnlyRootFilesystem = 14; } // ReplicaSet represents the configuration of a ReplicaSet. @@ -836,6 +870,16 @@ message SubresourceReference { optional string subresource = 4; } +// SupplementalGroupsStrategyOptions defines the strategy type and options used to create the strategy. +message SupplementalGroupsStrategyOptions { + // Rule is the strategy that will dictate what supplemental groups is used in the SecurityContext. + optional string rule = 1; + + // Ranges are the allowed ranges of supplemental groups. If you would like to force a single + // supplemental group then supply a single range with the same start and end. + repeated IDRange ranges = 2; +} + // A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource // types to the API. It consists of one or more Versions of the api. message ThirdPartyResource { diff --git a/pkg/apis/extensions/v1beta1/types.generated.go b/pkg/apis/extensions/v1beta1/types.generated.go index 7f82d21e7a8..0c5b16fbf53 100644 --- a/pkg/apis/extensions/v1beta1/types.generated.go +++ b/pkg/apis/extensions/v1beta1/types.generated.go @@ -16765,23 +16765,24 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [9]bool + var yyq2 [14]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Privileged != false - yyq2[1] = len(x.Capabilities) != 0 - yyq2[2] = len(x.Volumes) != 0 - yyq2[3] = x.HostNetwork != false - yyq2[4] = len(x.HostPorts) != 0 - yyq2[5] = x.HostPID != false - yyq2[6] = x.HostIPC != false - yyq2[7] = true - yyq2[8] = true + yyq2[1] = len(x.DefaultAddCapabilities) != 0 + yyq2[2] = len(x.RequiredDropCapabilities) != 0 + yyq2[3] = len(x.AllowedCapabilities) != 0 + yyq2[4] = len(x.Volumes) != 0 + yyq2[5] = x.HostNetwork != false + yyq2[6] = len(x.HostPorts) != 0 + yyq2[7] = x.HostPID != false + yyq2[8] = x.HostIPC != false + yyq2[13] = x.ReadOnlyRootFilesystem != false var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(9) + r.EncodeArrayStart(14) } else { - yynn2 = 0 + yynn2 = 4 for _, b := range yyq2 { if b { yynn2++ @@ -16818,14 +16819,14 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - if x.Capabilities == nil { + if x.DefaultAddCapabilities == nil { r.EncodeNil() } else { yym7 := z.EncBinary() _ = yym7 if false { } else { - h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.Capabilities), e) + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.DefaultAddCapabilities), e) } } } else { @@ -16834,16 +16835,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("capabilities")) + r.EncodeString(codecSelferC_UTF81234, string("defaultAddCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Capabilities == nil { + if x.DefaultAddCapabilities == nil { r.EncodeNil() } else { yym8 := z.EncBinary() _ = yym8 if false { } else { - h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.Capabilities), e) + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.DefaultAddCapabilities), e) } } } @@ -16851,14 +16852,14 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[2] { - if x.Volumes == nil { + if x.RequiredDropCapabilities == nil { r.EncodeNil() } else { yym10 := z.EncBinary() _ = yym10 if false { } else { - h.encSliceFSType(([]FSType)(x.Volumes), e) + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.RequiredDropCapabilities), e) } } } else { @@ -16867,16 +16868,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("volumes")) + r.EncodeString(codecSelferC_UTF81234, string("requiredDropCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Volumes == nil { + if x.RequiredDropCapabilities == nil { r.EncodeNil() } else { yym11 := z.EncBinary() _ = yym11 if false { } else { - h.encSliceFSType(([]FSType)(x.Volumes), e) + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.RequiredDropCapabilities), e) } } } @@ -16884,39 +16885,47 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[3] { - yym13 := z.EncBinary() - _ = yym13 - if false { + if x.AllowedCapabilities == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(x.HostNetwork)) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.AllowedCapabilities), e) + } } } else { - r.EncodeBool(false) + r.EncodeNil() } } else { if yyq2[3] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostNetwork")) + r.EncodeString(codecSelferC_UTF81234, string("allowedCapabilities")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym14 := z.EncBinary() - _ = yym14 - if false { + if x.AllowedCapabilities == nil { + r.EncodeNil() } else { - r.EncodeBool(bool(x.HostNetwork)) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + h.encSlicev1_Capability(([]pkg2_v1.Capability)(x.AllowedCapabilities), e) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[4] { - if x.HostPorts == nil { + if x.Volumes == nil { r.EncodeNil() } else { yym16 := z.EncBinary() _ = yym16 if false { } else { - h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + h.encSliceFSType(([]FSType)(x.Volumes), e) } } } else { @@ -16925,16 +16934,16 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[4] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostPorts")) + r.EncodeString(codecSelferC_UTF81234, string("volumes")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.HostPorts == nil { + if x.Volumes == nil { r.EncodeNil() } else { yym17 := z.EncBinary() _ = yym17 if false { } else { - h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + h.encSliceFSType(([]FSType)(x.Volumes), e) } } } @@ -16946,7 +16955,7 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym19 if false { } else { - r.EncodeBool(bool(x.HostPID)) + r.EncodeBool(bool(x.HostNetwork)) } } else { r.EncodeBool(false) @@ -16954,11 +16963,69 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[5] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("hostPID")) + r.EncodeString(codecSelferC_UTF81234, string("hostNetwork")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym20 := z.EncBinary() _ = yym20 if false { + } else { + r.EncodeBool(bool(x.HostNetwork)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[6] { + if x.HostPorts == nil { + r.EncodeNil() + } else { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[6] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("hostPorts")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.HostPorts == nil { + r.EncodeNil() + } else { + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + h.encSliceHostPortRange(([]HostPortRange)(x.HostPorts), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[7] { + yym25 := z.EncBinary() + _ = yym25 + if false { + } else { + r.EncodeBool(bool(x.HostPID)) + } + } else { + r.EncodeBool(false) + } + } else { + if yyq2[7] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("hostPID")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym26 := z.EncBinary() + _ = yym26 + if false { } else { r.EncodeBool(bool(x.HostPID)) } @@ -16966,9 +17033,9 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[6] { - yym22 := z.EncBinary() - _ = yym22 + if yyq2[8] { + yym28 := z.EncBinary() + _ = yym28 if false { } else { r.EncodeBool(bool(x.HostIPC)) @@ -16977,12 +17044,12 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeBool(false) } } else { - if yyq2[6] { + if yyq2[8] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("hostIPC")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym23 := z.EncBinary() - _ = yym23 + yym29 := z.EncBinary() + _ = yym29 if false { } else { r.EncodeBool(bool(x.HostIPC)) @@ -16991,36 +17058,71 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[7] { - yy25 := &x.SELinux - yy25.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } + yy31 := &x.SELinux + yy31.CodecEncodeSelf(e) } else { - if yyq2[7] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("seLinux")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy27 := &x.SELinux - yy27.CodecEncodeSelf(e) - } + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("seLinux")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy33 := &x.SELinux + yy33.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[8] { - yy30 := &x.RunAsUser - yy30.CodecEncodeSelf(e) + yy36 := &x.RunAsUser + yy36.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("runAsUser")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy38 := &x.RunAsUser + yy38.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy41 := &x.SupplementalGroups + yy41.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("supplementalGroups")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy43 := &x.SupplementalGroups + yy43.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy46 := &x.FSGroup + yy46.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("fsGroup")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy48 := &x.FSGroup + yy48.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[13] { + yym51 := z.EncBinary() + _ = yym51 + if false { + } else { + r.EncodeBool(bool(x.ReadOnlyRootFilesystem)) + } } else { - r.EncodeNil() + r.EncodeBool(false) } } else { - if yyq2[8] { + if yyq2[13] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("runAsUser")) + r.EncodeString(codecSelferC_UTF81234, string("readOnlyRootFilesystem")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy32 := &x.RunAsUser - yy32.CodecEncodeSelf(e) + yym52 := z.EncBinary() + _ = yym52 + if false { + } else { + r.EncodeBool(bool(x.ReadOnlyRootFilesystem)) + } } } if yyr2 || yy2arr2 { @@ -17090,11 +17192,11 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod } else { x.Privileged = bool(r.DecodeBool()) } - case "capabilities": + case "defaultAddCapabilities": if r.TryDecodeAsNil() { - x.Capabilities = nil + x.DefaultAddCapabilities = nil } else { - yyv5 := &x.Capabilities + yyv5 := &x.DefaultAddCapabilities yym6 := z.DecBinary() _ = yym6 if false { @@ -17102,16 +17204,40 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv5), d) } } - case "volumes": + case "requiredDropCapabilities": if r.TryDecodeAsNil() { - x.Volumes = nil + x.RequiredDropCapabilities = nil } else { - yyv7 := &x.Volumes + yyv7 := &x.RequiredDropCapabilities yym8 := z.DecBinary() _ = yym8 if false { } else { - h.decSliceFSType((*[]FSType)(yyv7), d) + h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv7), d) + } + } + case "allowedCapabilities": + if r.TryDecodeAsNil() { + x.AllowedCapabilities = nil + } else { + yyv9 := &x.AllowedCapabilities + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv9), d) + } + } + case "volumes": + if r.TryDecodeAsNil() { + x.Volumes = nil + } else { + yyv11 := &x.Volumes + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + h.decSliceFSType((*[]FSType)(yyv11), d) } } case "hostNetwork": @@ -17124,12 +17250,12 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod if r.TryDecodeAsNil() { x.HostPorts = nil } else { - yyv10 := &x.HostPorts - yym11 := z.DecBinary() - _ = yym11 + yyv14 := &x.HostPorts + yym15 := z.DecBinary() + _ = yym15 if false { } else { - h.decSliceHostPortRange((*[]HostPortRange)(yyv10), d) + h.decSliceHostPortRange((*[]HostPortRange)(yyv14), d) } } case "hostPID": @@ -17148,15 +17274,35 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod if r.TryDecodeAsNil() { x.SELinux = SELinuxStrategyOptions{} } else { - yyv14 := &x.SELinux - yyv14.CodecDecodeSelf(d) + yyv18 := &x.SELinux + yyv18.CodecDecodeSelf(d) } case "runAsUser": if r.TryDecodeAsNil() { x.RunAsUser = RunAsUserStrategyOptions{} } else { - yyv15 := &x.RunAsUser - yyv15.CodecDecodeSelf(d) + yyv19 := &x.RunAsUser + yyv19.CodecDecodeSelf(d) + } + case "supplementalGroups": + if r.TryDecodeAsNil() { + x.SupplementalGroups = SupplementalGroupsStrategyOptions{} + } else { + yyv20 := &x.SupplementalGroups + yyv20.CodecDecodeSelf(d) + } + case "fsGroup": + if r.TryDecodeAsNil() { + x.FSGroup = FSGroupStrategyOptions{} + } else { + yyv21 := &x.FSGroup + yyv21.CodecDecodeSelf(d) + } + case "readOnlyRootFilesystem": + if r.TryDecodeAsNil() { + x.ReadOnlyRootFilesystem = false + } else { + x.ReadOnlyRootFilesystem = bool(r.DecodeBool()) } default: z.DecStructFieldNotFound(-1, yys3) @@ -17169,16 +17315,16 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj16 int - var yyb16 bool - var yyhl16 bool = l >= 0 - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + var yyj23 int + var yyb23 bool + var yyhl23 bool = l >= 0 + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17188,35 +17334,79 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.Privileged = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Capabilities = nil + x.DefaultAddCapabilities = nil } else { - yyv18 := &x.Capabilities - yym19 := z.DecBinary() - _ = yym19 + yyv25 := &x.DefaultAddCapabilities + yym26 := z.DecBinary() + _ = yym26 if false { } else { - h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv18), d) + h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv25), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.RequiredDropCapabilities = nil + } else { + yyv27 := &x.RequiredDropCapabilities + yym28 := z.DecBinary() + _ = yym28 + if false { + } else { + h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv27), d) + } + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.AllowedCapabilities = nil + } else { + yyv29 := &x.AllowedCapabilities + yym30 := z.DecBinary() + _ = yym30 + if false { + } else { + h.decSlicev1_Capability((*[]pkg2_v1.Capability)(yyv29), d) + } + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17224,21 +17414,21 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.Volumes = nil } else { - yyv20 := &x.Volumes - yym21 := z.DecBinary() - _ = yym21 + yyv31 := &x.Volumes + yym32 := z.DecBinary() + _ = yym32 if false { } else { - h.decSliceFSType((*[]FSType)(yyv20), d) + h.decSliceFSType((*[]FSType)(yyv31), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17248,13 +17438,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostNetwork = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17262,21 +17452,21 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.HostPorts = nil } else { - yyv23 := &x.HostPorts - yym24 := z.DecBinary() - _ = yym24 + yyv34 := &x.HostPorts + yym35 := z.DecBinary() + _ = yym35 if false { } else { - h.decSliceHostPortRange((*[]HostPortRange)(yyv23), d) + h.decSliceHostPortRange((*[]HostPortRange)(yyv34), d) } } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17286,13 +17476,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostPID = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17302,13 +17492,13 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } else { x.HostIPC = bool(r.DecodeBool()) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17316,16 +17506,16 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.SELinux = SELinuxStrategyOptions{} } else { - yyv27 := &x.SELinux - yyv27.CodecDecodeSelf(d) + yyv38 := &x.SELinux + yyv38.CodecDecodeSelf(d) } - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17333,21 +17523,71 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.RunAsUser = RunAsUserStrategyOptions{} } else { - yyv28 := &x.RunAsUser - yyv28.CodecDecodeSelf(d) + yyv39 := &x.RunAsUser + yyv39.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.SupplementalGroups = SupplementalGroupsStrategyOptions{} + } else { + yyv40 := &x.SupplementalGroups + yyv40.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FSGroup = FSGroupStrategyOptions{} + } else { + yyv41 := &x.FSGroup + yyv41.CodecDecodeSelf(d) + } + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l + } else { + yyb23 = r.CheckBreak() + } + if yyb23 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ReadOnlyRootFilesystem = false + } else { + x.ReadOnlyRootFilesystem = bool(r.DecodeBool()) } for { - yyj16++ - if yyhl16 { - yyb16 = yyj16 > l + yyj23++ + if yyhl23 { + yyb23 = yyj23 > l } else { - yyb16 = r.CheckBreak() + yyb23 = r.CheckBreak() } - if yyb16 { + if yyb23 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj16-1, "") + z.DecStructFieldNotFound(yyj23-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -18264,6 +18504,512 @@ func (x *RunAsUserStrategy) CodecDecodeSelf(d *codec1978.Decoder) { } } +func (x *FSGroupStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Rule != "" + yyq2[1] = len(x.Ranges) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + x.Rule.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rule")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Rule.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Ranges == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("ranges")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Ranges == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *FSGroupStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *FSGroupStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "rule": + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = FSGroupStrategyType(r.DecodeString()) + } + case "ranges": + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv5 := &x.Ranges + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv5), d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *FSGroupStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = FSGroupStrategyType(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv9 := &x.Ranges + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv9), d) + } + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x FSGroupStrategyType) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *FSGroupStrategyType) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x *SupplementalGroupsStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Rule != "" + yyq2[1] = len(x.Ranges) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + x.Rule.CodecEncodeSelf(e) + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rule")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Rule.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Ranges == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("ranges")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Ranges == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceIDRange(([]IDRange)(x.Ranges), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *SupplementalGroupsStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *SupplementalGroupsStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "rule": + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = SupplementalGroupsStrategyType(r.DecodeString()) + } + case "ranges": + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv5 := &x.Ranges + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv5), d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *SupplementalGroupsStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Rule = "" + } else { + x.Rule = SupplementalGroupsStrategyType(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Ranges = nil + } else { + yyv9 := &x.Ranges + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceIDRange((*[]IDRange)(yyv9), d) + } + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x SupplementalGroupsStrategyType) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *SupplementalGroupsStrategyType) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *PodSecurityPolicyList) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -21010,7 +21756,7 @@ func (x codecSelfer1234) decSlicePodSecurityPolicy(v *[]PodSecurityPolicy, d *co yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 400) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 536) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go b/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go index c6bc14c5507..e39e19cefd6 100644 --- a/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go +++ b/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go @@ -186,6 +186,16 @@ func (ExportOptions) SwaggerDoc() map[string]string { return map_ExportOptions } +var map_FSGroupStrategyOptions = map[string]string{ + "": "FSGroupStrategyOptions defines the strategy type and options used to create the strategy.", + "rule": "Rule is the strategy that will dictate what FSGroup is used in the SecurityContext.", + "ranges": "Ranges are the allowed ranges of fs groups. If you would like to force a single fs group then supply a single range with the same start and end.", +} + +func (FSGroupStrategyOptions) SwaggerDoc() map[string]string { + return map_FSGroupStrategyOptions +} + var map_HTTPIngressPath = map[string]string{ "": "HTTPIngressPath associates a path regex with a backend. Incoming urls matching the path are forwarded to the backend.", "path": "Path is a extended POSIX regex as defined by IEEE Std 1003.1, (i.e this follows the egrep/unix syntax, not the perl syntax) matched against the path of an incoming request. Currently it can contain characters disallowed from the conventional \"path\" part of a URL as defined by RFC 3986. Paths must begin with a '/'. If unspecified, the path defaults to a catch all sending traffic to the backend.", @@ -467,16 +477,21 @@ func (PodSecurityPolicyList) SwaggerDoc() map[string]string { } var map_PodSecurityPolicySpec = map[string]string{ - "": "Pod Security Policy Spec defines the policy enforced.", - "privileged": "privileged determines if a pod can request to be run as privileged.", - "capabilities": "capabilities is a list of capabilities that can be added.", - "volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.", - "hostNetwork": "hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.", - "hostPorts": "hostPorts determines which host port ranges are allowed to be exposed.", - "hostPID": "hostPID determines if the policy allows the use of HostPID in the pod spec.", - "hostIPC": "hostIPC determines if the policy allows the use of HostIPC in the pod spec.", - "seLinux": "seLinux is the strategy that will dictate the allowable labels that may be set.", - "runAsUser": "runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.", + "": "Pod Security Policy Spec defines the policy enforced.", + "privileged": "privileged determines if a pod can request to be run as privileged.", + "defaultAddCapabilities": "DefaultAddCapabilities is the default set of capabilities that will be added to the container unless the pod spec specifically drops the capability. You may not list a capabiility in both DefaultAddCapabilities and RequiredDropCapabilities.", + "requiredDropCapabilities": "RequiredDropCapabilities are the capabilities that will be dropped from the container. These are required to be dropped and cannot be added.", + "allowedCapabilities": "AllowedCapabilities is a list of capabilities that can be requested to add to the container. Capabilities in this field may be added at the pod author's discretion. You must not list a capability in both AllowedCapabilities and RequiredDropCapabilities.", + "volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.", + "hostNetwork": "hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.", + "hostPorts": "hostPorts determines which host port ranges are allowed to be exposed.", + "hostPID": "hostPID determines if the policy allows the use of HostPID in the pod spec.", + "hostIPC": "hostIPC determines if the policy allows the use of HostIPC in the pod spec.", + "seLinux": "seLinux is the strategy that will dictate the allowable labels that may be set.", + "runAsUser": "runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.", + "supplementalGroups": "SupplementalGroups is the strategy that will dictate what supplemental groups are used by the SecurityContext.", + "fsGroup": "FSGroup is the strategy that will dictate what fs group is used by the SecurityContext.", + "readOnlyRootFilesystem": "ReadOnlyRootFilesystem when set to true will force containers to run with a read only root file system. If the container specifically requests to run with a non-read only root file system the PSP should deny the pod. If set to false the container may run with a read only root file system if it wishes but it will not be forced to.", } func (PodSecurityPolicySpec) SwaggerDoc() map[string]string { @@ -615,6 +630,16 @@ func (SubresourceReference) SwaggerDoc() map[string]string { return map_SubresourceReference } +var map_SupplementalGroupsStrategyOptions = map[string]string{ + "": "SupplementalGroupsStrategyOptions defines the strategy type and options used to create the strategy.", + "rule": "Rule is the strategy that will dictate what supplemental groups is used in the SecurityContext.", + "ranges": "Ranges are the allowed ranges of supplemental groups. If you would like to force a single supplemental group then supply a single range with the same start and end.", +} + +func (SupplementalGroupsStrategyOptions) SwaggerDoc() map[string]string { + return map_SupplementalGroupsStrategyOptions +} + var map_ThirdPartyResource = map[string]string{ "": "A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource types to the API. It consists of one or more Versions of the api.", "metadata": "Standard object metadata", diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go index 3ced862a351..9b9f4749a64 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go @@ -27,6 +27,7 @@ type ExtensionsInterface interface { DaemonSetsGetter DeploymentsGetter IngressesGetter + PodSecurityPoliciesGetter ReplicaSetsGetter ScalesGetter ThirdPartyResourcesGetter @@ -49,6 +50,10 @@ func (c *ExtensionsClient) Ingresses(namespace string) IngressInterface { return newIngresses(c, namespace) } +func (c *ExtensionsClient) PodSecurityPolicies() PodSecurityPolicyInterface { + return newPodSecurityPolicies(c) +} + func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { return newReplicaSets(c, namespace) } diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go index 37553041c43..7c2fa08518a 100644 --- a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go @@ -38,6 +38,10 @@ func (c *FakeExtensions) Ingresses(namespace string) unversioned.IngressInterfac return &FakeIngresses{c, namespace} } +func (c *FakeExtensions) PodSecurityPolicies() unversioned.PodSecurityPolicyInterface { + return &FakePodSecurityPolicies{c} +} + func (c *FakeExtensions) ReplicaSets(namespace string) unversioned.ReplicaSetInterface { return &FakeReplicaSets{c, namespace} } diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_podsecuritypolicy.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_podsecuritypolicy.go new file mode 100644 index 00000000000..52b6f3acd16 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_podsecuritypolicy.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePodSecurityPolicies implements PodSecurityPolicyInterface +type FakePodSecurityPolicies struct { + Fake *FakeExtensions +} + +var podsecuritypoliciesResource = unversioned.GroupVersionResource{Group: "extensions", Version: "", Resource: "podsecuritypolicies"} + +func (c *FakePodSecurityPolicies) Create(podSecurityPolicy *extensions.PodSecurityPolicy) (result *extensions.PodSecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction(podsecuritypoliciesResource, podSecurityPolicy), &extensions.PodSecurityPolicy{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.PodSecurityPolicy), err +} + +func (c *FakePodSecurityPolicies) Update(podSecurityPolicy *extensions.PodSecurityPolicy) (result *extensions.PodSecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction(podsecuritypoliciesResource, podSecurityPolicy), &extensions.PodSecurityPolicy{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.PodSecurityPolicy), err +} + +func (c *FakePodSecurityPolicies) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction(podsecuritypoliciesResource, name), &extensions.PodSecurityPolicy{}) + return err +} + +func (c *FakePodSecurityPolicies) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction(podsecuritypoliciesResource, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.PodSecurityPolicyList{}) + return err +} + +func (c *FakePodSecurityPolicies) Get(name string) (result *extensions.PodSecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction(podsecuritypoliciesResource, name), &extensions.PodSecurityPolicy{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.PodSecurityPolicy), err +} + +func (c *FakePodSecurityPolicies) List(opts api.ListOptions) (result *extensions.PodSecurityPolicyList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction(podsecuritypoliciesResource, opts), &extensions.PodSecurityPolicyList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.PodSecurityPolicyList{} + for _, item := range obj.(*extensions.PodSecurityPolicyList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested podSecurityPolicies. +func (c *FakePodSecurityPolicies) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction(podsecuritypoliciesResource, opts)) +} diff --git a/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/podsecuritypolicy.go b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/podsecuritypolicy.go new file mode 100644 index 00000000000..06a7908f467 --- /dev/null +++ b/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/podsecuritypolicy.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodSecurityPoliciesGetter has a method to return a PodSecurityPolicyInterface. +// A group's client should implement this interface. +type PodSecurityPoliciesGetter interface { + PodSecurityPolicies() PodSecurityPolicyInterface +} + +// PodSecurityPolicyInterface has methods to work with PodSecurityPolicy resources. +type PodSecurityPolicyInterface interface { + Create(*extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) + Update(*extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.PodSecurityPolicy, error) + List(opts api.ListOptions) (*extensions.PodSecurityPolicyList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodSecurityPolicyExpansion +} + +// podSecurityPolicies implements PodSecurityPolicyInterface +type podSecurityPolicies struct { + client *ExtensionsClient +} + +// newPodSecurityPolicies returns a PodSecurityPolicies +func newPodSecurityPolicies(c *ExtensionsClient) *podSecurityPolicies { + return &podSecurityPolicies{ + client: c, + } +} + +// Create takes the representation of a podSecurityPolicy and creates it. Returns the server's representation of the podSecurityPolicy, and an error, if there is any. +func (c *podSecurityPolicies) Create(podSecurityPolicy *extensions.PodSecurityPolicy) (result *extensions.PodSecurityPolicy, err error) { + result = &extensions.PodSecurityPolicy{} + err = c.client.Post(). + Resource("podsecuritypolicies"). + Body(podSecurityPolicy). + Do(). + Into(result) + return +} + +// Update takes the representation of a podSecurityPolicy and updates it. Returns the server's representation of the podSecurityPolicy, and an error, if there is any. +func (c *podSecurityPolicies) Update(podSecurityPolicy *extensions.PodSecurityPolicy) (result *extensions.PodSecurityPolicy, err error) { + result = &extensions.PodSecurityPolicy{} + err = c.client.Put(). + Resource("podsecuritypolicies"). + Name(podSecurityPolicy.Name). + Body(podSecurityPolicy). + Do(). + Into(result) + return +} + +// Delete takes name of the podSecurityPolicy and deletes it. Returns an error if one occurs. +func (c *podSecurityPolicies) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("podsecuritypolicies"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *podSecurityPolicies) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("podsecuritypolicies"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the podSecurityPolicy, and returns the corresponding podSecurityPolicy object, and an error if there is any. +func (c *podSecurityPolicies) Get(name string) (result *extensions.PodSecurityPolicy, err error) { + result = &extensions.PodSecurityPolicy{} + err = c.client.Get(). + Resource("podsecuritypolicies"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PodSecurityPolicies that match those selectors. +func (c *podSecurityPolicies) List(opts api.ListOptions) (result *extensions.PodSecurityPolicyList, err error) { + result = &extensions.PodSecurityPolicyList{} + err = c.client.Get(). + Resource("podsecuritypolicies"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested podSecurityPolicies. +func (c *podSecurityPolicies) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("podsecuritypolicies"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} From 65c8a1f66c14a2cb2223d5ab12340fa4b2d88f95 Mon Sep 17 00:00:00 2001 From: Paul Weil Date: Mon, 9 May 2016 10:31:25 -0400 Subject: [PATCH 6/6] default policy --- .../podsecuritypolicies/privileged.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cluster/addons/podsecuritypolicies/privileged.yaml diff --git a/cluster/addons/podsecuritypolicies/privileged.yaml b/cluster/addons/podsecuritypolicies/privileged.yaml new file mode 100644 index 00000000000..345c0797cc6 --- /dev/null +++ b/cluster/addons/podsecuritypolicies/privileged.yaml @@ -0,0 +1,32 @@ +apiVersion: extensions/v1beta1 +kind: PodSecurityPolicy +metadata: + annotations: + kubernetes.io/description: 'privileged allows access to all privileged and host + features and the ability to run as any user, any group, any fsGroup, and with + any SELinux context.' + creationTimestamp: 2016-05-06T19:28:58Z + name: privileged +spec: + privileged: true + defaultAddCapabilities: null + requiredDropCapabilities: null + allowedCapabilities: null + volumes: + - '*' + hostNetwork: true + hostPorts: + - + min: 0 + max: 65535 + hostIPC: true + hostPID: true + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false