mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 22:44:18 +00:00
Rename existing references to "IDP" and "Identity Provider".
Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package webhookcachefiller implements a controller for filling an authncache.Cache with each added/updated WebhookAuthenticator.
|
||||
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"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
|
||||
authinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions/authentication/v1alpha1"
|
||||
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
)
|
||||
|
||||
// New instantiates a new controllerlib.Controller which will populate the provided authncache.Cache.
|
||||
func New(cache *authncache.Cache, webhooks authinformers.WebhookAuthenticatorInformer, log logr.Logger) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: "webhookcachefiller-controller",
|
||||
Syncer: &controller{
|
||||
cache: cache,
|
||||
webhooks: webhooks,
|
||||
log: log.WithName("webhookcachefiller-controller"),
|
||||
},
|
||||
},
|
||||
controllerlib.WithInformer(
|
||||
webhooks,
|
||||
pinnipedcontroller.MatchAnythingFilter(),
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
cache *authncache.Cache
|
||||
webhooks authinformers.WebhookAuthenticatorInformer
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
obj, err := c.webhooks.Lister().WebhookAuthenticators(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
c.log.Info("Sync() found that the WebhookAuthenticator does not exist yet or was deleted")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get WebhookAuthenticator %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(authncache.Key{
|
||||
APIGroup: auth1alpha1.GroupName,
|
||||
Kind: "WebhookAuthenticator",
|
||||
Namespace: ctx.Key.Namespace,
|
||||
Name: ctx.Key.Name,
|
||||
}, webhookAuthenticator)
|
||||
c.log.WithValues("webhook", klog.KObj(obj), "endpoint", obj.Spec.Endpoint).Info("added new webhook authenticator")
|
||||
return nil
|
||||
}
|
||||
|
||||
// newWebhookAuthenticator creates a webhook from the provided API server url and caBundle
|
||||
// used to validate TLS connections.
|
||||
func newWebhookAuthenticator(
|
||||
spec *auth1alpha1.WebhookAuthenticatorSpec,
|
||||
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 *auth1alpha1.TLSSpec) ([]byte, error) {
|
||||
if spec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(spec.CertificateAuthorityData)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webhookcachefiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
|
||||
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
|
||||
pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
)
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
syncKey controllerlib.Key
|
||||
webhooks []runtime.Object
|
||||
wantErr string
|
||||
wantLogs []string
|
||||
wantCacheEntries int
|
||||
}{
|
||||
{
|
||||
name: "not found",
|
||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||
wantLogs: []string{
|
||||
`webhookcachefiller-controller "level"=0 "msg"="Sync() found that the WebhookAuthenticator does not exist yet or was deleted"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid webhook",
|
||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||
webhooks: []runtime.Object{
|
||||
&auth1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "invalid url",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: `failed to build webhook config: parse "http://invalid url": invalid character " " in host name`,
|
||||
},
|
||||
{
|
||||
name: "valid webhook",
|
||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||
webhooks: []runtime.Object{
|
||||
&auth1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "test-namespace",
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://example.com",
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`webhookcachefiller-controller "level"=0 "msg"="added new webhook authenticator" "endpoint"="https://example.com" "webhook"={"name":"test-name","namespace":"test-namespace"}`,
|
||||
},
|
||||
wantCacheEntries: 1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fakeClient := pinnipedfake.NewSimpleClientset(tt.webhooks...)
|
||||
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
|
||||
cache := authncache.New()
|
||||
testLog := testlogger.New(t)
|
||||
|
||||
controller := New(cache, informers.Authentication().V1alpha1().WebhookAuthenticators(), testLog)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
informers.Start(ctx.Done())
|
||||
controllerlib.TestRunSynchronously(t, controller)
|
||||
|
||||
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
|
||||
|
||||
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
||||
require.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.wantLogs, testLog.Lines())
|
||||
require.Equal(t, tt.wantCacheEntries, len(cache.Keys()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWebhookAuthenticator(t *testing.T) {
|
||||
t.Run("temp file failure", func(t *testing.T) {
|
||||
brokenTempFile := func(_ string, _ string) (*os.File, error) { return nil, fmt.Errorf("some temp file error") }
|
||||
res, err := newWebhookAuthenticator(nil, brokenTempFile, clientcmd.WriteToFile)
|
||||
require.Nil(t, res)
|
||||
require.EqualError(t, err, "unable to create temporary file: some temp file error")
|
||||
})
|
||||
|
||||
t.Run("marshal failure", func(t *testing.T) {
|
||||
marshalError := func(_ clientcmdapi.Config, _ string) error { return fmt.Errorf("some marshal error") }
|
||||
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{}, ioutil.TempFile, marshalError)
|
||||
require.Nil(t, res)
|
||||
require.EqualError(t, err, "unable to marshal kubeconfig: some marshal error")
|
||||
})
|
||||
|
||||
t.Run("invalid base64", func(t *testing.T) {
|
||||
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://example.com",
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid-base64"},
|
||||
}, ioutil.TempFile, clientcmd.WriteToFile)
|
||||
require.Nil(t, res)
|
||||
require.EqualError(t, err, "invalid TLS configuration: illegal base64 data at input byte 7")
|
||||
})
|
||||
|
||||
t.Run("valid config with no TLS spec", func(t *testing.T) {
|
||||
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://example.com",
|
||||
}, ioutil.TempFile, clientcmd.WriteToFile)
|
||||
require.NotNil(t, res)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
caBundle, url := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "test-token")
|
||||
_, err = w.Write([]byte(`{}`))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
spec := &auth1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: url,
|
||||
TLS: &auth1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(caBundle)),
|
||||
},
|
||||
}
|
||||
res, err := newWebhookAuthenticator(spec, ioutil.TempFile, clientcmd.WriteToFile)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
resp, authenticated, err := res.AuthenticateToken(context.Background(), "test-token")
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
require.False(t, authenticated)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user