PSP: move internal types from extensions to policy.
This commit is contained in:
@@ -17,10 +17,7 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,15 +27,11 @@ import (
|
||||
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
)
|
||||
|
||||
// ValidateDaemonSet tests if required fields in the DaemonSet are set.
|
||||
@@ -613,299 +606,3 @@ func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selecto
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidatePodSecurityPolicyName can be used to check whether the given
|
||||
// pod security policy name is valid.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
var ValidatePodSecurityPolicyName = apivalidation.NameIsDNSSubdomain
|
||||
|
||||
func ValidatePodSecurityPolicy(psp *extensions.PodSecurityPolicy) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(psp.Annotations, field.NewPath("metadata").Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...)
|
||||
allErrs = append(allErrs, validatePSPSELinux(fldPath.Child("seLinux"), &spec.SELinux)...)
|
||||
allErrs = append(allErrs, validatePSPSupplementalGroup(fldPath.Child("supplementalGroups"), &spec.SupplementalGroups)...)
|
||||
allErrs = append(allErrs, validatePSPFSGroup(fldPath.Child("fsGroup"), &spec.FSGroup)...)
|
||||
allErrs = append(allErrs, validatePodSecurityPolicyVolumes(fldPath, spec.Volumes)...)
|
||||
if len(spec.RequiredDropCapabilities) > 0 && hasCap(extensions.AllowAllCapabilities, spec.AllowedCapabilities) {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("requiredDropCapabilities"), spec.RequiredDropCapabilities,
|
||||
"must be empty when all capabilities are allowed by a wildcard"))
|
||||
}
|
||||
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.DefaultAddCapabilities, field.NewPath("defaultAddCapabilities"))...)
|
||||
allErrs = append(allErrs, validatePSPCapsAgainstDrops(spec.RequiredDropCapabilities, spec.AllowedCapabilities, field.NewPath("allowedCapabilities"))...)
|
||||
allErrs = append(allErrs, validatePSPDefaultAllowPrivilegeEscalation(fldPath.Child("defaultAllowPrivilegeEscalation"), spec.DefaultAllowPrivilegeEscalation, spec.AllowPrivilegeEscalation)...)
|
||||
allErrs = append(allErrs, validatePSPAllowedHostPaths(fldPath.Child("allowedHostPaths"), spec.AllowedHostPaths)...)
|
||||
allErrs = append(allErrs, validatePSPAllowedFlexVolumes(fldPath.Child("allowedFlexVolumes"), spec.AllowedFlexVolumes)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidatePodSecurityPolicySpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if p := annotations[apparmor.DefaultProfileAnnotationKey]; p != "" {
|
||||
if err := apparmor.ValidateProfileFormat(p); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.DefaultProfileAnnotationKey), p, err.Error()))
|
||||
}
|
||||
}
|
||||
if allowed := annotations[apparmor.AllowedProfilesAnnotationKey]; allowed != "" {
|
||||
for _, p := range strings.Split(allowed, ",") {
|
||||
if err := apparmor.ValidateProfileFormat(p); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Key(apparmor.AllowedProfilesAnnotationKey), allowed, err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sysctlAnnotation := annotations[extensions.SysctlsPodSecurityPolicyAnnotationKey]
|
||||
sysctlFldPath := fldPath.Key(extensions.SysctlsPodSecurityPolicyAnnotationKey)
|
||||
sysctls, err := extensions.SysctlsFromPodSecurityPolicyAnnotation(sysctlAnnotation)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(sysctlFldPath, sysctlAnnotation, err.Error()))
|
||||
} else {
|
||||
allErrs = append(allErrs, validatePodSecurityPolicySysctls(sysctlFldPath, sysctls)...)
|
||||
}
|
||||
|
||||
if p := annotations[seccomp.DefaultProfileAnnotationKey]; p != "" {
|
||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.DefaultProfileAnnotationKey))...)
|
||||
}
|
||||
if allowed := annotations[seccomp.AllowedProfilesAnnotationKey]; allowed != "" {
|
||||
for _, p := range strings.Split(allowed, ",") {
|
||||
if p == seccomp.AllowAny {
|
||||
continue
|
||||
}
|
||||
allErrs = append(allErrs, apivalidation.ValidateSeccompProfile(p, fldPath.Key(seccomp.AllowedProfilesAnnotationKey))...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPAllowedHostPaths makes sure all allowed host paths follow:
|
||||
// 1. path prefix is required
|
||||
// 2. path prefix does not have any element which is ".."
|
||||
func validatePSPAllowedHostPaths(fldPath *field.Path, allowedHostPaths []extensions.AllowedHostPath) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
for i, target := range allowedHostPaths {
|
||||
if target.PathPrefix == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i), "is required"))
|
||||
break
|
||||
}
|
||||
parts := strings.Split(filepath.ToSlash(target.PathPrefix), "/")
|
||||
for _, item := range parts {
|
||||
if item == ".." {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), target.PathPrefix, "must not contain '..'"))
|
||||
break // even for `../../..`, one error is sufficient to make the point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPAllowedFlexVolumes
|
||||
func validatePSPAllowedFlexVolumes(fldPath *field.Path, flexVolumes []extensions.AllowedFlexVolume) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(flexVolumes) > 0 {
|
||||
for idx, fv := range flexVolumes {
|
||||
if len(fv.Driver) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("allowedFlexVolumes").Index(idx).Child("driver"),
|
||||
"must specify a driver"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPSELinux validates the SELinux fields of PodSecurityPolicy.
|
||||
func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
// ensure the selinux strategy has a valid rule
|
||||
supportedSELinuxRules := sets.NewString(string(extensions.SELinuxStrategyMustRunAs),
|
||||
string(extensions.SELinuxStrategyRunAsAny))
|
||||
if !supportedSELinuxRules.Has(string(seLinux.Rule)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), seLinux.Rule, supportedSELinuxRules.List()))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPRunAsUser validates the RunAsUser fields of PodSecurityPolicy.
|
||||
func validatePSPRunAsUser(fldPath *field.Path, runAsUser *extensions.RunAsUserStrategyOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
// ensure the user strategy has a valid rule
|
||||
supportedRunAsUserRules := sets.NewString(string(extensions.RunAsUserStrategyMustRunAs),
|
||||
string(extensions.RunAsUserStrategyMustRunAsNonRoot),
|
||||
string(extensions.RunAsUserStrategyRunAsAny))
|
||||
if !supportedRunAsUserRules.Has(string(runAsUser.Rule)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), runAsUser.Rule, supportedRunAsUserRules.List()))
|
||||
}
|
||||
|
||||
// validate range settings
|
||||
for idx, rng := range runAsUser.Ranges {
|
||||
allErrs = append(allErrs, validateUserIDRange(fldPath.Child("ranges").Index(idx), rng)...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPFSGroup validates the FSGroupStrategyOptions fields of the PodSecurityPolicy.
|
||||
func validatePSPFSGroup(fldPath *field.Path, groupOptions *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, validateGroupIDRange(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, validateGroupIDRange(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 := 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()))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPDefaultAllowPrivilegeEscalation validates the DefaultAllowPrivilegeEscalation field against the AllowPrivilegeEscalation field of a PodSecurityPolicy.
|
||||
func validatePSPDefaultAllowPrivilegeEscalation(fldPath *field.Path, defaultAllowPrivilegeEscalation *bool, allowPrivilegeEscalation bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if defaultAllowPrivilegeEscalation != nil && *defaultAllowPrivilegeEscalation && !allowPrivilegeEscalation {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, defaultAllowPrivilegeEscalation, "Cannot set DefaultAllowPrivilegeEscalation to true without also setting AllowPrivilegeEscalation to true"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
const sysctlPatternSegmentFmt string = "([a-z0-9][-_a-z0-9]*)?[a-z0-9*]"
|
||||
const SysctlPatternFmt string = "(" + apivalidation.SysctlSegmentFmt + "\\.)*" + sysctlPatternSegmentFmt
|
||||
|
||||
var sysctlPatternRegexp = regexp.MustCompile("^" + SysctlPatternFmt + "$")
|
||||
|
||||
func IsValidSysctlPattern(name string) bool {
|
||||
if len(name) > apivalidation.SysctlMaxLength {
|
||||
return false
|
||||
}
|
||||
return sysctlPatternRegexp.MatchString(name)
|
||||
}
|
||||
|
||||
// validatePodSecurityPolicySysctls validates the sysctls fields of PodSecurityPolicy.
|
||||
func validatePodSecurityPolicySysctls(fldPath *field.Path, sysctls []string) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, s := range sysctls {
|
||||
if !IsValidSysctlPattern(string(s)) {
|
||||
allErrs = append(
|
||||
allErrs,
|
||||
field.Invalid(fldPath.Index(i), sysctls[i], fmt.Sprintf("must have at most %d characters and match regex %s",
|
||||
apivalidation.SysctlMaxLength,
|
||||
SysctlPatternFmt,
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateUserIDRange(fldPath *field.Path, rng extensions.UserIDRange) field.ErrorList {
|
||||
return validateIDRanges(fldPath, int64(rng.Min), int64(rng.Max))
|
||||
}
|
||||
|
||||
func validateGroupIDRange(fldPath *field.Path, rng extensions.GroupIDRange) field.ErrorList {
|
||||
return validateIDRanges(fldPath, int64(rng.Min), int64(rng.Max))
|
||||
}
|
||||
|
||||
// validateIDRanges ensures the range is valid.
|
||||
func validateIDRanges(fldPath *field.Path, min, max int64) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
// if 0 <= Min <= Max then we do not need to validate max. It is always greater than or
|
||||
// equal to 0 and Min.
|
||||
if min < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), min, "min cannot be negative"))
|
||||
}
|
||||
if max < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("max"), max, "max cannot be negative"))
|
||||
}
|
||||
if min > max {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), min, "min cannot be greater than max"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePSPCapsAgainstDrops ensures an allowed cap is not listed in the required drops.
|
||||
func validatePSPCapsAgainstDrops(requiredDrops []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{}
|
||||
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(new.Annotations, field.NewPath("metadata").Child("annotations"))...)
|
||||
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
Reference in New Issue
Block a user