38802c2184
- Setting a Secret in the supervisor's namespace with a special name will cause it to get picked up and served as the supervisor's TLS cert for any request which does not have a matching SNI cert. - This is especially useful for when there is no DNS record for an issuer and the user will be accessing it via IP address. This is not how we would expect it to be used in production, but it might be useful for other cases. - Includes a new integration test - Also suppress all of the warnings about ignoring the error returned by Close() in lines like `defer x.Close()` to make GoLand happier
120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package supervisorconfig
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"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"
|
|
)
|
|
|
|
const SecretNameForDefaultTLSCertificate = "default-tls-certificate" //nolint:gosec // this is not a hardcoded credential
|
|
|
|
type tlsCertObserverController struct {
|
|
issuerTLSCertSetter IssuerTLSCertSetter
|
|
oidcProviderConfigInformer v1alpha1.OIDCProviderConfigInformer
|
|
secretInformer corev1informers.SecretInformer
|
|
}
|
|
|
|
type IssuerTLSCertSetter interface {
|
|
SetIssuerHostToTLSCertMap(issuerHostToTLSCertMap map[string]*tls.Certificate)
|
|
SetDefaultTLSCert(certificate *tls.Certificate)
|
|
}
|
|
|
|
func NewTLSCertObserverController(
|
|
issuerTLSCertSetter IssuerTLSCertSetter,
|
|
secretInformer corev1informers.SecretInformer,
|
|
oidcProviderConfigInformer v1alpha1.OIDCProviderConfigInformer,
|
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
|
) controllerlib.Controller {
|
|
return controllerlib.New(
|
|
controllerlib.Config{
|
|
Name: "tls-certs-observer-controller",
|
|
Syncer: &tlsCertObserverController{
|
|
issuerTLSCertSetter: issuerTLSCertSetter,
|
|
oidcProviderConfigInformer: oidcProviderConfigInformer,
|
|
secretInformer: secretInformer,
|
|
},
|
|
},
|
|
withInformer(
|
|
secretInformer,
|
|
pinnipedcontroller.MatchAnythingFilter(),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
withInformer(
|
|
oidcProviderConfigInformer,
|
|
pinnipedcontroller.MatchAnythingFilter(),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
)
|
|
}
|
|
|
|
func (c *tlsCertObserverController) 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.
|
|
issuerHostToTLSCertMap := map[string]*tls.Certificate{}
|
|
|
|
for _, provider := range allProviders {
|
|
secretName := provider.Spec.SNICertificateSecretName
|
|
issuerURL, err := url.Parse(provider.Spec.Issuer)
|
|
if err != nil {
|
|
klog.InfoS("tlsCertObserverController Sync found an invalid issuer URL", "namespace", ns, "issuer", provider.Spec.Issuer)
|
|
continue
|
|
}
|
|
certFromSecret, err := c.certFromSecret(ns, secretName)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// Lowercase the host part of the URL because hostnames should be treated as case-insensitive.
|
|
issuerHostToTLSCertMap[lowercaseHostWithoutPort(issuerURL)] = certFromSecret
|
|
}
|
|
|
|
klog.InfoS("tlsCertObserverController Sync updated the TLS cert cache", "issuerHostCount", len(issuerHostToTLSCertMap))
|
|
c.issuerTLSCertSetter.SetIssuerHostToTLSCertMap(issuerHostToTLSCertMap)
|
|
|
|
defaultCert, err := c.certFromSecret(ns, SecretNameForDefaultTLSCertificate)
|
|
if err != nil {
|
|
c.issuerTLSCertSetter.SetDefaultTLSCert(nil)
|
|
} else {
|
|
c.issuerTLSCertSetter.SetDefaultTLSCert(defaultCert)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *tlsCertObserverController) certFromSecret(ns string, secretName string) (*tls.Certificate, error) {
|
|
tlsSecret, err := c.secretInformer.Lister().Secrets(ns).Get(secretName)
|
|
if err != nil {
|
|
klog.InfoS("tlsCertObserverController Sync could not find TLS cert secret", "namespace", ns, "secretName", secretName)
|
|
return nil, err
|
|
}
|
|
certFromSecret, err := tls.X509KeyPair(tlsSecret.Data["tls.crt"], tlsSecret.Data["tls.key"])
|
|
if err != nil {
|
|
klog.InfoS("tlsCertObserverController Sync found a TLS secret with Data in an unexpected format", "namespace", ns, "secretName", secretName)
|
|
return nil, err
|
|
}
|
|
return &certFromSecret, nil
|
|
}
|
|
|
|
func lowercaseHostWithoutPort(issuerURL *url.URL) string {
|
|
lowercaseHost := strings.ToLower(issuerURL.Host)
|
|
colonSegments := strings.Split(lowercaseHost, ":")
|
|
return colonSegments[0]
|
|
}
|