func ValidateIdentity(identity *api.Identity) field.ErrorList { allErrs := kvalidation.ValidateObjectMeta(&identity.ObjectMeta, false, ValidateIdentityName, field.NewPath("metadata")) if len(identity.ProviderName) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("providerName"), "")) } else if reasons := ValidateIdentityProviderName(identity.ProviderName); len(reasons) != 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("providerName"), identity.ProviderName, strings.Join(reasons, ", "))) } if len(identity.ProviderUserName) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("providerUserName"), "")) } else if reasons := ValidateIdentityProviderName(identity.ProviderUserName); len(reasons) != 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("providerUserName"), identity.ProviderUserName, strings.Join(reasons, ", "))) } userPath := field.NewPath("user") if len(identity.ProviderName) > 0 && len(identity.ProviderUserName) > 0 { expectedIdentityName := identity.ProviderName + ":" + identity.ProviderUserName if identity.Name != expectedIdentityName { allErrs = append(allErrs, field.Invalid(userPath.Child("name"), identity.User.Name, fmt.Sprintf("must be %s", expectedIdentityName))) } } if reasons := ValidateUserName(identity.User.Name, false); len(reasons) != 0 { allErrs = append(allErrs, field.Invalid(userPath.Child("name"), identity.User.Name, strings.Join(reasons, ", "))) } if len(identity.User.Name) == 0 && len(identity.User.UID) != 0 { allErrs = append(allErrs, field.Required(userPath.Child("username"), "username is required when uid is provided")) } if len(identity.User.Name) != 0 && len(identity.User.UID) == 0 { allErrs = append(allErrs, field.Required(userPath.Child("uid"), "uid is required when username is provided")) } return allErrs }
func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, minimalNameRequirements, field.NewPath("metadata"))...) // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking // advantage of the binding infrastructure if roleBinding.RoleRef.APIGroup != rbac.GroupName { allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) } switch roleBinding.RoleRef.Kind { case "ClusterRole": default: allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"})) } if len(roleBinding.RoleRef.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) } else { for _, msg := range minimalNameRequirements(roleBinding.RoleRef.Name, false) { allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) } } subjectsPath := field.NewPath("subjects") for i, subject := range roleBinding.Subjects { allErrs = append(allErrs, validateRoleBindingSubject(subject, false, subjectsPath.Index(i))...) } return allErrs }
func TestCheckInvalidErr(t *testing.T) { tests := []struct { err error expected string }{ { errors.NewInvalid("Invalid1", "invalidation", field.ErrorList{field.Invalid(field.NewPath("field"), "single", "details")}), `Error from server: Invalid1 "invalidation" is invalid: field: invalid value 'single', Details: details`, }, { errors.NewInvalid("Invalid2", "invalidation", field.ErrorList{field.Invalid(field.NewPath("field1"), "multi1", "details"), field.Invalid(field.NewPath("field2"), "multi2", "details")}), `Error from server: Invalid2 "invalidation" is invalid: [field1: invalid value 'multi1', Details: details, field2: invalid value 'multi2', Details: details]`, }, { errors.NewInvalid("Invalid3", "invalidation", field.ErrorList{}), `Error from server: Invalid3 "invalidation" is invalid: <nil>`, }, } var errReturned string errHandle := func(err string) { errReturned = err } for _, test := range tests { checkErr(test.err, errHandle) if errReturned != test.expected { t.Fatalf("Got: %s, expected: %s", errReturned, test.expected) } } }
// ValidateEvent makes sure that the event makes sense. func ValidateEvent(event *api.Event) field.ErrorList { allErrs := field.ErrorList{} // There is no namespace required for root-scoped kind, for example, node. // However, older client code accidentally sets event.Namespace // to api.NamespaceDefault, so we accept that too, but "" is preferred. // Todo: Events may reference 3rd party object, and we can't check whether the object is namespaced. // Suppose them are namespaced. Do check if we can get the piece of information. // This should apply to all groups served by this apiserver. namespacedKindFlag, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion) if err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "kind"), event.InvolvedObject.Kind, fmt.Sprintf("couldn't check whether namespace is allowed: %s", err))) } else { if !namespacedKindFlag && event.Namespace != api.NamespaceDefault && event.Namespace != "" { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for %s", event.InvolvedObject.Kind))) } if namespacedKindFlag && event.Namespace != event.InvolvedObject.Namespace { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match involvedObject")) } } if !validation.IsDNS1123Subdomain(event.Namespace) { allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, "")) } return allErrs }
// ValidateBuild tests required fields for a Build. func ValidateBuildDefaultsConfig(config *api.BuildDefaultsConfig) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validateURL(config.GitHTTPProxy, field.NewPath("gitHTTPProxy"))...) allErrs = append(allErrs, validateURL(config.GitHTTPSProxy, field.NewPath("gitHTTPSProxy"))...) allErrs = append(allErrs, buildvalidation.ValidateStrategyEnv(config.Env, field.NewPath("env"))...) return allErrs }
func ValidateDeploymentConfig(config *deployapi.DeploymentConfig) field.ErrorList { allErrs := validation.ValidateObjectMeta(&config.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata")) // TODO: Refactor to validate spec and status separately for i := range config.Spec.Triggers { allErrs = append(allErrs, validateTrigger(&config.Spec.Triggers[i], field.NewPath("spec", "triggers").Index(i))...) } specPath := field.NewPath("spec") allErrs = append(allErrs, validateDeploymentStrategy(&config.Spec.Strategy, field.NewPath("spec", "strategy"))...) if config.Spec.Template == nil { allErrs = append(allErrs, field.Required(specPath.Child("template"), "")) } else { allErrs = append(allErrs, validation.ValidatePodTemplateSpec(config.Spec.Template, specPath.Child("template"))...) } if config.Status.LatestVersion < 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("status", "latestVersion"), config.Status.LatestVersion, "latestVersion cannot be negative")) } if config.Spec.Replicas < 0 { allErrs = append(allErrs, field.Invalid(specPath.Child("replicas"), config.Spec.Replicas, "replicas cannot be negative")) } if len(config.Spec.Selector) == 0 { allErrs = append(allErrs, field.Invalid(specPath.Child("selector"), config.Spec.Selector, "selector cannot be empty")) } return allErrs }
// ValidateEvent makes sure that the event makes sense. func ValidateEvent(event *api.Event) field.ErrorList { allErrs := field.ErrorList{} // Make sure event.Namespace and the involvedObject.Namespace agree if len(event.InvolvedObject.Namespace) == 0 { // event.Namespace must also be empty (or "default", for compatibility with old clients) if event.Namespace != api.NamespaceNone && event.Namespace != api.NamespaceDefault { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) } } else { // event namespace must match if event.Namespace != event.InvolvedObject.Namespace { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) } } // For kinds we recognize, make sure involvedObject.Namespace is set for namespaced kinds if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil { if namespaced && len(event.InvolvedObject.Namespace) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind))) } if !namespaced && len(event.InvolvedObject.Namespace) > 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for kind %s", event.InvolvedObject.Kind))) } } for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) { allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg)) } return allErrs }
// validateTemplateBody checks the body of a template. func validateTemplateBody(template *api.Template) (allErrs field.ErrorList) { for i := range template.Parameters { allErrs = append(allErrs, ValidateParameter(&template.Parameters[i], field.NewPath("parameters").Index(i))...) } allErrs = append(allErrs, validation.ValidateLabels(template.ObjectLabels, field.NewPath("labels"))...) return }
func validateRoleBinding(roleBinding *rbac.RoleBinding, isNamespaced bool) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, isNamespaced, minimalNameRequirements, field.NewPath("metadata"))...) // roleRef namespace is empty when referring to global policy. if len(roleBinding.RoleRef.Namespace) > 0 { for _, msg := range validation.ValidateNamespaceName(roleBinding.RoleRef.Namespace, false) { allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "namespace"), roleBinding.RoleRef.Namespace, msg)) } } if len(roleBinding.RoleRef.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) } else { for _, msg := range minimalNameRequirements(roleBinding.RoleRef.Name, false) { allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) } } subjectsPath := field.NewPath("subjects") for i, subject := range roleBinding.Subjects { allErrs = append(allErrs, validateRoleBindingSubject(subject, isNamespaced, subjectsPath.Index(i))...) } return allErrs }
func validateSessionConfig(config *api.SessionConfig, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // Validate session secrets file, if specified sessionSecretsFilePath := field.NewPath("sessionSecretsFile") if len(config.SessionSecretsFile) > 0 { fileErrs := ValidateFile(config.SessionSecretsFile, sessionSecretsFilePath) if len(fileErrs) != 0 { // Missing file allErrs = append(allErrs, fileErrs...) } else { // Validate file contents secrets, err := latest.ReadSessionSecrets(config.SessionSecretsFile) if err != nil { allErrs = append(allErrs, field.Invalid(sessionSecretsFilePath, config.SessionSecretsFile, fmt.Sprintf("error reading file: %v", err))) } else { for _, err := range ValidateSessionSecrets(secrets) { allErrs = append(allErrs, field.Invalid(sessionSecretsFilePath, config.SessionSecretsFile, err.Error())) } } } } if len(config.SessionName) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("sessionName"))) } return allErrs }
// validateInsecureEdgeTerminationPolicy tests fields for different types of // insecure options. Called by validateTLS. func validateInsecureEdgeTerminationPolicy(tls *routeapi.TLSConfig) *field.Error { // Check insecure option value if specified (empty is ok). if len(tls.InsecureEdgeTerminationPolicy) == 0 { return nil } // Ensure insecure is set only for edge terminated routes. if routeapi.TLSTerminationEdge != tls.Termination { // tls.InsecureEdgeTerminationPolicy option is not supported for a non edge-terminated routes. return field.Invalid(field.NewPath("insecureEdgeTerminationPolicy"), tls.InsecureEdgeTerminationPolicy, "InsecureEdgeTerminationPolicy is only allowed for edge-terminated routes") } // It is an edge-terminated route, check insecure option value is // one of None(for disable), Allow or Redirect. allowedValues := map[routeapi.InsecureEdgeTerminationPolicyType]struct{}{ routeapi.InsecureEdgeTerminationPolicyNone: {}, routeapi.InsecureEdgeTerminationPolicyAllow: {}, routeapi.InsecureEdgeTerminationPolicyRedirect: {}, } if _, ok := allowedValues[tls.InsecureEdgeTerminationPolicy]; !ok { msg := fmt.Sprintf("invalid value for InsecureEdgeTerminationPolicy option, acceptable values are %s, %s, %s, or empty", routeapi.InsecureEdgeTerminationPolicyNone, routeapi.InsecureEdgeTerminationPolicyAllow, routeapi.InsecureEdgeTerminationPolicyRedirect) return field.Invalid(field.NewPath("InsecureEdgeTerminationPolicy"), tls.InsecureEdgeTerminationPolicy, msg) } return nil }
func ValidatePolicyBinding(policyBinding *authorizationapi.PolicyBinding, isNamespaced bool) field.ErrorList { allErrs := validation.ValidateObjectMeta(&policyBinding.ObjectMeta, isNamespaced, PolicyBindingNameValidator(policyBinding.PolicyRef.Namespace), field.NewPath("metadata")) if !isNamespaced { if len(policyBinding.PolicyRef.Namespace) > 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("policyRef", "namespace"), policyBinding.PolicyRef.Namespace, "may not reference another namespace")) } } roleBindingsPath := field.NewPath("roleBindings") for roleBindingKey, roleBinding := range policyBinding.RoleBindings { keyPath := roleBindingsPath.Key(roleBindingKey) if roleBinding == nil { allErrs = append(allErrs, field.Required(keyPath, "")) } if roleBinding.RoleRef.Namespace != policyBinding.PolicyRef.Namespace { allErrs = append(allErrs, field.Invalid(keyPath.Child("roleRef", "namespace"), policyBinding.PolicyRef.Namespace, "must be "+policyBinding.PolicyRef.Namespace)) } if roleBindingKey != roleBinding.Name { allErrs = append(allErrs, field.Invalid(keyPath.Child("metadata", "name"), roleBinding.Name, "must be "+roleBindingKey)) } allErrs = append(allErrs, validateRoleBinding(roleBinding, isNamespaced, keyPath)...) } return allErrs }
// Admit determines if the service should be admitted based on the configured network CIDR. func (r *externalIPRanger) Admit(a kadmission.Attributes) error { if a.GetResource() != kapi.Resource("services") { return nil } svc, ok := a.GetObject().(*kapi.Service) // if we can't convert then we don't handle this object so just return if !ok { return nil } var errs field.ErrorList switch { // administrator disabled externalIPs case len(svc.Spec.ExternalIPs) > 0 && len(r.admit) == 0: errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs"), "externalIPs have been disabled")) // administrator has limited the range case len(svc.Spec.ExternalIPs) > 0 && len(r.admit) > 0: for i, s := range svc.Spec.ExternalIPs { ip := net.ParseIP(s) if ip == nil { errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIPs must be a valid address")) continue } if networkSlice(r.reject).Contains(ip) || !networkSlice(r.admit).Contains(ip) { errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIP is not allowed")) continue } } } if len(errs) > 0 { return apierrs.NewInvalid(a.GetKind(), a.GetName(), errs) } return nil }
func TestValidateLabels(t *testing.T) { successCases := []map[string]string{ {"simple": "bar"}, {"now-with-dashes": "bar"}, {"1-starts-with-num": "bar"}, {"1234": "bar"}, {"simple/simple": "bar"}, {"now-with-dashes/simple": "bar"}, {"now-with-dashes/now-with-dashes": "bar"}, {"now.with.dots/simple": "bar"}, {"now-with.dashes-and.dots/simple": "bar"}, {"1-num.2-num/3-num": "bar"}, {"1234/5678": "bar"}, {"1.2.3.4/5678": "bar"}, {"UpperCaseAreOK123": "bar"}, {"goodvalue": "123_-.BaR"}, } for i := range successCases { errs := ValidateLabels(successCases[i], field.NewPath("field")) if len(errs) != 0 { t.Errorf("case[%d] expected success, got %#v", i, errs) } } labelNameErrorCases := []map[string]string{ {"nospecialchars^=@": "bar"}, {"cantendwithadash-": "bar"}, {"only/one/slash": "bar"}, {strings.Repeat("a", 254): "bar"}, } for i := range labelNameErrorCases { errs := ValidateLabels(labelNameErrorCases[i], field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d] expected failure", i) } else { detail := errs[0].Detail if detail != qualifiedNameErrorMsg { t.Errorf("error detail %s should be equal %s", detail, qualifiedNameErrorMsg) } } } labelValueErrorCases := []map[string]string{ {"toolongvalue": strings.Repeat("a", 64)}, {"backslashesinvalue": "some\\bad\\value"}, {"nocommasallowed": "bad,value"}, {"strangecharsinvalue": "?#$notsogood"}, } for i := range labelValueErrorCases { errs := ValidateLabels(labelValueErrorCases[i], field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d] expected failure", i) } else { detail := errs[0].Detail if detail != labelValueErrorMsg { t.Errorf("error detail %s should be equal %s", detail, labelValueErrorMsg) } } } }
// ValidateImageStream tests required fields for an ImageStream. func ValidateImageStream(stream *api.ImageStream) field.ErrorList { result := validation.ValidateObjectMeta(&stream.ObjectMeta, true, ValidateImageStreamName, field.NewPath("metadata")) // Ensure we can generate a valid docker image repository from namespace/name if len(stream.Namespace+"/"+stream.Name) > reference.NameTotalLengthMax { result = append(result, field.Invalid(field.NewPath("metadata", "name"), stream.Name, fmt.Sprintf("'namespace/name' cannot be longer than %d characters", reference.NameTotalLengthMax))) } if len(stream.Spec.DockerImageRepository) != 0 { dockerImageRepositoryPath := field.NewPath("spec", "dockerImageRepository") if ref, err := api.ParseDockerImageReference(stream.Spec.DockerImageRepository); err != nil { result = append(result, field.Invalid(dockerImageRepositoryPath, stream.Spec.DockerImageRepository, err.Error())) } else { if len(ref.Tag) > 0 { result = append(result, field.Invalid(dockerImageRepositoryPath, stream.Spec.DockerImageRepository, "the repository name may not contain a tag")) } if len(ref.ID) > 0 { result = append(result, field.Invalid(dockerImageRepositoryPath, stream.Spec.DockerImageRepository, "the repository name may not contain an ID")) } } } for tag, tagRef := range stream.Spec.Tags { path := field.NewPath("spec", "tags").Key(tag) result = append(result, ValidateImageStreamTagReference(tagRef, path)...) } for tag, history := range stream.Status.Tags { for i, tagEvent := range history.Items { if len(tagEvent.DockerImageReference) == 0 { result = append(result, field.Required(field.NewPath("status", "tags").Key(tag).Child("items").Index(i).Child("dockerImageReference"), "")) } } } return result }
func ValidateIdentity(identity *api.Identity) field.ErrorList { allErrs := kvalidation.ValidateObjectMeta(&identity.ObjectMeta, false, ValidateIdentityName, field.NewPath("metadata")) if len(identity.ProviderName) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("providerName"), "")) } else if ok, msg := ValidateIdentityProviderName(identity.ProviderName); !ok { allErrs = append(allErrs, field.Invalid(field.NewPath("providerName"), identity.ProviderName, msg)) } if len(identity.ProviderUserName) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("providerUserName"), "")) } else if ok, msg := ValidateIdentityProviderName(identity.ProviderUserName); !ok { allErrs = append(allErrs, field.Invalid(field.NewPath("providerUserName"), identity.ProviderUserName, msg)) } userPath := field.NewPath("user") if len(identity.ProviderName) > 0 && len(identity.ProviderUserName) > 0 { expectedIdentityName := identity.ProviderName + ":" + identity.ProviderUserName if identity.Name != expectedIdentityName { allErrs = append(allErrs, field.Invalid(userPath.Child("name"), identity.User.Name, fmt.Sprintf("must be %s", expectedIdentityName))) } } if ok, msg := ValidateUserName(identity.User.Name, false); !ok { allErrs = append(allErrs, field.Invalid(userPath.Child("name"), identity.User.Name, msg)) } if len(identity.User.Name) == 0 && len(identity.User.UID) != 0 { allErrs = append(allErrs, field.Invalid(userPath.Child("uid"), identity.User.UID, "may not be set if user.name is empty")) } if len(identity.User.Name) != 0 && len(identity.User.UID) == 0 { allErrs = append(allErrs, field.Required(userPath.Child("uid"), "")) } return allErrs }
// ValidateStorageClass validates a StorageClass. func ValidateStorageClass(storageClass *extensions.StorageClass) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...) allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...) return allErrs }
// ValidateClusterNetwork tests if required fields in the ClusterNetwork are set. func ValidateClusterNetwork(clusterNet *sdnapi.ClusterNetwork) field.ErrorList { allErrs := validation.ValidateObjectMeta(&clusterNet.ObjectMeta, false, oapi.MinimalNameRequirements, field.NewPath("metadata")) clusterIP, clusterIPNet, err := net.ParseCIDR(clusterNet.Network) if err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("network"), clusterNet.Network, err.Error())) } else { ones, bitSize := clusterIPNet.Mask.Size() if uint32(bitSize-ones) <= clusterNet.HostSubnetLength { allErrs = append(allErrs, field.Invalid(field.NewPath("hostSubnetLength"), clusterNet.HostSubnetLength, "subnet length is greater than cluster Mask")) } } serviceIP, serviceIPNet, err := net.ParseCIDR(clusterNet.ServiceNetwork) if err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("serviceNetwork"), clusterNet.ServiceNetwork, err.Error())) } if (clusterIPNet != nil) && (serviceIP != nil) && clusterIPNet.Contains(serviceIP) { allErrs = append(allErrs, field.Invalid(field.NewPath("serviceNetwork"), clusterNet.ServiceNetwork, "service network overlaps with cluster network")) } if (serviceIPNet != nil) && (clusterIP != nil) && serviceIPNet.Contains(clusterIP) { allErrs = append(allErrs, field.Invalid(field.NewPath("network"), clusterNet.Network, "cluster network overlaps with service network")) } return allErrs }
func ValidateSelfSubjectAccessReview(sar *authorizationapi.SelfSubjectAccessReview) field.ErrorList { allErrs := ValidateSelfSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec")) if !api.Semantic.DeepEqual(api.ObjectMeta{}, sar.ObjectMeta) { allErrs = append(allErrs, field.Invalid(field.NewPath("metadata"), sar.ObjectMeta, `must be empty`)) } return allErrs }
// 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 }
// 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)...) allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...) // 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")) } allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...) // TODO(timstclair): ValidatePodSecurityContext should be renamed to ValidatePod since its scope // is not limited to the PodSecurityContext. 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(field.NewPath("spec", "volumes").Index(i), string(fsType), err.Error())) continue } if !allowedVolumes.Has(string(fsType)) { allErrs = append(allErrs, field.Invalid( field.NewPath("spec", "volumes").Index(i), string(fsType), fmt.Sprintf("%s volumes are not allowed to be used", string(fsType)))) } } } return allErrs }
func ValidateClusterResourceQuota(clusterquota *quotaapi.ClusterResourceQuota) field.ErrorList { allErrs := validation.ValidateObjectMeta(&clusterquota.ObjectMeta, false, validation.ValidateResourceQuotaName, field.NewPath("metadata")) hasSelectionCriteria := (clusterquota.Spec.Selector.LabelSelector != nil && len(clusterquota.Spec.Selector.LabelSelector.MatchLabels)+len(clusterquota.Spec.Selector.LabelSelector.MatchExpressions) > 0) || (len(clusterquota.Spec.Selector.AnnotationSelector) > 0) if !hasSelectionCriteria { allErrs = append(allErrs, field.Required(field.NewPath("spec", "selector"), "must restrict the selected projects")) } if clusterquota.Spec.Selector.LabelSelector != nil { allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(clusterquota.Spec.Selector.LabelSelector, field.NewPath("spec", "selector", "labels"))...) if len(clusterquota.Spec.Selector.LabelSelector.MatchLabels)+len(clusterquota.Spec.Selector.LabelSelector.MatchExpressions) == 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "selector", "labels"), clusterquota.Spec.Selector.LabelSelector, "must restrict the selected projects")) } } allErrs = append(allErrs, validation.ValidateResourceQuotaSpec(&clusterquota.Spec.Quota, field.NewPath("spec", "quota"))...) allErrs = append(allErrs, validation.ValidateResourceQuotaStatus(&clusterquota.Status.Total, field.NewPath("status", "overall"))...) for e := clusterquota.Status.Namespaces.OrderedKeys().Front(); e != nil; e = e.Next() { namespace := e.Value.(string) used, _ := clusterquota.Status.Namespaces.Get(namespace) fldPath := field.NewPath("status", "namespaces").Key(namespace) for k, v := range used.Used { resPath := fldPath.Key(string(k)) allErrs = append(allErrs, validation.ValidateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validation.ValidateResourceQuantityValue(string(k), v, resPath)...) } } return allErrs }
func ValidateBuildOverridesConfig(config *api.BuildOverridesConfig) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, buildvalidation.ValidateImageLabels(config.ImageLabels, field.NewPath("imageLabels"))...) allErrs = append(allErrs, buildvalidation.ValidateNodeSelector(config.NodeSelector, field.NewPath("nodeSelector"))...) allErrs = append(allErrs, validation.ValidateAnnotations(config.Annotations, field.NewPath("annotations"))...) return allErrs }
func ValidateDeploymentRollback(obj *extensions.DeploymentRollback) field.ErrorList { allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations")) if len(obj.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required")) } allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...) return allErrs }
func ValidateNetNamespaceUpdate(obj *sdnapi.NetNamespace, old *sdnapi.NetNamespace) field.ErrorList { allErrs := validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) if err := sdnapi.ValidVNID(obj.NetID); err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("netID"), obj.NetID, err.Error())) } return allErrs }
// 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.ValidatePodSecurityContext(pod, 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.ValidatePodSecurityContext(pod, 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.ValidatePodSecurityContext(pod, field.NewPath("")) if len(errs) != 0 { t.Errorf("wildcard volume expected no errors for %s but got %v", fieldVal.Name, errs) } } }
// assignSecurityContext creates a security context for each container in the pod // and validates that the sc falls within the scc constraints. All containers must validate against // the same scc or is not considered valid. func assignSecurityContext(provider scc.SecurityContextConstraintsProvider, pod *kapi.Pod, fldPath *field.Path) field.ErrorList { generatedSCs := make([]*kapi.SecurityContext, len(pod.Spec.InitContainers)+len(pod.Spec.Containers)) errs := field.ErrorList{} psc, err := provider.CreatePodSecurityContext(pod) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error())) } // save the original PSC and validate the generated PSC. Leave the generated PSC // set for container generation/validation. We will reset to original post container // validation. originalPSC := pod.Spec.SecurityContext pod.Spec.SecurityContext = psc errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...) // Note: this is not changing the original container, we will set container SCs later so long // as all containers validated under the same SCC. containerPath := field.NewPath("spec", "initContainers") for i, containerCopy := range pod.Spec.InitContainers { csc, resolutionErrs := resolveContainerSecurityContext(provider, pod, &containerCopy, containerPath.Index(i)) errs = append(errs, resolutionErrs...) if len(resolutionErrs) > 0 { continue } generatedSCs[i] = csc } base := len(pod.Spec.InitContainers) // Note: this is not changing the original container, we will set container SCs later so long // as all containers validated under the same SCC. containerPath = field.NewPath("spec", "containers") for i, containerCopy := range pod.Spec.Containers { csc, resolutionErrs := resolveContainerSecurityContext(provider, pod, &containerCopy, containerPath.Index(i)) errs = append(errs, resolutionErrs...) if len(resolutionErrs) > 0 { continue } generatedSCs[i+base] = csc } if len(errs) > 0 { // ensure psc is not mutated if there are errors pod.Spec.SecurityContext = originalPSC return errs } // if we've reached this code then we've generated and validated an SC for every container in the // pod so let's apply what we generated. Note: the psc is already applied. for i := range pod.Spec.InitContainers { pod.Spec.InitContainers[i].SecurityContext = generatedSCs[i] } for i := range pod.Spec.Containers { pod.Spec.Containers[i].SecurityContext = generatedSCs[i+base] } return nil }
func ValidateHostSubnetUpdate(obj *sdnapi.HostSubnet, old *sdnapi.HostSubnet) field.ErrorList { allErrs := validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) if obj.Subnet != old.Subnet { allErrs = append(allErrs, field.Invalid(field.NewPath("subnet"), obj.Subnet, "cannot change the subnet lease midflight.")) } return allErrs }
func ValidateRoleBindingRestriction(rbr *authorizationapi.RoleBindingRestriction) field.ErrorList { allErrs := validation.ValidateObjectMeta(&rbr.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrs = append(allErrs, ValidateRoleBindingRestrictionSpec(&rbr.Spec, field.NewPath("spec"))...) return allErrs }
func ValidateAuthorizeToken(authorizeToken *api.OAuthAuthorizeToken) field.ErrorList { allErrs := validation.ValidateObjectMeta(&authorizeToken.ObjectMeta, false, ValidateTokenName, field.NewPath("metadata")) allErrs = append(allErrs, ValidateClientNameField(authorizeToken.ClientName, field.NewPath("clientName"))...) allErrs = append(allErrs, ValidateUserNameField(authorizeToken.UserName, field.NewPath("userName"))...) allErrs = append(allErrs, ValidateScopes(authorizeToken.Scopes, field.NewPath("scopes"))...) if len(authorizeToken.UserUID) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("userUID"), "")) } if ok, msg := ValidateRedirectURI(authorizeToken.RedirectURI); !ok { allErrs = append(allErrs, field.Invalid(field.NewPath("redirectURI"), authorizeToken.RedirectURI, msg)) } if len(authorizeToken.CodeChallenge) > 0 || len(authorizeToken.CodeChallengeMethod) > 0 { switch { case len(authorizeToken.CodeChallenge) == 0: allErrs = append(allErrs, field.Required(field.NewPath("codeChallenge"), "required if codeChallengeMethod is specified")) case !codeChallengeRegex.MatchString(authorizeToken.CodeChallenge): allErrs = append(allErrs, field.Invalid(field.NewPath("codeChallenge"), authorizeToken.CodeChallenge, "must be 43-128 characters [a-zA-Z0-9.~_-]")) } switch authorizeToken.CodeChallengeMethod { case "": allErrs = append(allErrs, field.Required(field.NewPath("codeChallengeMethod"), "required if codeChallenge is specified")) case codeChallengeMethodPlain, codeChallengeMethodSHA256: // no-op, good default: allErrs = append(allErrs, field.NotSupported(field.NewPath("codeChallengeMethod"), authorizeToken.CodeChallengeMethod, CodeChallengeMethodsSupported)) } } return allErrs }