sigs.k8s.io/external-dns@v0.14.1/source/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 source 18 19 import ( 20 "context" 21 "fmt" 22 "net/netip" 23 "sort" 24 "strings" 25 "text/template" 26 27 log "github.com/sirupsen/logrus" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/wait" 33 kubeinformers "k8s.io/client-go/informers" 34 coreinformers "k8s.io/client-go/informers/core/v1" 35 cache "k8s.io/client-go/tools/cache" 36 v1 "sigs.k8s.io/gateway-api/apis/v1" 37 gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" 38 informers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" 39 informers_v1 "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions/apis/v1" 40 41 "sigs.k8s.io/external-dns/endpoint" 42 ) 43 44 const ( 45 gatewayGroup = "gateway.networking.k8s.io" 46 gatewayKind = "Gateway" 47 ) 48 49 type gatewayRoute interface { 50 // Object returns the underlying route object to be used by templates. 51 Object() kubeObject 52 // Metadata returns the route's metadata. 53 Metadata() *metav1.ObjectMeta 54 // Hostnames returns the route's specified hostnames. 55 Hostnames() []v1.Hostname 56 // Protocol returns the route's protocol type. 57 Protocol() v1.ProtocolType 58 // RouteStatus returns the route's common status. 59 RouteStatus() v1.RouteStatus 60 } 61 62 type newGatewayRouteInformerFunc func(informers.SharedInformerFactory) gatewayRouteInformer 63 64 type gatewayRouteInformer interface { 65 List(namespace string, selector labels.Selector) ([]gatewayRoute, error) 66 Informer() cache.SharedIndexInformer 67 } 68 69 func newGatewayInformerFactory(client gateway.Interface, namespace string, labelSelector labels.Selector) informers.SharedInformerFactory { 70 var opts []informers.SharedInformerOption 71 if namespace != "" { 72 opts = append(opts, informers.WithNamespace(namespace)) 73 } 74 if labelSelector != nil && !labelSelector.Empty() { 75 lbls := labelSelector.String() 76 opts = append(opts, informers.WithTweakListOptions(func(o *metav1.ListOptions) { 77 o.LabelSelector = lbls 78 })) 79 } 80 return informers.NewSharedInformerFactoryWithOptions(client, 0, opts...) 81 } 82 83 type gatewayRouteSource struct { 84 gwNamespace string 85 gwLabels labels.Selector 86 gwInformer informers_v1.GatewayInformer 87 88 rtKind string 89 rtNamespace string 90 rtLabels labels.Selector 91 rtAnnotations labels.Selector 92 rtInformer gatewayRouteInformer 93 94 nsInformer coreinformers.NamespaceInformer 95 96 fqdnTemplate *template.Template 97 combineFQDNAnnotation bool 98 ignoreHostnameAnnotation bool 99 } 100 101 func newGatewayRouteSource(clients ClientGenerator, config *Config, kind string, newInformerFn newGatewayRouteInformerFunc) (Source, error) { 102 ctx := context.TODO() 103 104 gwLabels, err := getLabelSelector(config.GatewayLabelFilter) 105 if err != nil { 106 return nil, err 107 } 108 rtLabels := config.LabelFilter 109 if rtLabels == nil { 110 rtLabels = labels.Everything() 111 } 112 rtAnnotations, err := getLabelSelector(config.AnnotationFilter) 113 if err != nil { 114 return nil, err 115 } 116 tmpl, err := parseTemplate(config.FQDNTemplate) 117 if err != nil { 118 return nil, err 119 } 120 121 client, err := clients.GatewayClient() 122 if err != nil { 123 return nil, err 124 } 125 126 informerFactory := newGatewayInformerFactory(client, config.GatewayNamespace, gwLabels) 127 gwInformer := informerFactory.Gateway().V1().Gateways() // TODO: Gateway informer should be shared across gateway sources. 128 gwInformer.Informer() // Register with factory before starting. 129 130 rtInformerFactory := informerFactory 131 if config.Namespace != config.GatewayNamespace || !selectorsEqual(rtLabels, gwLabels) { 132 rtInformerFactory = newGatewayInformerFactory(client, config.Namespace, rtLabels) 133 } 134 rtInformer := newInformerFn(rtInformerFactory) 135 rtInformer.Informer() // Register with factory before starting. 136 137 kubeClient, err := clients.KubeClient() 138 if err != nil { 139 return nil, err 140 } 141 142 kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0) 143 nsInformer := kubeInformerFactory.Core().V1().Namespaces() // TODO: Namespace informer should be shared across gateway sources. 144 nsInformer.Informer() // Register with factory before starting. 145 146 informerFactory.Start(wait.NeverStop) 147 kubeInformerFactory.Start(wait.NeverStop) 148 if rtInformerFactory != informerFactory { 149 rtInformerFactory.Start(wait.NeverStop) 150 151 if err := waitForCacheSync(ctx, rtInformerFactory); err != nil { 152 return nil, err 153 } 154 } 155 if err := waitForCacheSync(ctx, informerFactory); err != nil { 156 return nil, err 157 } 158 if err := waitForCacheSync(ctx, kubeInformerFactory); err != nil { 159 return nil, err 160 } 161 162 src := &gatewayRouteSource{ 163 gwNamespace: config.GatewayNamespace, 164 gwLabels: gwLabels, 165 gwInformer: gwInformer, 166 167 rtKind: kind, 168 rtNamespace: config.Namespace, 169 rtLabels: rtLabels, 170 rtAnnotations: rtAnnotations, 171 rtInformer: rtInformer, 172 173 nsInformer: nsInformer, 174 175 fqdnTemplate: tmpl, 176 combineFQDNAnnotation: config.CombineFQDNAndAnnotation, 177 ignoreHostnameAnnotation: config.IgnoreHostnameAnnotation, 178 } 179 return src, nil 180 } 181 182 func (src *gatewayRouteSource) AddEventHandler(ctx context.Context, handler func()) { 183 log.Debugf("Adding event handlers for %s", src.rtKind) 184 eventHandler := eventHandlerFunc(handler) 185 src.gwInformer.Informer().AddEventHandler(eventHandler) 186 src.rtInformer.Informer().AddEventHandler(eventHandler) 187 src.nsInformer.Informer().AddEventHandler(eventHandler) 188 } 189 190 func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { 191 var endpoints []*endpoint.Endpoint 192 routes, err := src.rtInformer.List(src.rtNamespace, src.rtLabels) 193 if err != nil { 194 return nil, err 195 } 196 gateways, err := src.gwInformer.Lister().Gateways(src.gwNamespace).List(src.gwLabels) 197 if err != nil { 198 return nil, err 199 } 200 namespaces, err := src.nsInformer.Lister().List(labels.Everything()) 201 if err != nil { 202 return nil, err 203 } 204 kind := strings.ToLower(src.rtKind) 205 resolver := newGatewayRouteResolver(src, gateways, namespaces) 206 for _, rt := range routes { 207 // Filter by annotations. 208 meta := rt.Metadata() 209 annots := meta.Annotations 210 if !src.rtAnnotations.Matches(labels.Set(annots)) { 211 continue 212 } 213 214 // Check controller annotation to see if we are responsible. 215 if v, ok := annots[controllerAnnotationKey]; ok && v != controllerAnnotationValue { 216 log.Debugf("Skipping %s %s/%s because controller value does not match, found: %s, required: %s", 217 src.rtKind, meta.Namespace, meta.Name, v, controllerAnnotationValue) 218 continue 219 } 220 221 // Get Route hostnames and their targets. 222 hostTargets, err := resolver.resolve(rt) 223 if err != nil { 224 return nil, err 225 } 226 if len(hostTargets) == 0 { 227 log.Debugf("No endpoints could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name) 228 continue 229 } 230 231 // Create endpoints from hostnames and targets. 232 resource := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name) 233 providerSpecific, setIdentifier := getProviderSpecificAnnotations(annots) 234 ttl := getTTLFromAnnotations(annots, resource) 235 for host, targets := range hostTargets { 236 endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...) 237 } 238 log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints) 239 } 240 return endpoints, nil 241 } 242 243 func namespacedName(namespace, name string) types.NamespacedName { 244 return types.NamespacedName{Namespace: namespace, Name: name} 245 } 246 247 type gatewayRouteResolver struct { 248 src *gatewayRouteSource 249 gws map[types.NamespacedName]gatewayListeners 250 nss map[string]*corev1.Namespace 251 } 252 253 type gatewayListeners struct { 254 gateway *v1.Gateway 255 listeners map[v1.SectionName][]v1.Listener 256 } 257 258 func newGatewayRouteResolver(src *gatewayRouteSource, gateways []*v1.Gateway, namespaces []*corev1.Namespace) *gatewayRouteResolver { 259 // Create Gateway Listener lookup table. 260 gws := make(map[types.NamespacedName]gatewayListeners, len(gateways)) 261 for _, gw := range gateways { 262 lss := make(map[v1.SectionName][]v1.Listener, len(gw.Spec.Listeners)+1) 263 for i, lis := range gw.Spec.Listeners { 264 lss[lis.Name] = gw.Spec.Listeners[i : i+1] 265 } 266 lss[""] = gw.Spec.Listeners 267 gws[namespacedName(gw.Namespace, gw.Name)] = gatewayListeners{ 268 gateway: gw, 269 listeners: lss, 270 } 271 } 272 // Create Namespace lookup table. 273 nss := make(map[string]*corev1.Namespace, len(namespaces)) 274 for _, ns := range namespaces { 275 nss[ns.Name] = ns 276 } 277 return &gatewayRouteResolver{ 278 src: src, 279 gws: gws, 280 nss: nss, 281 } 282 } 283 284 func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Targets, error) { 285 rtHosts, err := c.hosts(rt) 286 if err != nil { 287 return nil, err 288 } 289 hostTargets := make(map[string]endpoint.Targets) 290 291 meta := rt.Metadata() 292 for _, rps := range rt.RouteStatus().Parents { 293 // Confirm the Parent is the standard Gateway kind. 294 ref := rps.ParentRef 295 group := strVal((*string)(ref.Group), gatewayGroup) 296 kind := strVal((*string)(ref.Kind), gatewayKind) 297 if group != gatewayGroup || kind != gatewayKind { 298 log.Debugf("Unsupported parent %s/%s for %s %s/%s", group, kind, c.src.rtKind, meta.Namespace, meta.Name) 299 continue 300 } 301 // Lookup the Gateway and its Listeners. 302 namespace := strVal((*string)(ref.Namespace), meta.Namespace) 303 gw, ok := c.gws[namespacedName(namespace, string(ref.Name))] 304 if !ok { 305 log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name) 306 continue 307 } 308 // Confirm the Gateway has accepted the Route. 309 if !gwRouteIsAccepted(rps.Conditions) { 310 log.Debugf("Gateway %s/%s has not accepted %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name) 311 continue 312 } 313 // Match the Route to all possible Listeners. 314 match := false 315 section := sectionVal(ref.SectionName, "") 316 listeners := gw.listeners[section] 317 for i := range listeners { 318 lis := &listeners[i] 319 // Confirm that the Listener and Route protocols match. 320 if !gwProtocolMatches(rt.Protocol(), lis.Protocol) { 321 continue 322 } 323 // Confirm that the Listener and Route ports match, if specified. 324 // EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/ 325 if ref.Port != nil && *ref.Port != lis.Port { 326 continue 327 } 328 // Confirm that the Listener allows the Route (based on namespace and kind). 329 if !c.routeIsAllowed(gw.gateway, lis, rt) { 330 continue 331 } 332 // Find all overlapping hostnames between the Route and Listener. 333 // For {TCP,UDP}Routes, all annotation-generated hostnames should match since the Listener doesn't specify a hostname. 334 // For {HTTP,TLS}Routes, hostnames (including any annotation-generated) will be required to match any Listeners specified hostname. 335 gwHost := "" 336 if lis.Hostname != nil { 337 gwHost = string(*lis.Hostname) 338 } 339 for _, rtHost := range rtHosts { 340 if gwHost == "" && rtHost == "" { 341 // For {HTTP,TLS}Routes, this means the Route and the Listener both allow _any_ hostnames. 342 // For {TCP,UDP}Routes, this should always happen since neither specifies hostnames. 343 continue 344 } 345 host, ok := gwMatchingHost(gwHost, rtHost) 346 if !ok { 347 continue 348 } 349 override := getTargetsFromTargetAnnotation(gw.gateway.Annotations) 350 hostTargets[host] = append(hostTargets[host], override...) 351 if len(override) == 0 { 352 for _, addr := range gw.gateway.Status.Addresses { 353 hostTargets[host] = append(hostTargets[host], addr.Value) 354 } 355 } 356 match = true 357 } 358 } 359 if !match { 360 log.Debugf("Gateway %s/%s section %q does not match %s %s/%s hostnames %q", namespace, ref.Name, section, c.src.rtKind, meta.Namespace, meta.Name, rtHosts) 361 } 362 } 363 // If a Gateway has multiple matching Listeners for the same host, then we'll 364 // add its IPs to the target list multiple times and should dedupe them. 365 for host, targets := range hostTargets { 366 hostTargets[host] = uniqueTargets(targets) 367 } 368 return hostTargets, nil 369 } 370 371 func (c *gatewayRouteResolver) hosts(rt gatewayRoute) ([]string, error) { 372 var hostnames []string 373 for _, name := range rt.Hostnames() { 374 hostnames = append(hostnames, string(name)) 375 } 376 // TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template" 377 // but other sources don't check if fqdn-template is set. Which should it be? 378 if !c.src.ignoreHostnameAnnotation { 379 hostnames = append(hostnames, getHostnamesFromAnnotations(rt.Metadata().Annotations)...) 380 } 381 // TODO: The combine-fqdn-annotation flag is similarly vague. 382 if c.src.fqdnTemplate != nil && (len(hostnames) == 0 || c.src.combineFQDNAnnotation) { 383 hosts, err := execTemplate(c.src.fqdnTemplate, rt.Object()) 384 if err != nil { 385 return nil, err 386 } 387 hostnames = append(hostnames, hosts...) 388 } 389 // This means that the route doesn't specify a hostname and should use any provided by 390 // attached Gateway Listeners. This is only useful for {HTTP,TLS}Routes, but it doesn't 391 // break {TCP,UDP}Routes. 392 if len(rt.Hostnames()) == 0 { 393 hostnames = append(hostnames, "") 394 } 395 return hostnames, nil 396 } 397 398 func (c *gatewayRouteResolver) routeIsAllowed(gw *v1.Gateway, lis *v1.Listener, rt gatewayRoute) bool { 399 meta := rt.Metadata() 400 allow := lis.AllowedRoutes 401 402 // Check the route's namespace. 403 from := v1.NamespacesFromSame 404 if allow != nil && allow.Namespaces != nil && allow.Namespaces.From != nil { 405 from = *allow.Namespaces.From 406 } 407 switch from { 408 case v1.NamespacesFromAll: 409 // OK 410 case v1.NamespacesFromSame: 411 if gw.Namespace != meta.Namespace { 412 return false 413 } 414 case v1.NamespacesFromSelector: 415 selector, err := metav1.LabelSelectorAsSelector(allow.Namespaces.Selector) 416 if err != nil { 417 log.Debugf("Gateway %s/%s section %q has invalid namespace selector: %v", gw.Namespace, gw.Name, lis.Name, err) 418 return false 419 } 420 // Get namespace. 421 ns, ok := c.nss[meta.Namespace] 422 if !ok { 423 log.Errorf("Namespace not found for %s %s/%s", c.src.rtKind, meta.Namespace, meta.Name) 424 return false 425 } 426 if !selector.Matches(labels.Set(ns.Labels)) { 427 return false 428 } 429 default: 430 log.Debugf("Gateway %s/%s section %q has unknown namespace from %q", gw.Namespace, gw.Name, lis.Name, from) 431 return false 432 } 433 434 // Check the route's kind, if any are specified by the listener. 435 // TODO: Do we need to consider SupportedKinds in the ListenerStatus instead of the Spec? 436 // We only support core kinds and already check the protocol... Does this matter at all? 437 if allow == nil || len(allow.Kinds) == 0 { 438 return true 439 } 440 gvk := rt.Object().GetObjectKind().GroupVersionKind() 441 for _, gk := range allow.Kinds { 442 group := strVal((*string)(gk.Group), gatewayGroup) 443 if gvk.Group == group && gvk.Kind == string(gk.Kind) { 444 return true 445 } 446 } 447 return false 448 } 449 450 func gwRouteIsAccepted(conds []metav1.Condition) bool { 451 for _, c := range conds { 452 if v1.RouteConditionType(c.Type) == v1.RouteConditionAccepted { 453 return c.Status == metav1.ConditionTrue 454 } 455 } 456 return false 457 } 458 459 func uniqueTargets(targets endpoint.Targets) endpoint.Targets { 460 if len(targets) < 2 { 461 return targets 462 } 463 sort.Strings([]string(targets)) 464 prev := targets[0] 465 n := 1 466 for _, v := range targets[1:] { 467 if v == prev { 468 continue 469 } 470 prev = v 471 targets[n] = v 472 n++ 473 } 474 return targets[:n] 475 } 476 477 // gwProtocolMatches returns whether a and b are the same protocol, 478 // where HTTP and HTTPS are considered the same. 479 // and TLS and TCP are considered the same. 480 func gwProtocolMatches(a, b v1.ProtocolType) bool { 481 if a == v1.HTTPSProtocolType { 482 a = v1.HTTPProtocolType 483 } 484 if b == v1.HTTPSProtocolType { 485 b = v1.HTTPProtocolType 486 } 487 // if Listener is TLS and Route is TCP set Listener type to TCP as to pass true and return valid match 488 if a == v1.TCPProtocolType && b == v1.TLSProtocolType { 489 b = v1.TCPProtocolType 490 } 491 return a == b 492 } 493 494 // gwMatchingHost returns the most-specific overlapping host and a bool indicating if one was found. 495 // Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. 496 // That means that "*.example.com" would match both "test.example.com" and "foo.test.example.com", 497 // but not "example.com". An empty string matches anything. 498 func gwMatchingHost(a, b string) (string, bool) { 499 var ok bool 500 if a, ok = gwHost(a); !ok { 501 return "", false 502 } 503 if b, ok = gwHost(b); !ok { 504 return "", false 505 } 506 507 if a == "" { 508 return b, true 509 } 510 if b == "" || a == b { 511 return a, true 512 } 513 if na, nb := len(a), len(b); nb < na || (na == nb && strings.HasPrefix(b, "*.")) { 514 a, b = b, a 515 } 516 if strings.HasPrefix(a, "*.") && strings.HasSuffix(b, a[1:]) { 517 return b, true 518 } 519 return "", false 520 } 521 522 // gwHost returns the canonical host and a value indicating if it's valid. 523 func gwHost(host string) (string, bool) { 524 if host == "" { 525 return "", true 526 } 527 if isIPAddr(host) || !isDNS1123Domain(strings.TrimPrefix(host, "*.")) { 528 return "", false 529 } 530 return toLowerCaseASCII(host), true 531 } 532 533 // isIPAddr returns whether s in an IP address. 534 func isIPAddr(s string) bool { 535 _, err := netip.ParseAddr(s) 536 return err == nil 537 } 538 539 // isDNS1123Domain returns whether s is a valid domain name according to RFC 1123. 540 func isDNS1123Domain(s string) bool { 541 if n := len(s); n == 0 || n > 255 { 542 return false 543 } 544 for lbl, rest := "", s; rest != ""; { 545 if lbl, rest, _ = strings.Cut(rest, "."); !isDNS1123Label(lbl) { 546 return false 547 } 548 } 549 return true 550 } 551 552 // isDNS1123Label returns whether s is a valid domain label according to RFC 1123. 553 func isDNS1123Label(s string) bool { 554 n := len(s) 555 if n == 0 || n > 63 { 556 return false 557 } 558 if !isAlphaNum(s[0]) || !isAlphaNum(s[n-1]) { 559 return false 560 } 561 for i, k := 1, n-1; i < k; i++ { 562 if b := s[i]; b != '-' && !isAlphaNum(b) { 563 return false 564 } 565 } 566 return true 567 } 568 569 func isAlphaNum(b byte) bool { 570 switch { 571 case 'a' <= b && b <= 'z', 572 'A' <= b && b <= 'Z', 573 '0' <= b && b <= '9': 574 return true 575 default: 576 return false 577 } 578 } 579 580 func strVal(ptr *string, def string) string { 581 if ptr == nil || *ptr == "" { 582 return def 583 } 584 return *ptr 585 } 586 587 func sectionVal(ptr *v1.SectionName, def v1.SectionName) v1.SectionName { 588 if ptr == nil || *ptr == "" { 589 return def 590 } 591 return *ptr 592 } 593 594 func selectorsEqual(a, b labels.Selector) bool { 595 if a == nil || b == nil { 596 return a == b 597 } 598 aReq, aOK := a.DeepCopySelector().Requirements() 599 bReq, bOK := b.DeepCopySelector().Requirements() 600 if aOK != bOK || len(aReq) != len(bReq) { 601 return false 602 } 603 sort.Stable(labels.ByKey(aReq)) 604 sort.Stable(labels.ByKey(bReq)) 605 for i, r := range aReq { 606 if !r.Equal(bReq[i]) { 607 return false 608 } 609 } 610 return true 611 }