2020-10-17 00:51:40 +00:00
|
|
|
// 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"
|
|
|
|
|
2020-10-30 20:09:14 +00:00
|
|
|
"go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions/config/v1alpha1"
|
2020-10-17 00:51:40 +00:00
|
|
|
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
|
|
|
"go.pinniped.dev/internal/controllerlib"
|
2020-11-10 15:22:16 +00:00
|
|
|
"go.pinniped.dev/internal/plog"
|
2020-10-17 00:51:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type jwksObserverController struct {
|
2020-12-16 22:27:09 +00:00
|
|
|
issuerToJWKSSetter IssuerToJWKSMapSetter
|
|
|
|
federationDomainInformer v1alpha1.FederationDomainInformer
|
|
|
|
secretInformer corev1informers.SecretInformer
|
2020-10-17 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type IssuerToJWKSMapSetter interface {
|
2020-12-03 20:34:58 +00:00
|
|
|
SetIssuerToJWKSMap(
|
|
|
|
issuerToJWKSMap map[string]*jose.JSONWebKeySet,
|
|
|
|
issuerToActiveJWKMap map[string]*jose.JSONWebKey,
|
|
|
|
)
|
2020-10-17 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 22:27:09 +00:00
|
|
|
// Returns a controller which watches all of the FederationDomains and their corresponding Secrets
|
2020-10-17 00:51:40 +00:00
|
|
|
// 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,
|
2020-12-16 22:27:09 +00:00
|
|
|
federationDomainInformer v1alpha1.FederationDomainInformer,
|
2020-10-17 00:51:40 +00:00
|
|
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
|
|
|
) controllerlib.Controller {
|
|
|
|
return controllerlib.New(
|
|
|
|
controllerlib.Config{
|
2020-10-27 00:03:26 +00:00
|
|
|
Name: "jwks-observer-controller",
|
2020-10-17 00:51:40 +00:00
|
|
|
Syncer: &jwksObserverController{
|
2020-12-16 22:27:09 +00:00
|
|
|
issuerToJWKSSetter: issuerToJWKSSetter,
|
|
|
|
federationDomainInformer: federationDomainInformer,
|
|
|
|
secretInformer: secretInformer,
|
2020-10-17 00:51:40 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
withInformer(
|
|
|
|
secretInformer,
|
2020-12-18 23:41:07 +00:00
|
|
|
pinnipedcontroller.MatchAnySecretOfTypeFilter(jwksSecretTypeValue, nil),
|
2020-10-17 00:51:40 +00:00
|
|
|
controllerlib.InformerOption{},
|
|
|
|
),
|
|
|
|
withInformer(
|
2020-12-16 22:27:09 +00:00
|
|
|
federationDomainInformer,
|
2020-11-04 19:08:50 +00:00
|
|
|
pinnipedcontroller.MatchAnythingFilter(nil),
|
2020-10-17 00:51:40 +00:00
|
|
|
controllerlib.InformerOption{},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *jwksObserverController) Sync(ctx controllerlib.Context) error {
|
|
|
|
ns := ctx.Key.Namespace
|
2020-12-16 22:27:09 +00:00
|
|
|
allProviders, err := c.federationDomainInformer.Lister().FederationDomains(ns).List(labels.Everything())
|
2020-10-17 00:51:40 +00:00
|
|
|
if err != nil {
|
2020-12-16 22:27:09 +00:00
|
|
|
return fmt.Errorf("failed to list FederationDomains: %w", err)
|
2020-10-17 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 22:27:09 +00:00
|
|
|
// Rebuild the whole map on any change to any Secret or FederationDomain, because either can have changes that
|
2020-10-17 00:51:40 +00:00
|
|
|
// can cause the map to need to be updated.
|
|
|
|
issuerToJWKSMap := map[string]*jose.JSONWebKeySet{}
|
2020-12-03 20:34:58 +00:00
|
|
|
issuerToActiveJWKMap := map[string]*jose.JSONWebKey{}
|
2020-10-17 00:51:40 +00:00
|
|
|
|
|
|
|
for _, provider := range allProviders {
|
2020-12-15 14:13:01 +00:00
|
|
|
secretRef := provider.Status.Secrets.JWKS
|
2020-10-17 00:51:40 +00:00
|
|
|
jwksSecret, err := c.secretInformer.Lister().Secrets(ns).Get(secretRef.Name)
|
|
|
|
if err != nil {
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("jwksObserverController Sync could not find JWKS secret", "namespace", ns, "secretName", secretRef.Name)
|
2020-10-17 00:51:40 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-12-03 20:34:58 +00:00
|
|
|
|
|
|
|
jwksFromSecret := jose.JSONWebKeySet{}
|
|
|
|
err = json.Unmarshal(jwksSecret.Data[jwksKey], &jwksFromSecret)
|
2020-10-17 00:51:40 +00:00
|
|
|
if err != nil {
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("jwksObserverController Sync found a JWKS secret with Data in an unexpected format", "namespace", ns, "secretName", secretRef.Name)
|
2020-10-17 00:51:40 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-12-03 20:34:58 +00:00
|
|
|
|
|
|
|
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
|
2020-10-17 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-12-03 20:34:58 +00:00
|
|
|
plog.Debug(
|
|
|
|
"jwksObserverController Sync updated the JWKS cache",
|
|
|
|
"issuerJWKSCount",
|
|
|
|
len(issuerToJWKSMap),
|
|
|
|
"issuerActiveJWKCount",
|
|
|
|
len(issuerToActiveJWKMap),
|
|
|
|
)
|
|
|
|
c.issuerToJWKSSetter.SetIssuerToJWKSMap(issuerToJWKSMap, issuerToActiveJWKMap)
|
2020-10-17 00:51:40 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|