123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- /*
- Copyright 2017 The Kubernetes Authors.
- 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 validation
- import (
- "net"
- "regexp"
- "strings"
- apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
- unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
- "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/networking"
- )
- // ValidateNetworkPolicyName can be used to check whether the given networkpolicy
- // name is valid.
- func ValidateNetworkPolicyName(name string, prefix bool) []string {
- return apimachineryvalidation.NameIsDNSSubdomain(name, prefix)
- }
- // ValidateNetworkPolicyPort validates a NetworkPolicyPort
- func ValidateNetworkPolicyPort(port *networking.NetworkPolicyPort, portPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if port.Protocol != nil && *port.Protocol != api.ProtocolTCP && *port.Protocol != api.ProtocolUDP && *port.Protocol != api.ProtocolSCTP {
- allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(api.ProtocolTCP), string(api.ProtocolUDP), string(api.ProtocolSCTP)}))
- }
- if port.Port != nil {
- if port.Port.Type == intstr.Int {
- for _, msg := range validation.IsValidPortNum(int(port.Port.IntVal)) {
- allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.IntVal, msg))
- }
- } else {
- for _, msg := range validation.IsValidPortName(port.Port.StrVal) {
- allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.StrVal, msg))
- }
- }
- }
- return allErrs
- }
- // ValidateNetworkPolicyPeer validates a NetworkPolicyPeer
- func ValidateNetworkPolicyPeer(peer *networking.NetworkPolicyPeer, peerPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- numPeers := 0
- if peer.PodSelector != nil {
- numPeers++
- allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.PodSelector, peerPath.Child("podSelector"))...)
- }
- if peer.NamespaceSelector != nil {
- numPeers++
- allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.NamespaceSelector, peerPath.Child("namespaceSelector"))...)
- }
- if peer.IPBlock != nil {
- numPeers++
- allErrs = append(allErrs, ValidateIPBlock(peer.IPBlock, peerPath.Child("ipBlock"))...)
- }
- if numPeers == 0 {
- allErrs = append(allErrs, field.Required(peerPath, "must specify a peer"))
- } else if numPeers > 1 && peer.IPBlock != nil {
- allErrs = append(allErrs, field.Forbidden(peerPath, "may not specify both ipBlock and another peer"))
- }
- return allErrs
- }
- // ValidateNetworkPolicySpec tests if required fields in the networkpolicy spec are set.
- func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&spec.PodSelector, fldPath.Child("podSelector"))...)
- // Validate ingress rules.
- for i, ingress := range spec.Ingress {
- ingressPath := fldPath.Child("ingress").Index(i)
- for i, port := range ingress.Ports {
- portPath := ingressPath.Child("ports").Index(i)
- allErrs = append(allErrs, ValidateNetworkPolicyPort(&port, portPath)...)
- }
- for i, from := range ingress.From {
- fromPath := ingressPath.Child("from").Index(i)
- allErrs = append(allErrs, ValidateNetworkPolicyPeer(&from, fromPath)...)
- }
- }
- // Validate egress rules
- for i, egress := range spec.Egress {
- egressPath := fldPath.Child("egress").Index(i)
- for i, port := range egress.Ports {
- portPath := egressPath.Child("ports").Index(i)
- allErrs = append(allErrs, ValidateNetworkPolicyPort(&port, portPath)...)
- }
- for i, to := range egress.To {
- toPath := egressPath.Child("to").Index(i)
- allErrs = append(allErrs, ValidateNetworkPolicyPeer(&to, toPath)...)
- }
- }
- // Validate PolicyTypes
- allowed := sets.NewString(string(networking.PolicyTypeIngress), string(networking.PolicyTypeEgress))
- if len(spec.PolicyTypes) > len(allowed) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("policyTypes"), &spec.PolicyTypes, "may not specify more than two policyTypes"))
- return allErrs
- }
- for i, pType := range spec.PolicyTypes {
- policyPath := fldPath.Child("policyTypes").Index(i)
- if !allowed.Has(string(pType)) {
- allErrs = append(allErrs, field.NotSupported(policyPath, pType, []string{string(networking.PolicyTypeIngress), string(networking.PolicyTypeEgress)}))
- }
- }
- return allErrs
- }
- // ValidateNetworkPolicy validates a networkpolicy.
- func ValidateNetworkPolicy(np *networking.NetworkPolicy) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMeta(&np.ObjectMeta, true, ValidateNetworkPolicyName, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateNetworkPolicySpec(&np.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- // ValidateNetworkPolicyUpdate tests if an update to a NetworkPolicy is valid.
- func ValidateNetworkPolicyUpdate(update, old *networking.NetworkPolicy) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
- allErrs = append(allErrs, ValidateNetworkPolicySpec(&update.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- // ValidateIPBlock validates a cidr and the except fields of an IpBlock NetworkPolicyPeer
- func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if len(ipb.CIDR) == 0 || ipb.CIDR == "" {
- allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), ""))
- return allErrs
- }
- cidrIPNet, err := apivalidation.ValidateCIDR(ipb.CIDR)
- if err != nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), ipb.CIDR, "not a valid CIDR"))
- return allErrs
- }
- exceptCIDR := ipb.Except
- for i, exceptIP := range exceptCIDR {
- exceptPath := fldPath.Child("except").Index(i)
- exceptCIDR, err := apivalidation.ValidateCIDR(exceptIP)
- if err != nil {
- allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "not a valid CIDR"))
- return allErrs
- }
- cidrMaskLen, _ := cidrIPNet.Mask.Size()
- exceptMaskLen, _ := exceptCIDR.Mask.Size()
- if !cidrIPNet.Contains(exceptCIDR.IP) || cidrMaskLen >= exceptMaskLen {
- allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "must be a strict subset of `cidr`"))
- }
- }
- return allErrs
- }
- // ValidateIngress tests if required fields in the Ingress are set.
- func ValidateIngress(ingress *networking.Ingress) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- // ValidateIngressName validates that the given name can be used as an Ingress name.
- var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain
- func validateIngressTLS(spec *networking.IngressSpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes
- // the wildcard spec from RFC 6125 into account.
- for _, itls := range spec.TLS {
- for i, host := range itls.Hosts {
- if strings.Contains(host, "*") {
- for _, msg := range validation.IsWildcardDNS1123Subdomain(host) {
- allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg))
- }
- continue
- }
- for _, msg := range validation.IsDNS1123Subdomain(host) {
- allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg))
- }
- }
- }
- return allErrs
- }
- // ValidateIngressSpec tests if required fields in the IngressSpec are set.
- func ValidateIngressSpec(spec *networking.IngressSpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- // TODO: Is a default backend mandatory?
- if spec.Backend != nil {
- allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"))...)
- } else if len(spec.Rules) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, "either `backend` or `rules` must be specified"))
- }
- if len(spec.Rules) > 0 {
- allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"))...)
- }
- if len(spec.TLS) > 0 {
- allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"))...)
- }
- return allErrs
- }
- // ValidateIngressUpdate tests if required fields in the Ingress are set.
- func ValidateIngressUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- // ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status.
- func ValidateIngressStatusUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata"))
- allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...)
- return allErrs
- }
- func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if len(ingressRules) == 0 {
- return append(allErrs, field.Required(fldPath, ""))
- }
- for i, ih := range ingressRules {
- if len(ih.Host) > 0 {
- if isIP := (net.ParseIP(ih.Host) != nil); isIP {
- allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address"))
- }
- // TODO: Ports and ips are allowed in the host part of a url
- // according to RFC 3986, consider allowing them.
- if strings.Contains(ih.Host, "*") {
- for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) {
- allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
- }
- continue
- }
- for _, msg := range validation.IsDNS1123Subdomain(ih.Host) {
- allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
- }
- }
- allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...)
- }
- return allErrs
- }
- func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if ingressRule.HTTP != nil {
- allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"))...)
- }
- return allErrs
- }
- func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if len(httpIngressRuleValue.Paths) == 0 {
- allErrs = append(allErrs, field.Required(fldPath.Child("paths"), ""))
- }
- for i, rule := range httpIngressRuleValue.Paths {
- if len(rule.Path) > 0 {
- if !strings.HasPrefix(rule.Path, "/") {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be an absolute path"))
- }
- // TODO: More draconian path regex validation.
- // Path must be a valid regex. This is the basic requirement.
- // In addition to this any characters not allowed in a path per
- // RFC 3986 section-3.3 cannot appear as a literal in the regex.
- // Consider the example: http://host/valid?#bar, everything after
- // the last '/' is a valid regex that matches valid#bar, which
- // isn't a valid path, because the path terminates at the first ?
- // or #. A more sophisticated form of validation would detect that
- // the user is confusing url regexes with path regexes.
- _, err := regexp.CompilePOSIX(rule.Path)
- if err != nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex"))
- }
- }
- allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...)
- }
- return allErrs
- }
- // validateIngressBackend tests if a given backend is valid.
- func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- // All backends must reference a single local service by name, and a single service port by name or number.
- if len(backend.ServiceName) == 0 {
- return append(allErrs, field.Required(fldPath.Child("serviceName"), ""))
- }
- for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg))
- }
- allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...)
- return allErrs
- }
|