ContainerImage.Pinniped/internal/controller/supervisorconfig/jwks_observer.go

115 lines
3.9 KiB
Go

// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisorconfig
import (
"encoding/json"
"fmt"
"gopkg.in/square/go-jose.v2"
"k8s.io/apimachinery/pkg/labels"
corev1informers "k8s.io/client-go/informers/core/v1"
"go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/config/v1alpha1"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/plog"
)
type jwksObserverController struct {
issuerToJWKSSetter IssuerToJWKSMapSetter
federationDomainInformer v1alpha1.FederationDomainInformer
secretInformer corev1informers.SecretInformer
}
type IssuerToJWKSMapSetter interface {
SetIssuerToJWKSMap(
issuerToJWKSMap map[string]*jose.JSONWebKeySet,
issuerToActiveJWKMap map[string]*jose.JSONWebKey,
)
}
// Returns a controller which watches all of the FederationDomains and their corresponding Secrets
// and fills an in-memory cache of the JWKS info for each currently configured issuer.
// This controller assumes that the informers passed to it are already scoped down to the
// appropriate namespace. It also assumes that the IssuerToJWKSMapSetter passed to it has an
// underlying implementation which is thread-safe.
func NewJWKSObserverController(
issuerToJWKSSetter IssuerToJWKSMapSetter,
secretInformer corev1informers.SecretInformer,
federationDomainInformer v1alpha1.FederationDomainInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
return controllerlib.New(
controllerlib.Config{
Name: "jwks-observer-controller",
Syncer: &jwksObserverController{
issuerToJWKSSetter: issuerToJWKSSetter,
federationDomainInformer: federationDomainInformer,
secretInformer: secretInformer,
},
},
withInformer(
secretInformer,
pinnipedcontroller.MatchAnySecretOfTypeFilter(jwksSecretTypeValue, nil),
controllerlib.InformerOption{},
),
withInformer(
federationDomainInformer,
pinnipedcontroller.MatchAnythingFilter(nil),
controllerlib.InformerOption{},
),
)
}
func (c *jwksObserverController) Sync(ctx controllerlib.Context) error {
ns := ctx.Key.Namespace
allProviders, err := c.federationDomainInformer.Lister().FederationDomains(ns).List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list FederationDomains: %w", err)
}
// Rebuild the whole map on any change to any Secret or FederationDomain, because either can have changes that
// can cause the map to need to be updated.
issuerToJWKSMap := map[string]*jose.JSONWebKeySet{}
issuerToActiveJWKMap := map[string]*jose.JSONWebKey{}
for _, provider := range allProviders {
secretRef := provider.Status.Secrets.JWKS
jwksSecret, err := c.secretInformer.Lister().Secrets(ns).Get(secretRef.Name)
if err != nil {
plog.Debug("jwksObserverController Sync could not find JWKS secret", "namespace", ns, "secretName", secretRef.Name)
continue
}
jwksFromSecret := jose.JSONWebKeySet{}
err = json.Unmarshal(jwksSecret.Data[jwksKey], &jwksFromSecret)
if err != nil {
plog.Debug("jwksObserverController Sync found a JWKS secret with Data in an unexpected format", "namespace", ns, "secretName", secretRef.Name)
continue
}
activeJWKFromSecret := jose.JSONWebKey{}
err = json.Unmarshal(jwksSecret.Data[activeJWKKey], &activeJWKFromSecret)
if err != nil {
plog.Debug("jwksObserverController Sync found an active JWK secret with Data in an unexpected format", "namespace", ns, "secretName", secretRef.Name)
continue
}
issuerToJWKSMap[provider.Spec.Issuer] = &jwksFromSecret
issuerToActiveJWKMap[provider.Spec.Issuer] = &activeJWKFromSecret
}
plog.Debug(
"jwksObserverController Sync updated the JWKS cache",
"issuerJWKSCount",
len(issuerToJWKSMap),
"issuerActiveJWKCount",
len(issuerToActiveJWKMap),
)
c.issuerToJWKSSetter.SetIssuerToJWKSMap(issuerToJWKSMap, issuerToActiveJWKMap)
return nil
}