sigs.k8s.io/gateway-api@v1.0.0/apis/v1beta1/validation/gateway.go (about) 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 }