istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/gateway.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "fmt" 19 "strconv" 20 "strings" 21 22 networking "istio.io/api/networking/v1alpha3" 23 "istio.io/istio/pilot/pkg/features" 24 "istio.io/istio/pilot/pkg/model/credentials" 25 "istio.io/istio/pkg/config" 26 "istio.io/istio/pkg/config/gateway" 27 "istio.io/istio/pkg/config/protocol" 28 "istio.io/istio/pkg/config/schema/gvk" 29 "istio.io/istio/pkg/monitoring" 30 "istio.io/istio/pkg/util/sets" 31 ) 32 33 // ServerPort defines port for the gateway server. 34 type ServerPort struct { 35 // A valid non-negative integer port number. 36 Number uint32 37 // The protocol exposed on the port. 38 Protocol string 39 // The bind server specified on this port. 40 Bind string 41 } 42 43 // MergedServers describes set of servers defined in all gateways per port. 44 type MergedServers struct { 45 Servers []*networking.Server 46 RouteName string // RouteName for http servers. For HTTPS, TLSServerInfo will hold the route name. 47 } 48 49 // TLSServerInfo contains additional information for TLS Servers. 50 type TLSServerInfo struct { 51 RouteName string 52 SNIHosts []string 53 } 54 55 // MergedGateway describes a set of gateways for a workload merged into a single logical gateway. 56 type MergedGateway struct { 57 // ServerPorts maintains a list of unique server ports, used for stable ordering. 58 ServerPorts []ServerPort 59 60 // MergedServers map from physical port to virtual servers 61 // using TCP protocols (like HTTP1.1, H2, mysql, redis etc) 62 MergedServers map[ServerPort]*MergedServers 63 64 // MergedQUICTransportServers map from physical port to servers listening 65 // on QUIC (like HTTP3). Currently the support is experimental and 66 // is limited to HTTP3 only 67 MergedQUICTransportServers map[ServerPort]*MergedServers 68 69 // HTTP3AdvertisingRoutes represents the set of HTTP routes which advertise HTTP/3. 70 // This mapping is used to generate alt-svc header that is needed for HTTP/3 server discovery. 71 HTTP3AdvertisingRoutes sets.String 72 73 // GatewayNameForServer maps from server to the owning gateway name. 74 // Used for select the set of virtual services that apply to a port. 75 GatewayNameForServer map[*networking.Server]string 76 77 // ServersByRouteName maps from port names to virtual hosts 78 // Used for RDS. No two port names share same port except for HTTPS 79 // The typical length of the value is always 1, except for HTTP (not HTTPS), 80 ServersByRouteName map[string][]*networking.Server 81 82 // TLSServerInfo maps from server to a corresponding TLS information like TLS Routename and SNIHosts. 83 TLSServerInfo map[*networking.Server]*TLSServerInfo 84 85 // ContainsAutoPassthroughGateways determines if there are any type AUTO_PASSTHROUGH Gateways, requiring additional 86 // clusters to be sent to the workload 87 ContainsAutoPassthroughGateways bool 88 89 // PortMap defines a mapping of targetPorts to the set of Service ports that reference them 90 PortMap GatewayPortMap 91 92 // VerifiedCertificateReferences contains a set of all credentialNames referenced by gateways *in the same namespace as the proxy*. 93 // These are considered "verified", since there is mutually agreement from the pod, Secret, and Gateway, as all 94 // reside in the same namespace and trust boundary. 95 // Note: Secrets that are not referenced by any Gateway, but are in the same namespace as the pod, are explicitly *not* 96 // included. This ensures we don't give permission to unexpected secrets, such as the citadel root key/cert. 97 VerifiedCertificateReferences sets.String 98 } 99 100 func (g *MergedGateway) HasAutoPassthroughGateways() bool { 101 if g != nil { 102 return g.ContainsAutoPassthroughGateways 103 } 104 return false 105 } 106 107 // PrevMergedGateway describes previous state of the gateway. 108 // Currently, it only contains information relevant for CDS. 109 type PrevMergedGateway struct { 110 ContainsAutoPassthroughGateways bool 111 AutoPassthroughSNIHosts sets.Set[string] 112 } 113 114 func (g *PrevMergedGateway) HasAutoPassthroughGateway() bool { 115 if g != nil { 116 return g.ContainsAutoPassthroughGateways 117 } 118 return false 119 } 120 121 func (g *PrevMergedGateway) GetAutoPassthroughSNIHosts() sets.Set[string] { 122 if g != nil { 123 return g.AutoPassthroughSNIHosts 124 } 125 return sets.Set[string]{} 126 } 127 128 var ( 129 typeTag = monitoring.CreateLabel("type") 130 nameTag = monitoring.CreateLabel("name") 131 132 totalRejectedConfigs = monitoring.NewSum( 133 "pilot_total_rejected_configs", 134 "Total number of configs that Pilot had to reject or ignore.", 135 ) 136 ) 137 138 func RecordRejectedConfig(gatewayName string) { 139 totalRejectedConfigs.With(typeTag.Value("gateway"), nameTag.Value(gatewayName)).Increment() 140 } 141 142 // DisableGatewayPortTranslationLabel is a label on Service that declares that, for that particular 143 // service, we should not translate Gateway ports to target ports. For example, if I have a Service 144 // on port 80 with target port 8080, with the label. Gateways on port 80 would *not* match. Instead, 145 // only Gateways on port 8080 would be used. This prevents ambiguities when there are multiple 146 // Services on port 80 referring to different target ports. Long term, this will be replaced by 147 // Gateways directly referencing a Service, rather than label selectors. Warning: this label is 148 // intended solely for as a workaround for Knative's Istio integration, and not intended for any 149 // other usage. It can, and will, be removed immediately after the new direct reference is ready for 150 // use. 151 const DisableGatewayPortTranslationLabel = "experimental.istio.io/disable-gateway-port-translation" 152 153 // MergeGateways combines multiple gateways targeting the same workload into a single logical Gateway. 154 // Note that today any Servers in the combined gateways listening on the same port must have the same protocol. 155 // If servers with different protocols attempt to listen on the same port, one of the protocols will be chosen at random. 156 func MergeGateways(gateways []gatewayWithInstances, proxy *Proxy, ps *PushContext) *MergedGateway { 157 gatewayPorts := sets.New[uint32]() 158 nonPlainTextGatewayPortsBindMap := map[uint32]sets.String{} 159 mergedServers := make(map[ServerPort]*MergedServers) 160 mergedQUICServers := make(map[ServerPort]*MergedServers) 161 serverPorts := make([]ServerPort, 0) 162 plainTextServers := make(map[uint32]ServerPort) 163 serversByRouteName := make(map[string][]*networking.Server) 164 tlsServerInfo := make(map[*networking.Server]*TLSServerInfo) 165 gatewayNameForServer := make(map[*networking.Server]string) 166 verifiedCertificateReferences := sets.New[string]() 167 http3AdvertisingRoutes := sets.New[string]() 168 tlsHostsByPort := map[uint32]map[string]string{} // port -> host/bind map 169 autoPassthrough := false 170 171 log.Debugf("MergeGateways: merging %d gateways", len(gateways)) 172 for _, gwAndInstance := range gateways { 173 gatewayConfig := gwAndInstance.gateway 174 gatewayName := gatewayConfig.Namespace + "/" + gatewayConfig.Name // Format: %s/%s 175 gatewayCfg := gatewayConfig.Spec.(*networking.Gateway) 176 log.Debugf("MergeGateways: merging gateway %q :\n%v", gatewayName, gatewayCfg) 177 snames := sets.String{} 178 for _, s := range gatewayCfg.Servers { 179 if len(s.Name) > 0 { 180 if snames.InsertContains(s.Name) { 181 log.Warnf("Server name %s is not unique in gateway %s and may create possible issues like stat prefix collision ", 182 s.Name, gatewayName) 183 } 184 } 185 if s.Port == nil { 186 // Should be rejected in validation, this is an extra check 187 log.Debugf("invalid server without port: %q", gatewayName) 188 RecordRejectedConfig(gatewayName) 189 continue 190 } 191 sanitizeServerHostNamespace(s, gatewayConfig.Namespace) 192 gatewayNameForServer[s] = gatewayName 193 log.Debugf("MergeGateways: gateway %q processing server %s :%v", gatewayName, s.Name, s.Hosts) 194 195 cn := s.GetTls().GetCredentialName() 196 if cn != "" && proxy.VerifiedIdentity != nil { 197 rn := credentials.ToResourceName(cn) 198 parse, _ := credentials.ParseResourceName(rn, proxy.VerifiedIdentity.Namespace, "", "") 199 if gatewayConfig.Namespace == proxy.VerifiedIdentity.Namespace && parse.Namespace == proxy.VerifiedIdentity.Namespace { 200 // Same namespace is always allowed 201 verifiedCertificateReferences.Insert(rn) 202 if s.GetTls().GetMode() == networking.ServerTLSSettings_MUTUAL { 203 verifiedCertificateReferences.Insert(rn + credentials.SdsCaSuffix) 204 } 205 } else if ps.ReferenceAllowed(gvk.Secret, rn, proxy.VerifiedIdentity.Namespace) { 206 // Explicitly allowed by some policy 207 verifiedCertificateReferences.Insert(rn) 208 } 209 } 210 for _, resolvedPort := range resolvePorts(s.Port.Number, gwAndInstance.instances, gwAndInstance.legacyGatewaySelector) { 211 routeName := gatewayRDSRouteName(s, resolvedPort, gatewayConfig) 212 if s.Tls != nil { 213 // Envoy will reject config that has multiple filter chain matches with the same matching rules. 214 // To avoid this, we need to make sure we don't have duplicated hosts, which will become 215 // SNI filter chain matches. 216 217 // When there is Bind specified in the Gateway, the listener is built per IP instead of 218 // sharing one wildcard listener. So different Gateways can 219 // have same host as long as they have different Bind. 220 if tlsHostsByPort[resolvedPort] == nil { 221 tlsHostsByPort[resolvedPort] = map[string]string{} 222 } 223 if duplicateHosts := CheckDuplicates(s.Hosts, s.Bind, tlsHostsByPort[resolvedPort]); len(duplicateHosts) != 0 { 224 log.Warnf("skipping server on gateway %s, duplicate host names: %v", gatewayName, duplicateHosts) 225 RecordRejectedConfig(gatewayName) 226 continue 227 } 228 tlsServerInfo[s] = &TLSServerInfo{SNIHosts: GetSNIHostsForServer(s), RouteName: routeName} 229 if s.Tls.Mode == networking.ServerTLSSettings_AUTO_PASSTHROUGH { 230 autoPassthrough = true 231 } 232 } 233 serverPort := ServerPort{resolvedPort, s.Port.Protocol, s.Bind} 234 serverProtocol := protocol.Parse(serverPort.Protocol) 235 if gatewayPorts.Contains(resolvedPort) { 236 // We have two servers on the same port. Should we merge? 237 // 1. Yes if both servers are plain text and HTTP 238 // 2. Yes if both servers are using TLS 239 // if using HTTPS ensure that port name is distinct so that we can setup separate RDS 240 // for each server (as each server ends up as a separate http connection manager due to filter chain match) 241 // 3. No for everything else. 242 if current, exists := plainTextServers[resolvedPort]; exists { 243 if !canMergeProtocols(serverProtocol, protocol.Parse(current.Protocol)) && current.Bind == serverPort.Bind { 244 log.Infof("skipping server on gateway %s port %s.%d.%s: conflict with existing server %d.%s", 245 gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol, serverPort.Number, serverPort.Protocol) 246 RecordRejectedConfig(gatewayName) 247 continue 248 } 249 // For TCP gateway/route the route name is empty but if they are different binds, should continue to generate the listener 250 // i.e gateway 10.0.0.1:8000:TCP should not conflict with 10.0.0.2:8000:TCP 251 if routeName == "" && current.Bind == serverPort.Bind { 252 log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server", 253 gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol) 254 RecordRejectedConfig(gatewayName) 255 continue 256 } 257 if current.Bind != serverPort.Bind { 258 // Merge it to servers with the same port and bind. 259 if mergedServers[serverPort] == nil { 260 mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{}} 261 serverPorts = append(serverPorts, serverPort) 262 } 263 ms := mergedServers[serverPort] 264 ms.RouteName = routeName 265 ms.Servers = append(ms.Servers, s) 266 } else { 267 // Merge this to current known port with same bind. 268 ms := mergedServers[current] 269 ms.Servers = append(ms.Servers, s) 270 } 271 serversByRouteName[routeName] = append(serversByRouteName[routeName], s) 272 } else { 273 // We have duplicate port. Its not in plaintext servers. So, this has to be a TLS server. 274 // Check if this is also a HTTP server and if so, ensure uniqueness of port name. 275 if gateway.IsHTTPServer(s) { 276 if routeName == "" { 277 log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server", 278 gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol) 279 RecordRejectedConfig(gatewayName) 280 continue 281 } 282 283 // Both servers are HTTPS servers. Make sure the port names are different so that RDS can pick out individual servers. 284 // We cannot have two servers with same port name because we need the port name to distinguish one HTTPS server from another. 285 // We cannot merge two HTTPS servers even if their TLS settings have same path to the keys, because we don't know if the contents 286 // of the keys are same. So we treat them as effectively different TLS settings. 287 // This check is largely redundant now since we create rds names for https using gateway name, namespace 288 // and validation ensures that all port names within a single gateway config are unique. 289 if _, exists := serversByRouteName[routeName]; exists { 290 log.Infof("skipping server on gateway %s port %s.%d.%s: non unique port name for HTTPS port", 291 gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol) 292 RecordRejectedConfig(gatewayName) 293 continue 294 } 295 serversByRouteName[routeName] = []*networking.Server{s} 296 } 297 // build the port bind map for none plain text protocol, thus can avoid protocol conflict if it's different bind 298 var newBind bool 299 if bindsPortMap, ok := nonPlainTextGatewayPortsBindMap[resolvedPort]; ok { 300 newBind = !bindsPortMap.InsertContains(serverPort.Bind) 301 } else { 302 nonPlainTextGatewayPortsBindMap[resolvedPort] = sets.New(serverPort.Bind) 303 newBind = true 304 } 305 // If the bind/port combination is not being used as non-plaintext, they are different 306 // listeners and won't get conflicted even with same port different protocol 307 // i.e 0.0.0.0:443:GRPC/1.0.0.1:443:GRPC/1.0.0.2:443:HTTPS they are not conflicted, otherwise 308 // We have another TLS server on the same port. Can differentiate servers using SNI 309 if s.Tls == nil && !newBind { 310 log.Warnf("TLS server without TLS options %s %s", gatewayName, s.String()) 311 RecordRejectedConfig(gatewayName) 312 continue 313 } 314 if mergedServers[serverPort] == nil { 315 mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}} 316 serverPorts = append(serverPorts, serverPort) 317 } else { 318 mergedServers[serverPort].Servers = append(mergedServers[serverPort].Servers, s) 319 } 320 321 // We have TLS settings defined and we have already taken care of unique route names 322 // if it is HTTPS. So we can construct a QUIC server on the same port. It is okay as 323 // QUIC listens on UDP port, not TCP 324 if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) && 325 udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) { 326 log.Debugf("Server at port %d eligible for HTTP3 upgrade. Add UDP listener for QUIC", serverPort.Number) 327 if mergedQUICServers[serverPort] == nil { 328 mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{}} 329 } 330 mergedQUICServers[serverPort].Servers = append(mergedQUICServers[serverPort].Servers, s) 331 http3AdvertisingRoutes.Insert(routeName) 332 } 333 } 334 } else { 335 // This is a new gateway on this port. Create MergedServers for it. 336 gatewayPorts.Insert(resolvedPort) 337 if !gateway.IsTLSServer(s) { 338 plainTextServers[serverPort.Number] = serverPort 339 } 340 if gateway.IsHTTPServer(s) { 341 serversByRouteName[routeName] = []*networking.Server{s} 342 343 if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) && 344 udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) { 345 log.Debugf("Server at port %d eligible for HTTP3 upgrade. So QUIC listener will be added", serverPort.Number) 346 http3AdvertisingRoutes.Insert(routeName) 347 348 if mergedQUICServers[serverPort] == nil { 349 // This should be treated like non-passthrough HTTPS case. There will be multiple filter 350 // chains, multiple routes per server port. So just like in TLS server case we do not 351 // track route name here. Instead, TLS server info is used (it is fine for now because 352 // this would be a mirror of an existing non-passthrough HTTPS server) 353 mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}} 354 } 355 } 356 } 357 mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}, RouteName: routeName} 358 serverPorts = append(serverPorts, serverPort) 359 } 360 log.Debugf("MergeGateways: gateway %q merged server %v", gatewayName, s.Hosts) 361 } 362 } 363 } 364 365 return &MergedGateway{ 366 MergedServers: mergedServers, 367 MergedQUICTransportServers: mergedQUICServers, 368 ServerPorts: serverPorts, 369 GatewayNameForServer: gatewayNameForServer, 370 TLSServerInfo: tlsServerInfo, 371 ServersByRouteName: serversByRouteName, 372 HTTP3AdvertisingRoutes: http3AdvertisingRoutes, 373 ContainsAutoPassthroughGateways: autoPassthrough, 374 PortMap: getTargetPortMap(serversByRouteName), 375 VerifiedCertificateReferences: verifiedCertificateReferences, 376 } 377 } 378 379 func (g *MergedGateway) GetAutoPassthrughGatewaySNIHosts() sets.Set[string] { 380 hosts := sets.Set[string]{} 381 if g == nil { 382 return hosts 383 } 384 if g.ContainsAutoPassthroughGateways { 385 for _, tls := range g.MergedServers { 386 for _, s := range tls.Servers { 387 if s.GetTls().GetMode() == networking.ServerTLSSettings_AUTO_PASSTHROUGH { 388 hosts.InsertAll(s.Hosts...) 389 } 390 } 391 } 392 } 393 return hosts 394 } 395 396 func udpSupportedPort(number uint32, instances []ServiceTarget) bool { 397 for _, w := range instances { 398 if int(number) == w.Port.Port && w.Port.Protocol == protocol.UDP { 399 return true 400 } 401 } 402 return false 403 } 404 405 // resolvePorts takes a Gateway port, and resolves it to the port that will actually be listened on. 406 // When legacyGatewaySelector=false, then the gateway is directly referencing a Service. In this 407 // case, the translation is un-ambiguous - we just find the matching port and return the targetPort 408 // When legacyGatewaySelector=true things are a bit more complex, as we support referencing a Service 409 // port and translating to the targetPort in addition to just directly referencing a port. In this 410 // case, we just make a best effort guess by picking the first match. 411 func resolvePorts(number uint32, instances []ServiceTarget, legacyGatewaySelector bool) []uint32 { 412 ports := sets.New[uint32]() 413 for _, w := range instances { 414 if _, disablePortTranslation := w.Service.Attributes.Labels[DisableGatewayPortTranslationLabel]; disablePortTranslation && legacyGatewaySelector { 415 // Skip this Service, they opted out of port translation 416 // This is only done for legacyGatewaySelector, as the new gateway selection mechanism *only* allows 417 // referencing the Service port, and references are un-ambiguous. 418 continue 419 } 420 if w.Port.Port == int(number) { 421 if legacyGatewaySelector { 422 // When we are using legacy gateway label selection, we only resolve to a single port 423 // This has pros and cons; we don't allow merging of routes when it would be desirable, but 424 // we also avoid accidentally merging routes when we didn't intend to. While neither option is great, 425 // picking the first one here preserves backwards compatibility. 426 return []uint32{w.Port.TargetPort} 427 } 428 ports.Insert(w.Port.TargetPort) 429 } 430 } 431 ret := ports.UnsortedList() 432 if len(ret) == 0 && legacyGatewaySelector { 433 // When we are using legacy gateway label selection, we should bind to the port as-is if there is 434 // no matching ServiceInstance. 435 return []uint32{number} 436 } 437 // For cases where we are directly referencing a Service, we know that they port *must* be in the Service, 438 // so we have no fallback. If there was no match, the Gateway is a no-op. 439 return ret 440 } 441 442 func canMergeProtocols(current protocol.Instance, p protocol.Instance) bool { 443 return (current.IsHTTP() || current == p) && p.IsHTTP() 444 } 445 446 func GetSNIHostsForServer(server *networking.Server) []string { 447 if server.Tls == nil { 448 return nil 449 } 450 // sanitize the server hosts as it could contain hosts of form ns/host 451 sniHosts := sets.String{} 452 for _, h := range server.Hosts { 453 if strings.Contains(h, "/") { 454 parts := strings.Split(h, "/") 455 h = parts[1] 456 } 457 // do not add hosts, that have already been added 458 sniHosts.Insert(h) 459 } 460 return sets.SortedList(sniHosts) 461 } 462 463 // CheckDuplicates returns all of the hosts provided that are already known 464 // If there were no duplicates, all hosts are added to the known hosts. 465 func CheckDuplicates(hosts []string, bind string, knownHosts map[string]string) []string { 466 var duplicates []string 467 for _, h := range hosts { 468 if existingBind, ok := knownHosts[h]; ok && bind == existingBind { 469 duplicates = append(duplicates, h) 470 } 471 } 472 // No duplicates found, so we can mark all of these hosts as known 473 if len(duplicates) == 0 { 474 for _, h := range hosts { 475 knownHosts[h] = bind 476 } 477 } 478 return duplicates 479 } 480 481 // gatewayRDSRouteName generates the RDS route config name for gateway's servers. 482 // Unlike sidecars where the RDS route name is the listener port number, gateways have a different 483 // structure for RDS. 484 // HTTP servers have route name set to http.<portNumber>. 485 // 486 // Multiple HTTP servers can exist on the same port and the code will combine all of them into 487 // one single RDS payload for http.<portNumber> 488 // 489 // HTTPS servers with TLS termination (i.e. envoy decoding the content, and making outbound http calls to backends) 490 // will use route name https.<portNumber>.<portName>.<gatewayName>.<namespace>. HTTPS servers using SNI passthrough or 491 // non-HTTPS servers (e.g., TCP+TLS) with SNI passthrough will be setup as opaque TCP proxies without terminating 492 // the SSL connection. They would inspect the SNI header and forward to the appropriate upstream as opaque TCP. 493 // 494 // Within HTTPS servers terminating TLS, user could setup multiple servers in the gateway. each server could have 495 // one or more hosts but have different TLS certificates. In this case, we end up having separate filter chain 496 // for each server, with the filter chain match matching on the server specific TLS certs and SNI headers. 497 // We have two options here: either have all filter chains use the same RDS route name (e.g. "443") and expose 498 // all virtual hosts on that port to every filter chain uniformly or expose only the set of virtual hosts 499 // configured under the server for those certificates. We adopt the latter approach. In other words, each 500 // filter chain in the multi-filter-chain listener will have a distinct RDS route name 501 // (https.<portNumber>.<portName>.<gatewayName>.<namespace>) so that when a RDS request comes in, we serve the virtual 502 // hosts and associated routes for that server. 503 // 504 // Note that the common case is one where multiple servers are exposed under a single multi-SAN cert on a single port. 505 // In this case, we have a single https.<portNumber>.<portName>.<gatewayName>.<namespace> RDS for the HTTPS server. 506 // While we can use the same RDS route name for two servers (say HTTP and HTTPS) exposing the same set of hosts on 507 // different ports, the optimization (one RDS instead of two) could quickly become useless the moment the set of 508 // hosts on the two servers start differing -- necessitating the need for two different RDS routes. 509 func gatewayRDSRouteName(server *networking.Server, portNumber uint32, cfg config.Config) string { 510 p := protocol.Parse(server.Port.Protocol) 511 bind := "" 512 if server.Bind != "" { 513 bind = "." + server.Bind 514 } 515 if p.IsHTTP() { 516 return "http" + "." + strconv.Itoa(int(portNumber)) + bind // Format: http.%d.%s 517 } 518 519 if p == protocol.HTTPS && !gateway.IsPassThroughServer(server) { 520 return "https" + "." + strconv.Itoa(int(server.Port.Number)) + "." + 521 server.Port.Name + "." + cfg.Name + "." + cfg.Namespace + bind // Format: https.%d.%s.%s.%s.%s 522 } 523 524 return "" 525 } 526 527 // ParseGatewayRDSRouteName is used by the EnvoyFilter patching logic to match 528 // a specific route configuration to patch. 529 func ParseGatewayRDSRouteName(name string) (portNumber int, portName, gatewayName string) { 530 parts := strings.Split(name, ".") 531 if strings.HasPrefix(name, "http.") { 532 // this is a http gateway. Parse port number and return empty string for rest 533 if len(parts) >= 2 { 534 portNumber, _ = strconv.Atoi(parts[1]) 535 } 536 } else if strings.HasPrefix(name, "https.") { 537 if len(parts) >= 5 { 538 portNumber, _ = strconv.Atoi(parts[1]) 539 portName = parts[2] 540 // gateway name should be ns/name 541 gatewayName = parts[4] + "/" + parts[3] 542 } 543 } 544 return 545 } 546 547 // convert ./host to currentNamespace/Host 548 // */host to just host 549 // */* to just * 550 func sanitizeServerHostNamespace(server *networking.Server, namespace string) { 551 for i, h := range server.Hosts { 552 if strings.Contains(h, "/") { 553 parts := strings.Split(h, "/") 554 if parts[0] == "." { 555 server.Hosts[i] = fmt.Sprintf("%s/%s", namespace, parts[1]) 556 } else if parts[0] == "*" { 557 if parts[1] == "*" { 558 server.Hosts = []string{"*"} 559 return 560 } 561 server.Hosts[i] = parts[1] 562 } 563 } 564 } 565 } 566 567 type GatewayPortMap map[int]sets.Set[int] 568 569 func getTargetPortMap(serversByRouteName map[string][]*networking.Server) GatewayPortMap { 570 pm := GatewayPortMap{} 571 for r, s := range serversByRouteName { 572 portNumber, _, _ := ParseGatewayRDSRouteName(r) 573 if _, f := pm[portNumber]; !f { 574 pm[portNumber] = sets.New[int]() 575 } 576 for _, se := range s { 577 if se.Port == nil { 578 continue 579 } 580 pm[portNumber].Insert(int(se.Port.Number)) 581 } 582 } 583 return pm 584 }