8b7c30cfbd
- TLS certificates can be configured on the OIDCProviderConfig using the `secretName` field. - When listening for incoming TLS connections, choose the TLS cert based on the SNI hostname of the incoming request. - Because SNI hostname information on incoming requests does not include the port number of the request, we add a validation that OIDCProviderConfigs where the issuer hostnames (not including port number) are the same must use the same `secretName`. - Note that this approach does not yet support requests made to an IP address instead of a hostname. Also note that `localhost` is considered a hostname by SNI. - Add port 443 as a container port to the pod spec. - A new controller watches for TLS secrets and caches them in memory. That same in-memory cache is used while servicing incoming connections on the TLS port. - Make it easy to configure both port 443 and/or port 80 for various Service types using our ytt templates for the supervisor. - When deploying to kind, add another nodeport and forward it to the host on another port to expose our new HTTPS supervisor port to the host.
95 lines
3.3 KiB
Go
95 lines
3.3 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"
|
|
"k8s.io/klog/v2"
|
|
|
|
"go.pinniped.dev/generated/1.19/client/informers/externalversions/config/v1alpha1"
|
|
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
)
|
|
|
|
type jwksObserverController struct {
|
|
issuerToJWKSSetter IssuerToJWKSMapSetter
|
|
oidcProviderConfigInformer v1alpha1.OIDCProviderConfigInformer
|
|
secretInformer corev1informers.SecretInformer
|
|
}
|
|
|
|
type IssuerToJWKSMapSetter interface {
|
|
SetIssuerToJWKSMap(issuerToJWKSMap map[string]*jose.JSONWebKeySet)
|
|
}
|
|
|
|
// Returns a controller which watches all of the OIDCProviderConfigs 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,
|
|
oidcProviderConfigInformer v1alpha1.OIDCProviderConfigInformer,
|
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
|
) controllerlib.Controller {
|
|
return controllerlib.New(
|
|
controllerlib.Config{
|
|
Name: "jwks-observer-controller",
|
|
Syncer: &jwksObserverController{
|
|
issuerToJWKSSetter: issuerToJWKSSetter,
|
|
oidcProviderConfigInformer: oidcProviderConfigInformer,
|
|
secretInformer: secretInformer,
|
|
},
|
|
},
|
|
withInformer(
|
|
secretInformer,
|
|
pinnipedcontroller.MatchAnythingFilter(),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
withInformer(
|
|
oidcProviderConfigInformer,
|
|
pinnipedcontroller.MatchAnythingFilter(),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
)
|
|
}
|
|
|
|
func (c *jwksObserverController) Sync(ctx controllerlib.Context) error {
|
|
ns := ctx.Key.Namespace
|
|
allProviders, err := c.oidcProviderConfigInformer.Lister().OIDCProviderConfigs(ns).List(labels.Everything())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list OIDCProviderConfigs: %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{}
|
|
|
|
for _, provider := range allProviders {
|
|
secretRef := provider.Status.JWKSSecret
|
|
jwksSecret, err := c.secretInformer.Lister().Secrets(ns).Get(secretRef.Name)
|
|
if err != nil {
|
|
klog.InfoS("jwksObserverController Sync could not find JWKS secret", "namespace", ns, "secretName", secretRef.Name)
|
|
continue
|
|
}
|
|
jwkFromSecret := jose.JSONWebKeySet{}
|
|
err = json.Unmarshal(jwksSecret.Data[jwksKey], &jwkFromSecret)
|
|
if err != nil {
|
|
klog.InfoS("jwksObserverController Sync found a JWKS secret with Data in an unexpected format", "namespace", ns, "secretName", secretRef.Name)
|
|
continue
|
|
}
|
|
issuerToJWKSMap[provider.Spec.Issuer] = &jwkFromSecret
|
|
}
|
|
|
|
klog.InfoS("jwksObserverController Sync updated the JWKS cache", "issuerCount", len(issuerToJWKSMap))
|
|
c.issuerToJWKSSetter.SetIssuerToJWKSMap(issuerToJWKSMap)
|
|
|
|
return nil
|
|
}
|