ContainerImage.Pinniped/internal/controller/supervisorconfig/jwks_observer.go
Andrew Keesler 82ae98d9d0
Set secret names on OIDCProvider status field
We believe this API is more forwards compatible with future secrets management
use cases. The implementation is a cry for help, but I was trying to follow the
previously established pattern of encapsulating the secret generation
functionality to a single group of packages.

This commit makes a breaking change to the current OIDCProvider API, but that
OIDCProvider API was added after the latest release, so it is technically still
in development until we release, and therefore we can continue to thrash on it.

I also took this opportunity to make some things private that didn't need to be
public.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 09:13:01 -05:00

115 lines
3.8 KiB
Go

// Copyright 2020 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/1.19/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
oidcProviderInformer v1alpha1.OIDCProviderInformer
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 OIDCProviders 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,
oidcProviderInformer v1alpha1.OIDCProviderInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
return controllerlib.New(
controllerlib.Config{
Name: "jwks-observer-controller",
Syncer: &jwksObserverController{
issuerToJWKSSetter: issuerToJWKSSetter,
oidcProviderInformer: oidcProviderInformer,
secretInformer: secretInformer,
},
},
withInformer(
secretInformer,
pinnipedcontroller.MatchAnythingFilter(nil),
controllerlib.InformerOption{},
),
withInformer(
oidcProviderInformer,
pinnipedcontroller.MatchAnythingFilter(nil),
controllerlib.InformerOption{},
),
)
}
func (c *jwksObserverController) Sync(ctx controllerlib.Context) error {
ns := ctx.Key.Namespace
allProviders, err := c.oidcProviderInformer.Lister().OIDCProviders(ns).List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list OIDCProviders: %w", err)
}
// Rebuild the whole map on any change to any Secret or OIDCProvider, 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
}