pkg/controller/osm/ingress_backend_reconciler.go (147 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package osm import ( "context" "github.com/go-logr/logr" policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/Azure/aks-app-routing-operator/pkg/config" "github.com/Azure/aks-app-routing-operator/pkg/controller/controllername" "github.com/Azure/aks-app-routing-operator/pkg/controller/metrics" "github.com/Azure/aks-app-routing-operator/pkg/manifests" "github.com/Azure/aks-app-routing-operator/pkg/util" ) var ( ingressBackendControllerName = controllername.New("osm", "ingress", "backend") ) // IngressControllerSourceSpecer returns the IngressSourceSpec an Ingress consumes and a boolean indicating whether it's managed by web app routing. // If an ingress is not managed by web app routing, the specer will return false and isn't guaranteed to return the IngressSourceSpec type IngressControllerSourceSpecer interface { IngressSourceSpec(ing *netv1.Ingress) (policyv1alpha1.IngressSourceSpec, bool, error) } type ingressControllerSourceSpecer struct { ingressSourceSpecFn func(ing *netv1.Ingress) (policyv1alpha1.IngressSourceSpec, bool, error) } // NewIngressControllerNamer returns an IngressControllerSourceSpecer using a map from ingress class names to controller names func NewIngressControllerSourceSpecerFromFn(fn func(ing *netv1.Ingress) (policyv1alpha1.IngressSourceSpec, bool, error)) IngressControllerSourceSpecer { return &ingressControllerSourceSpecer{ingressSourceSpecFn: fn} } func (i *ingressControllerSourceSpecer) IngressSourceSpec(ing *netv1.Ingress) (policyv1alpha1.IngressSourceSpec, bool, error) { return i.ingressSourceSpecFn(ing) } // IngressBackendReconciler creates an Open Service Mesh IngressBackend for every ingress resource with "kubernetes.azure.com/use-osm-mtls=true". // This allows nginx to use mTLS provided by OSM when contacting upstreams. type IngressBackendReconciler struct { client client.Client events record.EventRecorder config *config.Config ingressControllerSourceSpecer IngressControllerSourceSpecer } func NewIngressBackendReconciler(manager ctrl.Manager, conf *config.Config, ingressControllerNamer IngressControllerSourceSpecer) error { metrics.InitControllerMetrics(ingressBackendControllerName) if conf.DisableOSM { return nil } return ingressBackendControllerName.AddToController( ctrl. NewControllerManagedBy(manager). For(&netv1.Ingress{}), manager.GetLogger(), ).Complete(&IngressBackendReconciler{ client: manager.GetClient(), config: conf, ingressControllerSourceSpecer: ingressControllerNamer, events: manager.GetEventRecorderFor("aks-app-routing-operator"), }) } func (i *IngressBackendReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var err error result := ctrl.Result{} // do metrics defer func() { //placing this call inside a closure allows for result and err to be bound after Reconcile executes //this makes sure they have the proper value //just calling defer metrics.HandleControllerReconcileMetrics(ingressCertConfigControllerName, result, err) would bind //the values of result and err to their zero values, since they were just instantiated metrics.HandleControllerReconcileMetrics(ingressBackendControllerName, result, err) }() logger, err := logr.FromContext(ctx) if err != nil { return result, err } logger = ingressBackendControllerName.AddToLogger(logger) logger.Info("getting Ingress", "name", req.Name, "namespace", req.Namespace) ing := &netv1.Ingress{} err = i.client.Get(ctx, req.NamespacedName, ing) if err != nil { return result, client.IgnoreNotFound(err) } logger = logger.WithValues("name", ing.Name, "namespace", ing.Namespace, "generation", ing.Generation) backend := &policyv1alpha1.IngressBackend{ TypeMeta: metav1.TypeMeta{ Kind: "IngressBackend", APIVersion: "policy.openservicemesh.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: ing.Name, Namespace: ing.Namespace, Labels: manifests.GetTopLevelLabels(), OwnerReferences: []metav1.OwnerReference{{ APIVersion: ing.APIVersion, Controller: util.ToPtr(true), Kind: ing.Kind, Name: ing.Name, UID: ing.UID, }}, }, } logger = logger.WithValues("ingressBackend", backend.Name) sourceSpec, ok, err := i.ingressControllerSourceSpecer.IngressSourceSpec(ing) if err != nil { logger.Error(err, "failed to get ingress source spec") return result, err } backend.Spec = policyv1alpha1.IngressBackendSpec{ Backends: []policyv1alpha1.BackendSpec{}, Sources: []policyv1alpha1.IngressSourceSpec{ sourceSpec, { Kind: "AuthenticatedPrincipal", Name: osmNginxSAN, }, }, } for _, rule := range ing.Spec.Rules { if rule.HTTP == nil { continue } for _, path := range rule.HTTP.Paths { if path.Backend.Service == nil || path.Backend.Service.Port.Number == 0 { continue } backend.Spec.Backends = append(backend.Spec.Backends, policyv1alpha1.BackendSpec{ Name: path.Backend.Service.Name, TLS: policyv1alpha1.TLSSpec{SkipClientCertValidation: false}, Port: policyv1alpha1.PortSpec{ Number: int(path.Backend.Service.Port.Number), Protocol: "https", }, }) } } if ing.Annotations == nil || ing.Annotations["kubernetes.azure.com/use-osm-mtls"] == "" || !ok { logger.Info("Ingress does not have osm mtls annotation, cleaning up managed IngressBackend") logger.Info("getting IngressBackend") toCleanBackend := &policyv1alpha1.IngressBackend{} err = i.client.Get(ctx, client.ObjectKeyFromObject(backend), toCleanBackend) if err != nil { return result, client.IgnoreNotFound(err) } if manifests.HasTopLevelLabels(toCleanBackend.Labels) { logger.Info("deleting IngressBackend") err = i.client.Delete(ctx, toCleanBackend) return result, client.IgnoreNotFound(err) } return result, nil } logger.Info("reconciling OSM ingress backend for ingress") err = util.Upsert(ctx, i.client, backend) if err != nil { i.events.Eventf(ing, corev1.EventTypeWarning, "FailedUpdateOrCreateIngressBackend", "error while creating or updating IngressBackend needed for OSM integration: %s", err) } return result, err }