pkg/controller/keyvault/nginx_secret_provider_class.go (123 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package keyvault
import (
"context"
"errors"
"fmt"
approutingv1alpha1 "github.com/Azure/aks-app-routing-operator/api/v1alpha1"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/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"
secv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"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 (
nginxSecretProviderControllerName = controllername.New("keyvault", "nginx", "secret", "provider")
)
// NginxSecretProviderClassReconciler manages a SecretProviderClass for each nginx ingress controller that
// has a Keyvault URI in its DefaultSSLCertificate field. The SPC is used to mirror the Keyvault values into
// a k8s secret so that it can be used by the CRD controller.
type NginxSecretProviderClassReconciler struct {
client client.Client
events record.EventRecorder
config *config.Config
}
func NewNginxSecretProviderClassReconciler(manager ctrl.Manager, conf *config.Config) error {
metrics.InitControllerMetrics(nginxSecretProviderControllerName)
if conf.DisableKeyvault {
return nil
}
return nginxSecretProviderControllerName.AddToController(
ctrl.
NewControllerManagedBy(manager).
For(&approutingv1alpha1.NginxIngressController{}).
Owns(&secv1.SecretProviderClass{}), manager.GetLogger(),
).Complete(&NginxSecretProviderClassReconciler{
client: manager.GetClient(),
events: manager.GetEventRecorderFor("aks-app-routing-operator"),
config: conf,
})
}
func (i *NginxSecretProviderClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
// 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(controllerName, result, err) would bind
//the values of result and err to their zero values, since they were just instantiated
metrics.HandleControllerReconcileMetrics(nginxSecretProviderControllerName, result, retErr)
}()
logger, err := logr.FromContext(ctx)
if err != nil {
return ctrl.Result{}, fmt.Errorf("initializing logger: %w", err)
}
logger = nginxSecretProviderControllerName.AddToLogger(logger).WithValues("name", req.Name, "namespace", req.Namespace)
logger.Info("getting Nginx Ingress")
nic := &approutingv1alpha1.NginxIngressController{}
err = i.client.Get(ctx, req.NamespacedName, nic)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
logger = logger.WithValues("name", nic.Name, "generation", nic.Generation)
spc := &secv1.SecretProviderClass{
TypeMeta: metav1.TypeMeta{
APIVersion: "secrets-store.csi.x-k8s.io/v1",
Kind: "SecretProviderClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: DefaultNginxCertName(nic),
Namespace: i.config.NS,
Labels: manifests.GetTopLevelLabels(),
OwnerReferences: []metav1.OwnerReference{{
APIVersion: nic.APIVersion,
Controller: util.ToPtr(true),
Kind: nic.Kind,
Name: nic.Name,
UID: nic.UID,
}},
},
}
logger = logger.WithValues("spc", spc.Name)
logger.Info("building spc and upserting if managed with labels")
if shouldDeploySpc(nic) {
spcConf := spcConfig{
ClientId: i.config.MSIClientID,
TenantId: i.config.TenantID,
KeyvaultCertUri: *nic.Spec.DefaultSSLCertificate.KeyVaultURI,
Name: DefaultNginxCertName(nic),
Cloud: i.config.Cloud,
}
err = buildSPC(spc, spcConf)
if err != nil {
var userErr util.UserError
if errors.As(err, &userErr) {
logger.Info(fmt.Sprintf("failed to build secret provider class for nginx ingress controller with error: %s. sending warning event", userErr.Error()))
i.events.Eventf(nic, corev1.EventTypeWarning, "InvalidInput", "error while processing Keyvault reference: %s", userErr.UserError())
return ctrl.Result{}, nil
}
logger.Error(err, fmt.Sprintf("failed to build secret provider class for nginx ingress controller with error: %s.", err.Error()))
return ctrl.Result{}, err
}
logger.Info("reconciling secret provider class for ingress")
err = util.Upsert(ctx, i.client, spc)
if err != nil {
i.events.Eventf(nic, corev1.EventTypeWarning, "FailedUpdateOrCreateSPC", "error while creating or updating SecretProviderClass needed to pull Keyvault reference: %s", err.Error())
logger.Error(err, fmt.Sprintf("failed to upsert secret provider class for nginx ingress class with error: %s.", err.Error()))
}
return ctrl.Result{}, err
}
logger.Info("spc is either not managed or key vault uri was removed")
logger.Info("getting and cleaning unused spc if ingress is managed")
toCleanSPC := &secv1.SecretProviderClass{}
err = i.client.Get(ctx, client.ObjectKeyFromObject(spc), toCleanSPC)
if err != nil {
logger.Error(err, "failed to fetch existing spc")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if manifests.HasTopLevelLabels(toCleanSPC.Labels) {
logger.Info("removing secret provider class for ingress")
err = i.client.Delete(ctx, toCleanSPC)
return ctrl.Result{}, client.IgnoreNotFound(err)
} else {
logger.Info("spc was not managed so spc was not removed")
}
return ctrl.Result{}, nil
}