pkg/appgw/ingress_rules.go (131 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 (
n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network"
networking "k8s.io/api/networking/v1"
"k8s.io/klog/v2"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment"
)
func (c *appGwConfigBuilder) getListenersFromIngress(ingress *networking.Ingress, env environment.EnvVariables) map[listenerIdentifier]listenerAzConfig {
listeners := make(map[listenerIdentifier]listenerAzConfig)
// if ingress has only backend configured
if ingress.Spec.DefaultBackend != nil && len(ingress.Spec.Rules) == 0 {
return listeners
}
// process ingress rules with TLS and Waf policy
policy, _ := annotations.WAFPolicy(ingress)
for ruleIdx := range ingress.Spec.Rules {
rule := &ingress.Spec.Rules[ruleIdx]
if rule.HTTP == nil {
continue
}
_, ruleListeners := c.processIngressRuleWithTLS(rule, ingress, env)
applyToListener := false
if policy != "" {
applyToListener = c.applyToListener(rule)
}
for k, v := range ruleListeners {
if applyToListener {
klog.V(3).Infof("Attach WAF policy: %s to listener: %s", policy, generateListenerName(k))
v.FirewallPolicy = policy
}
listeners[k] = v
}
}
return listeners
}
func (c *appGwConfigBuilder) applyToListener(rule *networking.IngressRule) bool {
for pathIdx := range rule.HTTP.Paths {
path := &rule.HTTP.Paths[pathIdx]
// if there is path that is /, /* , empty string, then apply the waf policy to the listener.
if isPathCatchAll(path.Path, path.PathType) {
return true
}
}
return false
}
func (c *appGwConfigBuilder) processIngressRuleWithTLS(rule *networking.IngressRule, ingress *networking.Ingress, env environment.EnvVariables) (map[Port]interface{}, map[listenerIdentifier]listenerAzConfig) {
frontendPorts := make(map[Port]interface{})
// certificate from ingress TLS spec
ingressHostNamesecretIDMap := c.newHostToSecretMap(ingress)
listeners := make(map[listenerIdentifier]listenerAzConfig)
// Override the defaults 80,443 ports use for the listener
overrideFrontendPortFromAnnotation, _ := annotations.OverrideFrontendPort(ingress)
overrideFrontendPortForIngress := Port(overrideFrontendPortFromAnnotation)
// Private IP is used when either annotation use-private-ip or USE_PRIVATE_IP env variable is true.
usePrivateIPFromAnnotation, _ := annotations.UsePrivateIP(ingress)
usePrivateIPForIngress := usePrivateIPFromAnnotation || env.UsePrivateIP
appgwCertName, _ := annotations.GetAppGwSslCertificate(ingress)
if len(appgwCertName) > 0 {
// logging to see the namespace of the ingress annotated with appgw-ssl-certificate
klog.V(3).Infof("Found annotation appgw-ssl-certificate: %s in ingress %s/%s", appgwCertName, ingress.Namespace, ingress.Name)
}
appgwProfileName, _ := annotations.GetAppGwSslProfile(ingress)
if len(appgwProfileName) > 0 {
// logging to see the namespace of the ingress annotated with appgw-ssl-certificate
klog.V(3).Infof("Found annotation appgw-ssl-profile: %s in ingress %s/%s", appgwProfileName, ingress.Namespace, ingress.Name)
}
cert, secID := c.getCertificate(ingress, rule.Host, ingressHostNamesecretIDMap)
hasTLS := (cert != nil || len(appgwCertName) > 0)
sslRedirect, _ := annotations.IsSslRedirect(ingress)
// If a certificate is available we enable only HTTPS; unless ingress is annotated with ssl-redirect - then
// we enable HTTPS as well as HTTP, and redirect HTTP to HTTPS;
if hasTLS {
listenerID := generateListenerID(ingress, rule, n.ApplicationGatewayProtocolHTTPS, &overrideFrontendPortForIngress, usePrivateIPForIngress)
frontendPorts[Port(listenerID.FrontendPort)] = nil
// Only associate the Listener with a Redirect if redirect is enabled
redirect := ""
if sslRedirect {
redirect = generateSSLRedirectConfigurationName(listenerID)
}
azConf := listenerAzConfig{
Protocol: n.ApplicationGatewayProtocolHTTPS,
SslRedirectConfigurationName: redirect,
}
// appgw-ssl-certificate annotation will be ignored if TLS spec found
if cert != nil {
azConf.Secret = *secID
} else if len(appgwCertName) > 0 {
// the cert annotated can be referred across namespace,
// set namespace to "" to ignore namespace
azConf.Secret = secretIdentifier{
Name: appgwCertName,
Namespace: "",
}
}
if len(appgwProfileName) > 0 {
azConf.SslProfile = appgwProfileName
}
listeners[listenerID] = azConf
}
// Enable HTTP only if HTTPS is not configured OR if ingress annotated with 'ssl-redirect'
if sslRedirect || !hasTLS {
listenerID := generateListenerID(ingress, rule, n.ApplicationGatewayProtocolHTTP, &overrideFrontendPortForIngress, usePrivateIPForIngress)
frontendPorts[Port(listenerID.FrontendPort)] = nil
listeners[listenerID] = listenerAzConfig{
Protocol: n.ApplicationGatewayProtocolHTTP,
}
}
return frontendPorts, listeners
}
func (c *appGwConfigBuilder) newBackendIdsFiltered(cbCtx *ConfigBuilderContext) map[backendIdentifier]interface{} {
if c.mem.backendIDs != nil {
return *c.mem.backendIDs
}
backendIDs := make(map[backendIdentifier]interface{})
for _, ingress := range cbCtx.IngressList {
if ingress.Spec.DefaultBackend != nil {
backendID := generateBackendID(ingress, nil, nil, ingress.Spec.DefaultBackend)
klog.V(3).Info("Found default backend:", backendID.serviceKey())
backendIDs[backendID] = nil
}
for ruleIdx := range ingress.Spec.Rules {
rule := &ingress.Spec.Rules[ruleIdx]
if rule.HTTP == nil {
// skip no http rule
klog.V(3).Infof("[%s] Skip rule #%d for host '%s' - it has no HTTP rules.", ingress.Namespace, ruleIdx+1, rule.Host)
continue
}
for pathIdx := range rule.HTTP.Paths {
path := &rule.HTTP.Paths[pathIdx]
backendID := generateBackendID(ingress, rule, path, &path.Backend)
klog.V(3).Info("Found backend:", backendID.serviceKey())
backendIDs[backendID] = nil
}
}
}
finalBackendIDs := make(map[backendIdentifier]interface{})
serviceSet := newServiceSet(&cbCtx.ServiceList)
// Filter out backends, where Ingresses reference non-existent Services
for be := range backendIDs {
if _, exists := serviceSet[be.serviceKey()]; !exists {
klog.Errorf("Ingress %s/%s references non existent Service %s. Please correct the Service section of your Kubernetes YAML", be.Ingress.Namespace, be.Ingress.Name, be.serviceKey())
// TODO(draychev): Enable this filter when we are certain this won't break anything!
// continue
}
finalBackendIDs[be] = nil
}
c.mem.backendIDs = &finalBackendIDs
return finalBackendIDs
}