...

Source file src/sigs.k8s.io/gateway-api/apis/v1beta1/validation/gateway.go

Documentation: sigs.k8s.io/gateway-api/apis/v1beta1/validation

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"net/netip"
    22  	"regexp"
    23  
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  
    27  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    28  	gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    29  )
    30  
    31  var (
    32  	// set of protocols for which we need to validate that hostname is empty
    33  	protocolsHostnameInvalid = map[gatewayv1b1.ProtocolType]struct{}{
    34  		gatewayv1.TCPProtocolType: {},
    35  		gatewayv1.UDPProtocolType: {},
    36  	}
    37  	// set of protocols for which TLSConfig shall not be present
    38  	protocolsTLSInvalid = map[gatewayv1b1.ProtocolType]struct{}{
    39  		gatewayv1.HTTPProtocolType: {},
    40  		gatewayv1.UDPProtocolType:  {},
    41  		gatewayv1.TCPProtocolType:  {},
    42  	}
    43  	// set of protocols for which TLSConfig must be set
    44  	protocolsTLSRequired = map[gatewayv1b1.ProtocolType]struct{}{
    45  		gatewayv1.HTTPSProtocolType: {},
    46  		gatewayv1.TLSProtocolType:   {},
    47  	}
    48  
    49  	validHostnameAddress = `^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
    50  	validHostnameRegexp  = regexp.MustCompile(validHostnameAddress)
    51  )
    52  
    53  // ValidateGateway validates gw according to the Gateway API specification.
    54  // For additional details of the Gateway spec, refer to:
    55  //
    56  //	https://gateway-api.sigs.k8s.io/v1beta1/reference/spec/#gateway.networking.k8s.io/v1beta1.Gateway
    57  //
    58  // Validation that is not possible with CRD annotations may be added here in the future.
    59  // See https://github.com/kubernetes-sigs/gateway-api/issues/868 for more information.
    60  func ValidateGateway(gw *gatewayv1b1.Gateway) field.ErrorList {
    61  	return ValidateGatewaySpec(&gw.Spec, field.NewPath("spec"))
    62  }
    63  
    64  // ValidateGatewaySpec validates whether required fields of spec are set according to the
    65  // Gateway API specification.
    66  func ValidateGatewaySpec(spec *gatewayv1b1.GatewaySpec, path *field.Path) field.ErrorList {
    67  	var errs field.ErrorList
    68  	errs = append(errs, validateGatewayListeners(spec.Listeners, path.Child("listeners"))...)
    69  	errs = append(errs, validateGatewayAddresses(spec.Addresses, path.Child("addresses"))...)
    70  	return errs
    71  }
    72  
    73  // validateGatewayListeners validates whether required fields of listeners are set according
    74  // to the Gateway API specification.
    75  func validateGatewayListeners(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
    76  	var errs field.ErrorList
    77  	errs = append(errs, ValidateListenerTLSConfig(listeners, path)...)
    78  	errs = append(errs, validateListenerHostname(listeners, path)...)
    79  	errs = append(errs, ValidateTLSCertificateRefs(listeners, path)...)
    80  	errs = append(errs, ValidateListenerNames(listeners, path)...)
    81  	errs = append(errs, validateHostnameProtocolPort(listeners, path)...)
    82  	return errs
    83  }
    84  
    85  // ValidateListenerTLSConfig validates TLS config must be set when protocol is HTTPS or TLS,
    86  // and TLS config shall not be present when protocol is HTTP, TCP or UDP
    87  func ValidateListenerTLSConfig(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
    88  	var errs field.ErrorList
    89  	for i, l := range listeners {
    90  		if isProtocolInSubset(l.Protocol, protocolsTLSRequired) && l.TLS == nil {
    91  			errs = append(errs, field.Forbidden(path.Index(i).Child("tls"), fmt.Sprintf("must be set for protocol %v", l.Protocol)))
    92  		}
    93  		if isProtocolInSubset(l.Protocol, protocolsTLSInvalid) && l.TLS != nil {
    94  			errs = append(errs, field.Forbidden(path.Index(i).Child("tls"), fmt.Sprintf("should be empty for protocol %v", l.Protocol)))
    95  		}
    96  	}
    97  	return errs
    98  }
    99  
   100  func isProtocolInSubset(protocol gatewayv1b1.ProtocolType, set map[gatewayv1b1.ProtocolType]struct{}) bool {
   101  	_, ok := set[protocol]
   102  	return ok
   103  }
   104  
   105  // validateListenerHostname validates each listener hostname
   106  // should be empty in case protocol is TCP or UDP
   107  func validateListenerHostname(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
   108  	var errs field.ErrorList
   109  	for i, h := range listeners {
   110  		if isProtocolInSubset(h.Protocol, protocolsHostnameInvalid) && h.Hostname != nil {
   111  			errs = append(errs, field.Forbidden(path.Index(i).Child("hostname"), fmt.Sprintf("should be empty for protocol %v", h.Protocol)))
   112  		}
   113  	}
   114  	return errs
   115  }
   116  
   117  // ValidateTLSCertificateRefs validates the certificateRefs
   118  // must be set and not empty when tls config is set and
   119  // TLSModeType is terminate
   120  func ValidateTLSCertificateRefs(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
   121  	var errs field.ErrorList
   122  	for i, c := range listeners {
   123  		if isProtocolInSubset(c.Protocol, protocolsTLSRequired) && c.TLS != nil {
   124  			if *c.TLS.Mode == gatewayv1.TLSModeTerminate && len(c.TLS.CertificateRefs) == 0 {
   125  				errs = append(errs, field.Forbidden(path.Index(i).Child("tls").Child("certificateRefs"), "should be set and not empty when TLSModeType is Terminate"))
   126  			}
   127  		}
   128  	}
   129  	return errs
   130  }
   131  
   132  // ValidateListenerNames validates the names of the listeners
   133  // must be unique within the Gateway
   134  func ValidateListenerNames(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
   135  	var errs field.ErrorList
   136  	nameMap := make(map[gatewayv1b1.SectionName]struct{}, len(listeners))
   137  	for i, c := range listeners {
   138  		if _, found := nameMap[c.Name]; found {
   139  			errs = append(errs, field.Duplicate(path.Index(i).Child("name"), "must be unique within the Gateway"))
   140  		}
   141  		nameMap[c.Name] = struct{}{}
   142  	}
   143  	return errs
   144  }
   145  
   146  // validateHostnameProtocolPort validates that the combination of port, protocol, and hostname are
   147  // unique for each listener.
   148  func validateHostnameProtocolPort(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
   149  	var errs field.ErrorList
   150  	hostnameProtocolPortSets := sets.Set[string]{}
   151  	for i, listener := range listeners {
   152  		hostname := new(gatewayv1b1.Hostname)
   153  		if listener.Hostname != nil {
   154  			hostname = listener.Hostname
   155  		}
   156  		protocol := listener.Protocol
   157  		port := listener.Port
   158  		hostnameProtocolPort := fmt.Sprintf("%s:%s:%d", *hostname, protocol, port)
   159  		if hostnameProtocolPortSets.Has(hostnameProtocolPort) {
   160  			errs = append(errs, field.Duplicate(path.Index(i), "combination of port, protocol, and hostname must be unique for each listener"))
   161  		} else {
   162  			hostnameProtocolPortSets.Insert(hostnameProtocolPort)
   163  		}
   164  	}
   165  	return errs
   166  }
   167  
   168  // validateGatewayAddresses validates whether fields of addresses are set according
   169  // to the Gateway API specification.
   170  func validateGatewayAddresses(addresses []gatewayv1b1.GatewayAddress, path *field.Path) field.ErrorList {
   171  	var errs field.ErrorList
   172  	ipAddrSet, hostnameAddrSet := sets.Set[string]{}, sets.Set[string]{}
   173  	for i, address := range addresses {
   174  		if address.Type != nil {
   175  			if *address.Type == gatewayv1b1.IPAddressType {
   176  				if _, err := netip.ParseAddr(address.Value); err != nil {
   177  					errs = append(errs, field.Invalid(path.Index(i), address.Value, "invalid ip address"))
   178  				}
   179  				if ipAddrSet.Has(address.Value) {
   180  					errs = append(errs, field.Duplicate(path.Index(i), address.Value))
   181  				} else {
   182  					ipAddrSet.Insert(address.Value)
   183  				}
   184  			} else if *address.Type == gatewayv1b1.HostnameAddressType {
   185  				if !validHostnameRegexp.MatchString(address.Value) {
   186  					errs = append(errs, field.Invalid(path.Index(i), address.Value, fmt.Sprintf("must only contain valid characters (matching %s)", validHostnameAddress)))
   187  				}
   188  				if hostnameAddrSet.Has(address.Value) {
   189  					errs = append(errs, field.Duplicate(path.Index(i), address.Value))
   190  				} else {
   191  					hostnameAddrSet.Insert(address.Value)
   192  				}
   193  			}
   194  		}
   195  	}
   196  	return errs
   197  }
   198  

View as plain text