mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-15 06:01:50 +00:00
PSP types
This commit is contained in:
@@ -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"`
|
||||
|
@@ -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"`
|
||||
|
@@ -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{}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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"},
|
||||
|
18
pkg/security/doc.go
Normal file
18
pkg/security/doc.go
Normal file
@@ -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
|
149
pkg/security/podsecuritypolicy/capabilities/mustrunas.go
Normal file
149
pkg/security/podsecuritypolicy/capabilities/mustrunas.go
Normal file
@@ -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
|
||||
}
|
387
pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go
Normal file
387
pkg/security/podsecuritypolicy/capabilities/mustrunas_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
30
pkg/security/podsecuritypolicy/capabilities/types.go
Normal file
30
pkg/security/podsecuritypolicy/capabilities/types.go
Normal file
@@ -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
|
||||
}
|
135
pkg/security/podsecuritypolicy/factory.go
Normal file
135
pkg/security/podsecuritypolicy/factory.go
Normal file
@@ -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)
|
||||
}
|
93
pkg/security/podsecuritypolicy/group/mustrunas.go
Normal file
93
pkg/security/podsecuritypolicy/group/mustrunas.go
Normal file
@@ -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
|
||||
}
|
193
pkg/security/podsecuritypolicy/group/mustrunas_test.go
Normal file
193
pkg/security/podsecuritypolicy/group/mustrunas_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
49
pkg/security/podsecuritypolicy/group/runasany.go
Normal file
49
pkg/security/podsecuritypolicy/group/runasany.go
Normal file
@@ -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{}
|
||||
|
||||
}
|
60
pkg/security/podsecuritypolicy/group/runasany_test.go
Normal file
60
pkg/security/podsecuritypolicy/group/runasany_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
35
pkg/security/podsecuritypolicy/group/types.go
Normal file
35
pkg/security/podsecuritypolicy/group/types.go
Normal file
@@ -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
|
||||
}
|
297
pkg/security/podsecuritypolicy/provider.go
Normal file
297
pkg/security/podsecuritypolicy/provider.go
Normal file
@@ -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
|
||||
}
|
822
pkg/security/podsecuritypolicy/provider_test.go
Normal file
822
pkg/security/podsecuritypolicy/provider_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
84
pkg/security/podsecuritypolicy/selinux/mustrunas.go
Normal file
84
pkg/security/podsecuritypolicy/selinux/mustrunas.go
Normal file
@@ -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
|
||||
}
|
159
pkg/security/podsecuritypolicy/selinux/mustrunas_test.go
Normal file
159
pkg/security/podsecuritypolicy/selinux/mustrunas_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
pkg/security/podsecuritypolicy/selinux/runasany.go
Normal file
43
pkg/security/podsecuritypolicy/selinux/runasany.go
Normal file
@@ -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{}
|
||||
}
|
73
pkg/security/podsecuritypolicy/selinux/runasany_test.go
Normal file
73
pkg/security/podsecuritypolicy/selinux/runasany_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
30
pkg/security/podsecuritypolicy/selinux/types.go
Normal file
30
pkg/security/podsecuritypolicy/selinux/types.go
Normal file
@@ -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
|
||||
}
|
62
pkg/security/podsecuritypolicy/types.go
Normal file
62
pkg/security/podsecuritypolicy/types.go
Normal file
@@ -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
|
||||
}
|
84
pkg/security/podsecuritypolicy/user/mustrunas.go
Normal file
84
pkg/security/podsecuritypolicy/user/mustrunas.go
Normal file
@@ -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
|
||||
}
|
152
pkg/security/podsecuritypolicy/user/mustrunas_test.go
Normal file
152
pkg/security/podsecuritypolicy/user/mustrunas_test.go
Normal file
@@ -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
|
||||
}
|
59
pkg/security/podsecuritypolicy/user/nonroot.go
Normal file
59
pkg/security/podsecuritypolicy/user/nonroot.go
Normal file
@@ -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
|
||||
}
|
80
pkg/security/podsecuritypolicy/user/nonroot_test.go
Normal file
80
pkg/security/podsecuritypolicy/user/nonroot_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
43
pkg/security/podsecuritypolicy/user/runasany.go
Normal file
43
pkg/security/podsecuritypolicy/user/runasany.go
Normal file
@@ -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{}
|
||||
}
|
59
pkg/security/podsecuritypolicy/user/runasany_test.go
Normal file
59
pkg/security/podsecuritypolicy/user/runasany_test.go
Normal file
@@ -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 ")
|
||||
}
|
||||
}
|
30
pkg/security/podsecuritypolicy/user/types.go
Normal file
30
pkg/security/podsecuritypolicy/user/types.go
Normal file
@@ -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
|
||||
}
|
142
pkg/security/podsecuritypolicy/util/util.go
Normal file
142
pkg/security/podsecuritypolicy/util/util.go
Normal file
@@ -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
|
||||
}
|
105
pkg/security/podsecuritypolicy/util/util_test.go
Normal file
105
pkg/security/podsecuritypolicy/util/util_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
18
plugin/pkg/admission/security/doc.go
Normal file
18
plugin/pkg/admission/security/doc.go
Normal file
@@ -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
|
Reference in New Issue
Block a user