sigs.k8s.io/gateway-api@v1.0.0/apis/v1/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 ) 29 30 var ( 31 // set of protocols for which we need to validate that hostname is empty 32 protocolsHostnameInvalid = map[gatewayv1.ProtocolType]struct{}{ 33 gatewayv1.TCPProtocolType: {}, 34 gatewayv1.UDPProtocolType: {}, 35 } 36 // set of protocols for which TLSConfig shall not be present 37 protocolsTLSInvalid = map[gatewayv1.ProtocolType]struct{}{ 38 gatewayv1.HTTPProtocolType: {}, 39 gatewayv1.UDPProtocolType: {}, 40 gatewayv1.TCPProtocolType: {}, 41 } 42 // set of protocols for which TLSConfig must be set 43 protocolsTLSRequired = map[gatewayv1.ProtocolType]struct{}{ 44 gatewayv1.HTTPSProtocolType: {}, 45 gatewayv1.TLSProtocolType: {}, 46 } 47 48 validHostnameAddress = `^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` 49 validHostnameRegexp = regexp.MustCompile(validHostnameAddress) 50 ) 51 52 // ValidateGateway validates gw according to the Gateway API specification. 53 // For additional details of the Gateway spec, refer to: 54 // 55 // https://gateway-api.sigs.k8s.io/v1beta1/references/spec/#gateway.networking.k8s.io/v1beta1.Gateway 56 // 57 // Validation that is not possible with CRD annotations may be added here in the future. 58 // See https://github.com/kubernetes-sigs/gateway-api/issues/868 for more information. 59 func ValidateGateway(gw *gatewayv1.Gateway) field.ErrorList { 60 return ValidateGatewaySpec(&gw.Spec, field.NewPath("spec")) 61 } 62 63 // ValidateGatewaySpec validates whether required fields of spec are set according to the 64 // Gateway API specification. 65 func ValidateGatewaySpec(spec *gatewayv1.GatewaySpec, path *field.Path) field.ErrorList { 66 var errs field.ErrorList 67 errs = append(errs, validateGatewayListeners(spec.Listeners, path.Child("listeners"))...) 68 errs = append(errs, validateGatewayAddresses(spec.Addresses, path.Child("addresses"))...) 69 return errs 70 } 71 72 // validateGatewayListeners validates whether required fields of listeners are set according 73 // to the Gateway API specification. 74 func validateGatewayListeners(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 75 var errs field.ErrorList 76 errs = append(errs, ValidateListenerTLSConfig(listeners, path)...) 77 errs = append(errs, validateListenerHostname(listeners, path)...) 78 errs = append(errs, ValidateTLSCertificateRefs(listeners, path)...) 79 errs = append(errs, ValidateListenerNames(listeners, path)...) 80 errs = append(errs, validateHostnameProtocolPort(listeners, path)...) 81 return errs 82 } 83 84 // ValidateListenerTLSConfig validates TLS config must be set when protocol is HTTPS or TLS, 85 // and TLS config shall not be present when protocol is HTTP, TCP or UDP 86 func ValidateListenerTLSConfig(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 87 var errs field.ErrorList 88 for i, l := range listeners { 89 if isProtocolInSubset(l.Protocol, protocolsTLSRequired) && l.TLS == nil { 90 errs = append(errs, field.Forbidden(path.Index(i).Child("tls"), fmt.Sprintf("must be set for protocol %v", l.Protocol))) 91 } 92 if isProtocolInSubset(l.Protocol, protocolsTLSInvalid) && l.TLS != nil { 93 errs = append(errs, field.Forbidden(path.Index(i).Child("tls"), fmt.Sprintf("should be empty for protocol %v", l.Protocol))) 94 } 95 } 96 return errs 97 } 98 99 func isProtocolInSubset(protocol gatewayv1.ProtocolType, set map[gatewayv1.ProtocolType]struct{}) bool { 100 _, ok := set[protocol] 101 return ok 102 } 103 104 // validateListenerHostname validates each listener hostname 105 // should be empty in case protocol is TCP or UDP 106 func validateListenerHostname(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 107 var errs field.ErrorList 108 for i, h := range listeners { 109 if isProtocolInSubset(h.Protocol, protocolsHostnameInvalid) && h.Hostname != nil { 110 errs = append(errs, field.Forbidden(path.Index(i).Child("hostname"), fmt.Sprintf("should be empty for protocol %v", h.Protocol))) 111 } 112 } 113 return errs 114 } 115 116 // ValidateTLSCertificateRefs validates the certificateRefs 117 // must be set and not empty when tls config is set and 118 // TLSModeType is terminate 119 func ValidateTLSCertificateRefs(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 120 var errs field.ErrorList 121 for i, c := range listeners { 122 if isProtocolInSubset(c.Protocol, protocolsTLSRequired) && c.TLS != nil { 123 if *c.TLS.Mode == gatewayv1.TLSModeTerminate && len(c.TLS.CertificateRefs) == 0 { 124 errs = append(errs, field.Forbidden(path.Index(i).Child("tls").Child("certificateRefs"), "should be set and not empty when TLSModeType is Terminate")) 125 } 126 } 127 } 128 return errs 129 } 130 131 // ValidateListenerNames validates the names of the listeners 132 // must be unique within the Gateway 133 func ValidateListenerNames(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 134 var errs field.ErrorList 135 nameMap := make(map[gatewayv1.SectionName]struct{}, len(listeners)) 136 for i, c := range listeners { 137 if _, found := nameMap[c.Name]; found { 138 errs = append(errs, field.Duplicate(path.Index(i).Child("name"), "must be unique within the Gateway")) 139 } 140 nameMap[c.Name] = struct{}{} 141 } 142 return errs 143 } 144 145 // validateHostnameProtocolPort validates that the combination of port, protocol, and hostname are 146 // unique for each listener. 147 func validateHostnameProtocolPort(listeners []gatewayv1.Listener, path *field.Path) field.ErrorList { 148 var errs field.ErrorList 149 hostnameProtocolPortSets := sets.Set[string]{} 150 for i, listener := range listeners { 151 hostname := new(gatewayv1.Hostname) 152 if listener.Hostname != nil { 153 hostname = listener.Hostname 154 } 155 protocol := listener.Protocol 156 port := listener.Port 157 hostnameProtocolPort := fmt.Sprintf("%s:%s:%d", *hostname, protocol, port) 158 if hostnameProtocolPortSets.Has(hostnameProtocolPort) { 159 errs = append(errs, field.Duplicate(path.Index(i), "combination of port, protocol, and hostname must be unique for each listener")) 160 } else { 161 hostnameProtocolPortSets.Insert(hostnameProtocolPort) 162 } 163 } 164 return errs 165 } 166 167 // validateGatewayAddresses validates whether fields of addresses are set according 168 // to the Gateway API specification. 169 func validateGatewayAddresses(addresses []gatewayv1.GatewayAddress, path *field.Path) field.ErrorList { 170 var errs field.ErrorList 171 ipAddrSet, hostnameAddrSet := sets.Set[string]{}, sets.Set[string]{} 172 for i, address := range addresses { 173 if address.Type != nil { 174 if *address.Type == gatewayv1.IPAddressType { 175 if _, err := netip.ParseAddr(address.Value); err != nil { 176 errs = append(errs, field.Invalid(path.Index(i), address.Value, "invalid ip address")) 177 } 178 if ipAddrSet.Has(address.Value) { 179 errs = append(errs, field.Duplicate(path.Index(i), address.Value)) 180 } else { 181 ipAddrSet.Insert(address.Value) 182 } 183 } else if *address.Type == gatewayv1.HostnameAddressType { 184 if !validHostnameRegexp.MatchString(address.Value) { 185 errs = append(errs, field.Invalid(path.Index(i), address.Value, fmt.Sprintf("must only contain valid characters (matching %s)", validHostnameAddress))) 186 } 187 if hostnameAddrSet.Has(address.Value) { 188 errs = append(errs, field.Duplicate(path.Index(i), address.Value)) 189 } else { 190 hostnameAddrSet.Insert(address.Value) 191 } 192 } 193 } 194 } 195 return errs 196 }