2020-10-06 00:28:19 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-10-27 00:03:26 +00:00
|
|
|
"crypto/tls"
|
2020-10-06 00:28:19 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
2020-10-27 00:03:26 +00:00
|
|
|
"strings"
|
2020-10-06 00:28:19 +00:00
|
|
|
"time"
|
|
|
|
|
2020-12-11 01:27:02 +00:00
|
|
|
"go.pinniped.dev/internal/secret"
|
|
|
|
|
2020-12-11 16:11:49 +00:00
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2020-10-08 17:27:45 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/clock"
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeinformers "k8s.io/client-go/informers"
|
|
|
|
"k8s.io/client-go/kubernetes"
|
2020-10-06 00:28:19 +00:00
|
|
|
"k8s.io/client-go/pkg/version"
|
|
|
|
"k8s.io/client-go/rest"
|
|
|
|
restclient "k8s.io/client-go/rest"
|
|
|
|
"k8s.io/component-base/logs"
|
|
|
|
"k8s.io/klog/v2"
|
2020-11-11 23:10:06 +00:00
|
|
|
"k8s.io/klog/v2/klogr"
|
2020-10-06 00:28:19 +00:00
|
|
|
|
2020-10-30 20:09:14 +00:00
|
|
|
pinnipedclientset "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned"
|
|
|
|
pinnipedinformers "go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions"
|
2020-10-15 19:40:56 +00:00
|
|
|
"go.pinniped.dev/internal/config/supervisor"
|
2020-10-06 00:28:19 +00:00
|
|
|
"go.pinniped.dev/internal/controller/supervisorconfig"
|
2020-12-12 00:05:08 +00:00
|
|
|
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
|
2020-11-11 23:10:06 +00:00
|
|
|
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
|
2020-10-06 00:28:19 +00:00
|
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
|
|
"go.pinniped.dev/internal/downward"
|
2020-10-17 00:51:40 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/jwks"
|
2020-10-27 00:03:26 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/provider"
|
2020-10-08 18:28:21 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/provider/manager"
|
2020-11-10 13:48:42 +00:00
|
|
|
"go.pinniped.dev/internal/plog"
|
2020-10-06 00:28:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
singletonWorker = 1
|
|
|
|
defaultResyncInterval = 3 * time.Minute
|
|
|
|
)
|
|
|
|
|
2020-10-08 02:18:34 +00:00
|
|
|
func start(ctx context.Context, l net.Listener, handler http.Handler) {
|
|
|
|
server := http.Server{Handler: handler}
|
2020-10-06 00:28:19 +00:00
|
|
|
|
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
errCh <- server.Serve(l)
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case err := <-errCh:
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("server exited", "err", err)
|
2020-10-06 00:28:19 +00:00
|
|
|
case <-ctx.Done():
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("server context cancelled", "err", ctx.Err())
|
2020-10-06 00:28:19 +00:00
|
|
|
if err := server.Shutdown(context.Background()); err != nil {
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("server shutdown failed", "err", err)
|
2020-10-06 00:28:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitForSignal() os.Signal {
|
|
|
|
signalCh := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(signalCh, os.Interrupt)
|
|
|
|
return <-signalCh
|
|
|
|
}
|
|
|
|
|
|
|
|
func startControllers(
|
|
|
|
ctx context.Context,
|
2020-10-15 19:40:56 +00:00
|
|
|
cfg *supervisor.Config,
|
2020-10-17 00:51:40 +00:00
|
|
|
issuerManager *manager.Manager,
|
|
|
|
dynamicJWKSProvider jwks.DynamicJWKSProvider,
|
2020-10-27 00:03:26 +00:00
|
|
|
dynamicTLSCertProvider provider.DynamicTLSCertProvider,
|
2020-11-11 23:10:06 +00:00
|
|
|
dynamicUpstreamIDPProvider provider.DynamicUpstreamIDPProvider,
|
2020-12-11 16:11:49 +00:00
|
|
|
secretCache *secret.Cache,
|
|
|
|
supervisorDeployment *appsv1.Deployment,
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeClient kubernetes.Interface,
|
2020-10-08 17:27:45 +00:00
|
|
|
pinnipedClient pinnipedclientset.Interface,
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeInformers kubeinformers.SharedInformerFactory,
|
2020-10-07 14:53:05 +00:00
|
|
|
pinnipedInformers pinnipedinformers.SharedInformerFactory,
|
2020-10-06 00:28:19 +00:00
|
|
|
) {
|
|
|
|
// Create controller manager.
|
|
|
|
controllerManager := controllerlib.
|
|
|
|
NewManager().
|
|
|
|
WithController(
|
2020-11-02 22:24:55 +00:00
|
|
|
supervisorconfig.NewOIDCProviderWatcherController(
|
2020-10-17 00:51:40 +00:00
|
|
|
issuerManager,
|
2020-10-08 17:27:45 +00:00
|
|
|
clock.RealClock{},
|
|
|
|
pinnipedClient,
|
2020-11-02 22:24:55 +00:00
|
|
|
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
|
2020-10-06 00:28:19 +00:00
|
|
|
controllerlib.WithInformer,
|
|
|
|
),
|
|
|
|
singletonWorker,
|
2020-10-14 13:47:34 +00:00
|
|
|
).
|
|
|
|
WithController(
|
2020-10-17 00:51:40 +00:00
|
|
|
supervisorconfig.NewJWKSWriterController(
|
2020-10-15 19:40:56 +00:00
|
|
|
cfg.Labels,
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeClient,
|
|
|
|
pinnipedClient,
|
|
|
|
kubeInformers.Core().V1().Secrets(),
|
2020-11-02 22:24:55 +00:00
|
|
|
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
|
2020-10-14 13:47:34 +00:00
|
|
|
controllerlib.WithInformer,
|
|
|
|
),
|
|
|
|
singletonWorker,
|
2020-10-17 00:51:40 +00:00
|
|
|
).
|
|
|
|
WithController(
|
|
|
|
supervisorconfig.NewJWKSObserverController(
|
|
|
|
dynamicJWKSProvider,
|
|
|
|
kubeInformers.Core().V1().Secrets(),
|
2020-11-02 22:24:55 +00:00
|
|
|
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
|
2020-10-17 00:51:40 +00:00
|
|
|
controllerlib.WithInformer,
|
|
|
|
),
|
|
|
|
singletonWorker,
|
2020-10-27 00:03:26 +00:00
|
|
|
).
|
|
|
|
WithController(
|
|
|
|
supervisorconfig.NewTLSCertObserverController(
|
|
|
|
dynamicTLSCertProvider,
|
2020-10-28 18:56:50 +00:00
|
|
|
cfg.NamesConfig.DefaultTLSCertificateSecret,
|
2020-10-27 00:03:26 +00:00
|
|
|
kubeInformers.Core().V1().Secrets(),
|
2020-11-02 22:24:55 +00:00
|
|
|
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
|
2020-10-27 00:03:26 +00:00
|
|
|
controllerlib.WithInformer,
|
|
|
|
),
|
|
|
|
singletonWorker,
|
2020-11-11 23:10:06 +00:00
|
|
|
).
|
2020-12-11 16:11:49 +00:00
|
|
|
WithController(
|
2020-12-12 00:05:08 +00:00
|
|
|
generator.NewSupervisorSecretsController(
|
2020-12-11 16:11:49 +00:00
|
|
|
supervisorDeployment,
|
|
|
|
kubeClient,
|
|
|
|
kubeInformers.Core().V1().Secrets(),
|
|
|
|
func(secret []byte) {
|
|
|
|
plog.Debug("setting csrf cookie secret")
|
|
|
|
secretCache.SetCSRFCookieEncoderHashKey(secret)
|
|
|
|
},
|
|
|
|
),
|
|
|
|
singletonWorker,
|
|
|
|
).
|
2020-11-11 23:10:06 +00:00
|
|
|
WithController(
|
|
|
|
upstreamwatcher.New(
|
|
|
|
dynamicUpstreamIDPProvider,
|
|
|
|
pinnipedClient,
|
|
|
|
pinnipedInformers.IDP().V1alpha1().UpstreamOIDCProviders(),
|
|
|
|
kubeInformers.Core().V1().Secrets(),
|
|
|
|
klogr.New()),
|
|
|
|
singletonWorker)
|
2020-10-06 00:28:19 +00:00
|
|
|
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeInformers.Start(ctx.Done())
|
2020-10-07 14:53:05 +00:00
|
|
|
pinnipedInformers.Start(ctx.Done())
|
2020-10-06 00:28:19 +00:00
|
|
|
|
2020-10-21 18:51:31 +00:00
|
|
|
// Wait until the caches are synced before returning.
|
|
|
|
kubeInformers.WaitForCacheSync(ctx.Done())
|
|
|
|
pinnipedInformers.WaitForCacheSync(ctx.Done())
|
|
|
|
|
2020-10-06 00:28:19 +00:00
|
|
|
go controllerManager.Start(ctx)
|
|
|
|
}
|
|
|
|
|
2020-12-11 16:11:49 +00:00
|
|
|
func getSupervisorDeployment(
|
|
|
|
ctx context.Context,
|
|
|
|
kubeClient kubernetes.Interface,
|
|
|
|
podInfo *downward.PodInfo,
|
|
|
|
) (*appsv1.Deployment, error) {
|
|
|
|
ns := podInfo.Namespace
|
|
|
|
|
|
|
|
pod, err := kubeClient.CoreV1().Pods(ns).Get(ctx, podInfo.Name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not get pod: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
podOwner := metav1.GetControllerOf(pod)
|
|
|
|
if podOwner == nil {
|
|
|
|
return nil, fmt.Errorf("pod %s/%s is missing owner", ns, podInfo.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
rs, err := kubeClient.AppsV1().ReplicaSets(ns).Get(ctx, podOwner.Name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not get replicaset: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rsOwner := metav1.GetControllerOf(rs)
|
|
|
|
if rsOwner == nil {
|
|
|
|
return nil, fmt.Errorf("replicaset %s/%s is missing owner", ns, podInfo.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
d, err := kubeClient.AppsV1().Deployments(ns).Get(ctx, rsOwner.Name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not get deployment: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
2020-10-14 13:47:34 +00:00
|
|
|
func newClients() (kubernetes.Interface, pinnipedclientset.Interface, error) {
|
2020-10-06 00:28:19 +00:00
|
|
|
kubeConfig, err := restclient.InClusterConfig()
|
|
|
|
if err != nil {
|
2020-10-14 13:47:34 +00:00
|
|
|
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
2020-10-06 00:28:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to the core Kubernetes API.
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("could not create kube client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to the Pinniped API.
|
2020-10-07 14:53:05 +00:00
|
|
|
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
|
2020-10-06 00:28:19 +00:00
|
|
|
if err != nil {
|
2020-10-14 13:47:34 +00:00
|
|
|
return nil, nil, fmt.Errorf("could not create pinniped client: %w", err)
|
2020-10-06 00:28:19 +00:00
|
|
|
}
|
|
|
|
|
2020-10-14 13:47:34 +00:00
|
|
|
return kubeClient, pinnipedClient, nil
|
2020-10-06 00:28:19 +00:00
|
|
|
}
|
|
|
|
|
2020-12-11 16:11:49 +00:00
|
|
|
func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
|
|
|
serverInstallationNamespace := podInfo.Namespace
|
|
|
|
|
2020-10-06 00:28:19 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeClient, pinnipedClient, err := newClients()
|
2020-10-06 00:28:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create k8s client: %w", err)
|
|
|
|
}
|
|
|
|
|
2020-10-14 13:47:34 +00:00
|
|
|
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
|
|
|
kubeClient,
|
|
|
|
defaultResyncInterval,
|
|
|
|
kubeinformers.WithNamespace(serverInstallationNamespace),
|
|
|
|
)
|
|
|
|
|
2020-10-07 14:53:05 +00:00
|
|
|
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
|
|
|
|
pinnipedClient,
|
2020-10-06 00:28:19 +00:00
|
|
|
defaultResyncInterval,
|
2020-10-07 14:53:05 +00:00
|
|
|
pinnipedinformers.WithNamespace(serverInstallationNamespace),
|
2020-10-06 00:28:19 +00:00
|
|
|
)
|
|
|
|
|
2020-10-21 22:24:48 +00:00
|
|
|
// Serve the /healthz endpoint and make all other paths result in 404.
|
|
|
|
healthMux := http.NewServeMux()
|
|
|
|
healthMux.Handle("/healthz", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
|
|
_, _ = writer.Write([]byte("ok"))
|
|
|
|
}))
|
|
|
|
|
2020-10-17 00:51:40 +00:00
|
|
|
dynamicJWKSProvider := jwks.NewDynamicJWKSProvider()
|
2020-10-27 00:03:26 +00:00
|
|
|
dynamicTLSCertProvider := provider.NewDynamicTLSCertProvider()
|
2020-11-05 01:06:47 +00:00
|
|
|
dynamicUpstreamIDPProvider := provider.NewDynamicUpstreamIDPProvider()
|
2020-12-11 16:11:49 +00:00
|
|
|
secretCache := secret.Cache{}
|
2020-10-27 00:03:26 +00:00
|
|
|
|
2020-10-21 22:24:48 +00:00
|
|
|
// OIDC endpoints will be served by the oidProvidersManager, and any non-OIDC paths will fallback to the healthMux.
|
2020-12-03 01:39:45 +00:00
|
|
|
oidProvidersManager := manager.NewManager(
|
|
|
|
healthMux,
|
|
|
|
dynamicJWKSProvider,
|
|
|
|
dynamicUpstreamIDPProvider,
|
2020-12-11 16:11:49 +00:00
|
|
|
&secretCache,
|
2020-12-03 01:39:45 +00:00
|
|
|
kubeClient.CoreV1().Secrets(serverInstallationNamespace),
|
|
|
|
)
|
2020-10-27 00:03:26 +00:00
|
|
|
|
2020-12-11 16:11:49 +00:00
|
|
|
supervisorDeployment, err := getSupervisorDeployment(ctx, kubeClient, podInfo)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get supervisor deployment: %w", err)
|
|
|
|
}
|
|
|
|
|
2020-10-27 00:03:26 +00:00
|
|
|
startControllers(
|
|
|
|
ctx,
|
|
|
|
cfg,
|
|
|
|
oidProvidersManager,
|
|
|
|
dynamicJWKSProvider,
|
|
|
|
dynamicTLSCertProvider,
|
2020-11-11 23:10:06 +00:00
|
|
|
dynamicUpstreamIDPProvider,
|
2020-12-11 16:11:49 +00:00
|
|
|
&secretCache,
|
|
|
|
supervisorDeployment,
|
2020-10-27 00:03:26 +00:00
|
|
|
kubeClient,
|
|
|
|
pinnipedClient,
|
|
|
|
kubeInformers,
|
|
|
|
pinnipedInformers,
|
|
|
|
)
|
2020-10-06 00:28:19 +00:00
|
|
|
|
|
|
|
//nolint: gosec // Intentionally binding to all network interfaces.
|
2020-11-02 16:57:05 +00:00
|
|
|
httpListener, err := net.Listen("tcp", ":8080")
|
2020-10-06 00:28:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create listener: %w", err)
|
|
|
|
}
|
2020-10-27 23:33:08 +00:00
|
|
|
defer func() { _ = httpListener.Close() }()
|
2020-10-27 00:03:26 +00:00
|
|
|
start(ctx, httpListener, oidProvidersManager)
|
2020-10-21 18:51:31 +00:00
|
|
|
|
2020-10-27 00:03:26 +00:00
|
|
|
//nolint: gosec // Intentionally binding to all network interfaces.
|
2020-11-02 16:57:05 +00:00
|
|
|
httpsListener, err := tls.Listen("tcp", ":8443", &tls.Config{
|
2020-10-27 21:57:25 +00:00
|
|
|
MinVersion: tls.VersionTLS12, // Allow v1.2 because clients like the default `curl` on MacOS don't support 1.3 yet.
|
2020-10-27 00:03:26 +00:00
|
|
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
2020-10-27 21:57:25 +00:00
|
|
|
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
|
2020-10-27 23:33:08 +00:00
|
|
|
defaultCert := dynamicTLSCertProvider.GetDefaultTLSCert()
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("GetCertificate called for port 8443",
|
2020-10-27 23:33:08 +00:00
|
|
|
"info.ServerName", info.ServerName,
|
|
|
|
"foundSNICert", cert != nil,
|
|
|
|
"foundDefaultCert", defaultCert != nil,
|
|
|
|
)
|
|
|
|
if cert == nil {
|
|
|
|
cert = defaultCert
|
|
|
|
}
|
2020-10-27 21:57:25 +00:00
|
|
|
return cert, nil
|
2020-10-27 00:03:26 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create listener: %w", err)
|
|
|
|
}
|
2020-10-27 23:33:08 +00:00
|
|
|
defer func() { _ = httpsListener.Close() }()
|
2020-10-27 00:03:26 +00:00
|
|
|
start(ctx, httpsListener, oidProvidersManager)
|
|
|
|
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("supervisor is ready",
|
2020-10-27 00:03:26 +00:00
|
|
|
"httpAddress", httpListener.Addr().String(),
|
|
|
|
"httpsAddress", httpsListener.Addr().String(),
|
|
|
|
)
|
2020-10-06 00:28:19 +00:00
|
|
|
|
|
|
|
gotSignal := waitForSignal()
|
2020-11-10 15:22:16 +00:00
|
|
|
plog.Debug("supervisor exiting", "signal", gotSignal)
|
2020-10-06 00:28:19 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
logs.InitLogs()
|
|
|
|
defer logs.FlushLogs()
|
2020-11-10 13:48:42 +00:00
|
|
|
plog.RemoveKlogGlobalFlags() // move this whenever the below code gets refactored to use cobra
|
2020-10-06 00:28:19 +00:00
|
|
|
|
|
|
|
klog.Infof("Running %s at %#v", rest.DefaultKubernetesUserAgent(), version.Get())
|
|
|
|
klog.Infof("Command-line arguments were: %s %s %s", os.Args[0], os.Args[1], os.Args[2])
|
|
|
|
|
|
|
|
// Discover in which namespace we are installed.
|
|
|
|
podInfo, err := downward.Load(os.Args[1])
|
|
|
|
if err != nil {
|
|
|
|
klog.Fatal(fmt.Errorf("could not read pod metadata: %w", err))
|
|
|
|
}
|
|
|
|
|
2020-10-15 19:40:56 +00:00
|
|
|
// Read the server config file.
|
|
|
|
cfg, err := supervisor.FromPath(os.Args[2])
|
|
|
|
if err != nil {
|
|
|
|
klog.Fatal(fmt.Errorf("could not load config: %w", err))
|
|
|
|
}
|
|
|
|
|
2020-12-11 16:11:49 +00:00
|
|
|
if err := run(podInfo, cfg); err != nil {
|
2020-10-06 00:28:19 +00:00
|
|
|
klog.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|