6cdd4a9506
This also has fallback compatibility support if no IDP is specified and there is exactly one IDP in the cache. Signed-off-by: Matt Moyer <moyerm@vmware.com>
131 lines
4.5 KiB
Go
131 lines
4.5 KiB
Go
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package webhookcachefiller implements a controller for filling an idpcache.Cache with each added/updated WebhookIdentityProvider.
|
|
package webhookcachefiller
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/go-logr/logr"
|
|
k8sauthv1beta1 "k8s.io/api/authentication/v1beta1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/util/net"
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
"k8s.io/klog/v2"
|
|
|
|
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1"
|
|
idpinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions/idp/v1alpha1"
|
|
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
|
"go.pinniped.dev/internal/controller/identityprovider/idpcache"
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
)
|
|
|
|
// New instantiates a new controllerlib.Controller which will populate the provided idpcache.Cache.
|
|
func New(cache *idpcache.Cache, webhookIDPs idpinformers.WebhookIdentityProviderInformer, log logr.Logger) controllerlib.Controller {
|
|
return controllerlib.New(
|
|
controllerlib.Config{
|
|
Name: "webhookcachefiller-controller",
|
|
Syncer: &controller{
|
|
cache: cache,
|
|
webhookIDPs: webhookIDPs,
|
|
log: log.WithName("webhookcachefiller-controller"),
|
|
},
|
|
},
|
|
controllerlib.WithInformer(
|
|
webhookIDPs,
|
|
pinnipedcontroller.NoOpFilter(),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
)
|
|
}
|
|
|
|
type controller struct {
|
|
cache *idpcache.Cache
|
|
webhookIDPs idpinformers.WebhookIdentityProviderInformer
|
|
log logr.Logger
|
|
}
|
|
|
|
// Sync implements controllerlib.Syncer.
|
|
func (c *controller) Sync(ctx controllerlib.Context) error {
|
|
obj, err := c.webhookIDPs.Lister().WebhookIdentityProviders(ctx.Key.Namespace).Get(ctx.Key.Name)
|
|
if err != nil && errors.IsNotFound(err) {
|
|
c.log.Info("Sync() found that the WebhookIdentityProvider does not exist yet or was deleted")
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get WebhookIdentityProvider %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
|
|
}
|
|
|
|
webhookAuthenticator, err := newWebhookAuthenticator(&obj.Spec, ioutil.TempFile, clientcmd.WriteToFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build webhook config: %w", err)
|
|
}
|
|
|
|
c.cache.Store(idpcache.Key{
|
|
APIGroup: idpv1alpha1.GroupName,
|
|
Kind: "WebhookIdentityProvider",
|
|
Namespace: ctx.Key.Namespace,
|
|
Name: ctx.Key.Name,
|
|
}, webhookAuthenticator)
|
|
c.log.WithValues("idp", klog.KObj(obj), "endpoint", obj.Spec.Endpoint).Info("added new webhook IDP")
|
|
return nil
|
|
}
|
|
|
|
// newWebhookAuthenticator creates a webhook from the provided API server url and caBundle
|
|
// used to validate TLS connections.
|
|
func newWebhookAuthenticator(
|
|
spec *idpv1alpha1.WebhookIdentityProviderSpec,
|
|
tempfileFunc func(string, string) (*os.File, error),
|
|
marshalFunc func(clientcmdapi.Config, string) error,
|
|
) (*webhook.WebhookTokenAuthenticator, error) {
|
|
temp, err := tempfileFunc("", "pinniped-webhook-kubeconfig-*")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create temporary file: %w", err)
|
|
}
|
|
defer func() { _ = os.Remove(temp.Name()) }()
|
|
|
|
cluster := &clientcmdapi.Cluster{Server: spec.Endpoint}
|
|
cluster.CertificateAuthorityData, err = getCABundle(spec.TLS)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid TLS configuration: %w", err)
|
|
}
|
|
|
|
kubeconfig := clientcmdapi.NewConfig()
|
|
kubeconfig.Clusters["anonymous-cluster"] = cluster
|
|
kubeconfig.Contexts["anonymous"] = &clientcmdapi.Context{Cluster: "anonymous-cluster"}
|
|
kubeconfig.CurrentContext = "anonymous"
|
|
|
|
if err := marshalFunc(*kubeconfig, temp.Name()); err != nil {
|
|
return nil, fmt.Errorf("unable to marshal kubeconfig: %w", err)
|
|
}
|
|
|
|
// We use v1beta1 instead of v1 since v1beta1 is more prevalent in our desired
|
|
// integration points.
|
|
version := k8sauthv1beta1.SchemeGroupVersion.Version
|
|
|
|
// At the current time, we don't provide any audiences because we simply don't
|
|
// have any requirements to do so. This can be changed in the future as
|
|
// requirements change.
|
|
var implicitAuds authenticator.Audiences
|
|
|
|
// We set this to nil because we would only need this to support some of the
|
|
// custom proxy stuff used by the API server.
|
|
var customDial net.DialFunc
|
|
|
|
return webhook.New(temp.Name(), version, implicitAuds, customDial)
|
|
}
|
|
|
|
func getCABundle(spec *idpv1alpha1.TLSSpec) ([]byte, error) {
|
|
if spec == nil {
|
|
return nil, nil
|
|
}
|
|
return base64.StdEncoding.DecodeString(spec.CertificateAuthorityData)
|
|
}
|