pkg/appgw/frontend_listeners.go (176 lines of code) (raw):

// ------------------------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // -------------------------------------------------------------------------------------------- package appgw import ( "sort" n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network" "github.com/Azure/go-autorest/autorest/to" "k8s.io/klog/v2" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/sorter" ) // getListeners constructs the unique set of App Gateway HTTP listeners across all ingresses. func (c *appGwConfigBuilder) getListeners(cbCtx *ConfigBuilderContext) (*[]n.ApplicationGatewayHTTPListener, *[]n.ApplicationGatewayFrontendPort) { if c.mem.listeners != nil && c.mem.ports != nil { return c.mem.listeners, c.mem.ports } portsByNumber := cbCtx.ExistingPortsByNumber var listeners []n.ApplicationGatewayHTTPListener if portsByNumber == nil { portsByNumber = make(map[Port]n.ApplicationGatewayFrontendPort) } if cbCtx.EnvVariables.EnableIstioIntegration { listeners, portsByNumber = c.getIstioListenersPorts(cbCtx) } for listenerID, config := range c.getListenerConfigs(cbCtx) { listener, port, err := c.newListener(cbCtx, listenerID, config.Protocol, portsByNumber) if err != nil { klog.Errorf("Failed creating listener %+v: %s", listenerID, err) continue } // newlistener created a new port; Add it to the set if _, exists := portsByNumber[Port(*port.Port)]; !exists { portsByNumber[Port(*port.Port)] = *port } if config.Protocol == n.ApplicationGatewayProtocolHTTPS { sslCertificateID := c.appGwIdentifier.sslCertificateID(config.Secret.secretFullName()) listener.SslCertificate = resourceRef(sslCertificateID) if config.SslProfile != "" { sslProfileID := c.appGwIdentifier.sslProfileID(config.SslProfile) listener.SslProfile = resourceRef(sslProfileID) } } if config.FirewallPolicy != "" { listener.FirewallPolicy = &n.SubResource{ID: to.StringPtr(config.FirewallPolicy)} } listeners = append(listeners, *listener) } if cbCtx.EnvVariables.EnableBrownfieldDeployment { er := brownfield.NewExistingResources(c.appGw, cbCtx.ProhibitedTargets, nil) // Listeners we obtained from App Gateway - we segment them into ones AGIC is and is not allowed to change. existingBlacklisted, existingNonBlacklisted := er.GetBlacklistedListeners() brownfield.LogListeners(existingBlacklisted, existingNonBlacklisted, listeners) // MergeListeners would produce unique list of listeners based on Name. Blacklisted listeners, // which have the same name as a managed listeners would be overwritten. listeners = brownfield.MergeListeners(existingBlacklisted, listeners) } portIDs := make(map[string]interface{}) // Cleanup unused ports for _, listener := range listeners { if listener.FrontendPort != nil && listener.FrontendPort.ID != nil { portIDs[*listener.FrontendPort.ID] = nil } } var ports []n.ApplicationGatewayFrontendPort for _, port := range portsByNumber { if _, exists := portIDs[*port.ID]; exists { ports = append(ports, port) } } sort.Sort(sorter.ByListenerName(listeners)) sort.Sort(sorter.ByFrontendPortName(ports)) // Since getListeners() would be called multiple times within the life cycle of a MutateAppGateway(Event) // we cache the results of this function in what would be final place to store the Listeners. c.mem.listeners = &listeners c.mem.ports = &ports return &listeners, &ports } // getListenerConfigs creates an intermediary representation of the listener configs based on the passed list of ingresses func (c *appGwConfigBuilder) getListenerConfigs(cbCtx *ConfigBuilderContext) map[listenerIdentifier]listenerAzConfig { if c.mem.listenerConfigs != nil { return *c.mem.listenerConfigs } if cbCtx.EnvVariables.AttachWAFPolicyToListener { // logging to see if customer configures env.AttachWAFPolicyToListener or not klog.V(3).Info("env.AttachWAFPolicyToListener is enabled") } // TODO(draychev): Emit an error event if 2 namespaces define different TLS for the same domain! allListeners := make(map[listenerIdentifier]listenerAzConfig) for _, ingress := range cbCtx.IngressList { klog.V(3).Infof("Processing Rules for Ingress: %s/%s", ingress.Namespace, ingress.Name) azListenerConfigs := c.getListenersFromIngress(ingress, cbCtx.EnvVariables) for listenerID, azConfig := range azListenerConfigs { allListeners[listenerID] = azConfig } } // App Gateway must have at least one listener - the default one! if len(allListeners) == 0 { listenerConfig := listenerAzConfig{ // Default protocol Protocol: n.ApplicationGatewayProtocolHTTP, } // See if we have an ingress annotated with a Firewall Policy; Attach it to the listener for _, ingress := range cbCtx.IngressList { // if ingress has only backend configured or ingress rule without path but empty host if policy, _ := annotations.WAFPolicy(ingress); policy != "" { listenerConfig.FirewallPolicy = policy break } } allListeners[defaultFrontendListenerIdentifier(c.appGw, cbCtx.EnvVariables)] = listenerConfig } c.mem.listenerConfigs = &allListeners return allListeners } func (c *appGwConfigBuilder) newListener(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, protocol n.ApplicationGatewayProtocol, portsByNumber map[Port]n.ApplicationGatewayFrontendPort) (*n.ApplicationGatewayHTTPListener, *n.ApplicationGatewayFrontendPort, error) { frontIPConfiguration := *LookupIPConfigurationByType(c.appGw.FrontendIPConfigurations, listenerID.FrontendType) portNumber := listenerID.FrontendPort var frontendPort n.ApplicationGatewayFrontendPort var exists bool if frontendPort, exists = portsByNumber[portNumber]; !exists { portName := generateFrontendPortName(listenerID.FrontendPort) frontendPort = n.ApplicationGatewayFrontendPort{ Etag: to.StringPtr("*"), Name: &portName, ID: to.StringPtr(c.appGwIdentifier.frontendPortID(portName)), ApplicationGatewayFrontendPortPropertiesFormat: &n.ApplicationGatewayFrontendPortPropertiesFormat{ Port: to.Int32Ptr(int32(portNumber)), }, } } listenerName := generateListenerName(listenerID) listener := n.ApplicationGatewayHTTPListener{ Etag: to.StringPtr("*"), Name: to.StringPtr(listenerName), ID: to.StringPtr(c.appGwIdentifier.listenerID(listenerName)), ApplicationGatewayHTTPListenerPropertiesFormat: &n.ApplicationGatewayHTTPListenerPropertiesFormat{ // TODO: expose this to external configuration FrontendIPConfiguration: resourceRef(*frontIPConfiguration.ID), FrontendPort: resourceRef(*frontendPort.ID), Protocol: protocol, HostName: nil, HostNames: &[]string{}, // setting to default RequireServerNameIndication: to.BoolPtr(false), }, } // Use only the 'HostNames' field as application gateway allows either 'HostName' or 'HostNames' if hostNames := listenerID.getHostNames(); len(hostNames) != 0 { listener.HostNames = &hostNames } return &listener, &frontendPort, nil } func (c *appGwConfigBuilder) groupListenersByListenerIdentifier(cbCtx *ConfigBuilderContext) map[listenerIdentifier]*n.ApplicationGatewayHTTPListener { listeners, ports := c.getListeners(cbCtx) portsByID := make(map[string]n.ApplicationGatewayFrontendPort) for _, port := range *ports { portsByID[*port.ID] = port } listenersByID := make(map[listenerIdentifier]*n.ApplicationGatewayHTTPListener) // Update the listenerMap with the final listener lists for idx, listener := range *listeners { listenerID := listenerIdentifier{ FrontendType: DetermineFrontendType(LookupIPConfigurationByID(c.appGw.FrontendIPConfigurations, listener.FrontendIPConfiguration.ID)), } if listener.HostNames != nil && len(*listener.HostNames) > 0 { listenerID.setHostNames(*listener.HostNames) } else if listener.HostName != nil { listenerID.setHostNames([]string{*listener.HostName}) } port, portExists := portsByID[*listener.FrontendPort.ID] if portExists && port.Port != nil { listenerID.FrontendPort = Port(*port.Port) } else { klog.Errorf("Failed to find port '%s' referenced by listener '%s'", *listener.FrontendPort.ID, *listener.Name) } listenersByID[listenerID] = &((*listeners)[idx]) } return listenersByID } // LookupListener gets by ID. func LookupListenerByID(listeners *[]n.ApplicationGatewayHTTPListener, ID *string) *n.ApplicationGatewayHTTPListener { for _, listener := range *listeners { if *listener.ID == *ID { return &listener } } return nil } func IsMutliSiteListener(listener *n.ApplicationGatewayHTTPListener) bool { if listener == nil { return false } if listener.HostName != nil && *listener.HostName != "" { return true } return listener.HostNames != nil && len(*listener.HostNames) > 0 }