pkg/controller/prune.go (194 lines of code) (raw):

package controller import ( "fmt" "strings" "sync" n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network" v1 "k8s.io/api/core/v1" 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/appgw" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/events" ) type pruneFunc func(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress var once sync.Once var pruneFuncList []pruneFunc // PruneIngress filters ingress list based on filter functions and returns a filtered ingress list func (c *AppGwIngressController) PruneIngress(appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext) []*networking.Ingress { once.Do(func() { if cbCtx.EnvVariables.EnableBrownfieldDeployment { pruneFuncList = append(pruneFuncList, pruneProhibitedIngress) } pruneFuncList = append(pruneFuncList, pruneNoPrivateIP) pruneFuncList = append(pruneFuncList, pruneNoPublicIP) pruneFuncList = append(pruneFuncList, pruneRedirectWithNoTLS) pruneFuncList = append(pruneFuncList, pruneNoSslCertificate) pruneFuncList = append(pruneFuncList, pruneNoSslProfile) pruneFuncList = append(pruneFuncList, pruneNoTrustedRootCertificate) }) prunedIngresses := cbCtx.IngressList for _, prune := range pruneFuncList { prunedIngresses = prune(c, appGw, cbCtx, prunedIngresses) } return prunedIngresses } // pruneProhibitedIngress filters rules that are specified by prohibited target CRD func pruneProhibitedIngress(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { // Mutate the list of Ingresses by removing ones that AGIC should not be creating configuration. for idx, ingress := range ingressList { klog.V(3).Infof("Original Ingress[%d] Rules: %+v", idx, ingress.Spec.Rules) ingressClone := ingressList[idx].DeepCopy() ingressClone.Spec.Rules = brownfield.PruneIngressRules(ingress, cbCtx.ProhibitedTargets) ingressList[idx] = ingressClone klog.V(3).Infof("Sanitized Ingress[%d] Rules: %+v", idx, ingressList[idx].Spec.Rules) } return ingressList } // pruneNoPrivateIP filters ingresses which use private IP annotation when AppGw doesn't have a private IP func pruneNoPrivateIP(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress appGwHasPrivateIP := appgw.LookupIPConfigurationByType(appGw.FrontendIPConfigurations, appgw.FrontendTypePrivate) != nil for _, ingress := range ingressList { usePrivateIP, err := annotations.UsePrivateIP(ingress) if err != nil && controllererrors.IsErrorCode(err, controllererrors.ErrorInvalidContent) { klog.Errorf("Ingress %s/%s has invalid value for annotation %s", ingress.Namespace, ingress.Name, annotations.UsePrivateIPKey) } usePrivateIP = usePrivateIP || cbCtx.EnvVariables.UsePrivateIP if usePrivateIP && !appGwHasPrivateIP { errorLine := fmt.Sprintf("ignoring Ingress %s/%s as it requires Application Gateway '%s' to have a private IP address. "+ "Either add a private IP to Application Gateway or remvove 'appgw.ingress.kubernetes.io/use-private-ip' from the ingress.", ingress.Namespace, ingress.Name, c.appGwIdentifier.AppGwName) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonNoPrivateIPError, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonNoPrivateIPError, errorLine) } } else { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses } // pruneNoPublicIP filters ingresses which need public IP but AppGw doesn't have a public IP func pruneNoPublicIP(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress appGwHasPublicIP := appgw.LookupIPConfigurationByType(appGw.FrontendIPConfigurations, appgw.FrontendTypePublic) != nil for _, ingress := range ingressList { usePrivateIP, err := annotations.UsePrivateIP(ingress) if err != nil && controllererrors.IsErrorCode(err, controllererrors.ErrorInvalidContent) { klog.Errorf("Ingress %s/%s has invalid value for annotation %s", ingress.Namespace, ingress.Name, annotations.UsePrivateIPKey) } usePublicIP := !usePrivateIP && !cbCtx.EnvVariables.UsePrivateIP if usePublicIP && !appGwHasPublicIP { errorLine := fmt.Sprintf( "ignoring Ingress %s/%s as it requires Application Gateway '%s' to have a public IP address. "+ "Either add a public IP to Application Gateway or annotate ingress with 'appgw.ingress.kubernetes.io/use-private-ip: true' to attach Ingress to private IP.", ingress.Namespace, ingress.Name, c.appGwIdentifier.AppGwName) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonNoPublicIPError, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonNoPublicIPError, errorLine) } } else { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses } // pruneNoSslCertificate filters ingresses which use appgw-ssl-certificate annotation when AppGw doesn't have annotated ssl certificate installed func pruneNoSslCertificate(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress set := make(map[string]bool) for _, installedSslCertificate := range *appGw.SslCertificates { set[*installedSslCertificate.Name] = true } for _, ingress := range ingressList { annotatedSslCertificate, err := annotations.GetAppGwSslCertificate(ingress) // if annotation is not specified, add the ingress and go check next if err != nil && controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) { prunedIngresses = append(prunedIngresses, ingress) continue } // given empty string is a valid annotation value, we error out with a message if no match if _, exists := set[annotatedSslCertificate]; !exists { errorLine := fmt.Sprintf("ignoring Ingress %s/%s as it requires Application Gateway %s to have pre-installed ssl certificate '%s'", ingress.Namespace, ingress.Name, c.appGwIdentifier.AppGwName, annotatedSslCertificate) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonNoPreInstalledSslCertificate, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonNoPreInstalledSslCertificate, errorLine) } } else { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses } // pruneNoSslProfile filters ingresses which use appgw-ssl-profile annotation when AppGw doesn't have annotated ssl profile installed func pruneNoSslProfile(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress set := make(map[string]bool) for _, installedSslProfile := range *appGw.SslProfiles { set[*installedSslProfile.Name] = true } for _, ingress := range ingressList { annotatedSslProfile, err := annotations.GetAppGwSslProfile(ingress) // if annotation is not specified, add the ingress and go check next if err != nil && controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) { prunedIngresses = append(prunedIngresses, ingress) continue } // given empty string is a valid annotation value, we error out with a message if no match if _, exists := set[annotatedSslProfile]; !exists { errorLine := fmt.Sprintf("ignoring Ingress %s/%s as it requires Application Gateway %s to have pre-installed ssl profile '%s'", ingress.Namespace, ingress.Name, c.appGwIdentifier.AppGwName, annotatedSslProfile) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonNoPreInstalledSslProfile, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonNoPreInstalledSslProfile, errorLine) } } else { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses } // pruneNoTrustedRootCertificate filters ingresses which use appgw-trusted-root-certificate annotation when AppGw doesn't have annotated root certificate(s) installed func pruneNoTrustedRootCertificate(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress set := make(map[string]bool) for _, installedTrustedRootCertificate := range *appGw.TrustedRootCertificates { set[*installedTrustedRootCertificate.Name] = true } for _, ingress := range ingressList { installed := true trustedRootCertificates, err := annotations.GetAppGwTrustedRootCertificate(ingress) // if annotation is not specified if err != nil && controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) { prunedIngresses = append(prunedIngresses, ingress) continue } for _, rootCert := range strings.Split(trustedRootCertificates, ",") { if _, exists := set[rootCert]; !exists { installed = false errorLine := fmt.Sprintf("ignoring Ingress %s/%s as it requires Application Gateway %s to have pre-installed root certificate '%s'", ingress.Namespace, ingress.Name, c.appGwIdentifier.AppGwName, rootCert) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonNoPreInstalledRootCertificate, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonNoPreInstalledRootCertificate, errorLine) } } } if installed { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses } // pruneRedirectWithNoTLS filters ingresses which are annotated for ssl redirect but don't have a TLS section in the spec func pruneRedirectWithNoTLS(c *AppGwIngressController, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext, ingressList []*networking.Ingress) []*networking.Ingress { var prunedIngresses []*networking.Ingress for _, ingress := range ingressList { appgwCertName, _ := annotations.GetAppGwSslCertificate(ingress) hasTLS := (ingress.Spec.TLS != nil && len(ingress.Spec.TLS) > 0) || len(appgwCertName) > 0 sslRedirect, _ := annotations.IsSslRedirect(ingress) if !hasTLS && sslRedirect { errorLine := fmt.Sprintf("ignoring Ingress %s/%s as it has an invalid spec. It is annotated with ssl-redirect: true but is missing a TLS secret or '%s' annotation. Please add a TLS secret/annotation or remove ssl-redirect annotation", ingress.Namespace, ingress.Name, annotations.AppGwSslCertificate) klog.Error(errorLine) c.recorder.Event(ingress, v1.EventTypeWarning, events.ReasonRedirectWithNoTLS, errorLine) if c.agicPod != nil { c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonRedirectWithNoTLS, errorLine) } } else { prunedIngresses = append(prunedIngresses, ingress) } } return prunedIngresses }