2020-07-08 17:06:44 +00:00
|
|
|
/*
|
|
|
|
Copyright 2020 VMware, Inc.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
2020-08-20 17:54:15 +00:00
|
|
|
// Package server is the command line entry point for pinniped-server.
|
2020-07-27 20:32:14 +00:00
|
|
|
package server
|
2020-07-08 17:06:44 +00:00
|
|
|
|
|
|
|
import (
|
2020-07-13 19:30:16 +00:00
|
|
|
"context"
|
|
|
|
"fmt"
|
2020-07-08 17:06:44 +00:00
|
|
|
"io"
|
2020-08-19 18:21:07 +00:00
|
|
|
"time"
|
2020-07-08 17:06:44 +00:00
|
|
|
|
2020-07-27 12:55:33 +00:00
|
|
|
"github.com/spf13/cobra"
|
2020-07-23 15:05:21 +00:00
|
|
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
|
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
|
|
|
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
2020-08-19 18:21:07 +00:00
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
restclient "k8s.io/client-go/rest"
|
2020-07-16 19:24:30 +00:00
|
|
|
|
2020-08-20 17:54:15 +00:00
|
|
|
"github.com/suzerain-io/pinniped/internal/apiserver"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/certauthority/kubecertauthority"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/controllermanager"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/downward"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/provider"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/registry/credentialrequest"
|
|
|
|
pinnipedv1alpha1 "github.com/suzerain-io/pinniped/kubernetes/1.19/api/apis/pinniped/v1alpha1"
|
|
|
|
"github.com/suzerain-io/pinniped/pkg/config"
|
2020-07-08 17:06:44 +00:00
|
|
|
)
|
|
|
|
|
2020-08-20 17:54:15 +00:00
|
|
|
// App is an object that represents the pinniped-server application.
|
2020-07-08 17:06:44 +00:00
|
|
|
type App struct {
|
2020-08-11 01:53:53 +00:00
|
|
|
cmd *cobra.Command
|
2020-07-08 17:06:44 +00:00
|
|
|
|
2020-07-16 19:24:30 +00:00
|
|
|
// CLI flags
|
2020-08-20 19:17:18 +00:00
|
|
|
configPath string
|
|
|
|
downwardAPIPath string
|
2020-07-08 17:06:44 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
// This is ignored for now because we turn off etcd storage below, but this is
|
|
|
|
// the right prefix in case we turn it back on.
|
2020-08-20 17:54:15 +00:00
|
|
|
const defaultEtcdPathPrefix = "/registry/" + pinnipedv1alpha1.GroupName
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-07-08 17:06:44 +00:00
|
|
|
// New constructs a new App with command line args, stdout and stderr.
|
2020-07-23 15:05:21 +00:00
|
|
|
func New(ctx context.Context, args []string, stdout, stderr io.Writer) *App {
|
2020-08-07 21:49:04 +00:00
|
|
|
app := &App{}
|
|
|
|
app.addServerCommand(ctx, args, stdout, stderr)
|
|
|
|
return app
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the server.
|
2020-08-11 01:53:53 +00:00
|
|
|
func (a *App) Run() error {
|
|
|
|
return a.cmd.Execute()
|
2020-08-07 21:49:04 +00:00
|
|
|
}
|
2020-07-08 17:06:44 +00:00
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Create the server command and save it into the App.
|
2020-08-11 01:53:53 +00:00
|
|
|
func (a *App) addServerCommand(ctx context.Context, args []string, stdout, stderr io.Writer) {
|
2020-07-08 17:06:44 +00:00
|
|
|
cmd := &cobra.Command{
|
2020-08-20 17:54:15 +00:00
|
|
|
Use: `pinniped-server`,
|
|
|
|
Long: "pinniped-server provides a generic API for mapping an external\n" +
|
2020-08-07 21:49:04 +00:00
|
|
|
"credential from somewhere to an internal credential to be used for\n" +
|
|
|
|
"authenticating to the Kubernetes API.",
|
2020-08-11 01:53:53 +00:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error { return a.runServer(ctx) },
|
2020-07-08 17:06:44 +00:00
|
|
|
Args: cobra.NoArgs,
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.SetArgs(args)
|
|
|
|
cmd.SetOut(stdout)
|
|
|
|
cmd.SetErr(stderr)
|
2020-08-11 01:53:53 +00:00
|
|
|
addCommandlineFlagsToCommand(cmd, a)
|
2020-07-08 17:06:44 +00:00
|
|
|
|
2020-08-11 01:53:53 +00:00
|
|
|
a.cmd = cmd
|
2020-08-07 21:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Define the app's commandline flags.
|
|
|
|
func addCommandlineFlagsToCommand(cmd *cobra.Command, app *App) {
|
2020-07-08 17:06:44 +00:00
|
|
|
cmd.Flags().StringVarP(
|
2020-08-07 21:49:04 +00:00
|
|
|
&app.configPath,
|
2020-07-08 17:06:44 +00:00
|
|
|
"config",
|
|
|
|
"c",
|
2020-08-20 17:54:15 +00:00
|
|
|
"pinniped.yaml",
|
2020-07-08 17:06:44 +00:00
|
|
|
"path to configuration file",
|
|
|
|
)
|
|
|
|
|
2020-07-16 19:24:30 +00:00
|
|
|
cmd.Flags().StringVar(
|
2020-08-07 21:49:04 +00:00
|
|
|
&app.downwardAPIPath,
|
2020-07-16 19:24:30 +00:00
|
|
|
"downward-api-path",
|
|
|
|
"/etc/podinfo",
|
|
|
|
"path to Downward API volume mount",
|
|
|
|
)
|
2020-07-08 17:06:44 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Boot the aggregated API server, which will in turn boot the controllers.
|
2020-08-11 01:53:53 +00:00
|
|
|
func (a *App) runServer(ctx context.Context) error {
|
2020-08-07 21:49:04 +00:00
|
|
|
// Read the server config file.
|
2020-08-11 01:53:53 +00:00
|
|
|
cfg, err := config.FromPath(a.configPath)
|
2020-07-14 15:50:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not load config: %w", err)
|
|
|
|
}
|
|
|
|
|
2020-07-27 12:55:33 +00:00
|
|
|
// Load the Kubernetes cluster signing CA.
|
2020-08-19 18:21:07 +00:00
|
|
|
k8sClusterCA, shutdownCA, err := getClusterCASigner()
|
2020-07-27 12:55:33 +00:00
|
|
|
if err != nil {
|
2020-08-19 18:21:07 +00:00
|
|
|
return err
|
2020-07-27 12:55:33 +00:00
|
|
|
}
|
2020-08-19 18:21:07 +00:00
|
|
|
defer shutdownCA()
|
2020-07-27 12:55:33 +00:00
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Create a WebhookTokenAuthenticator.
|
2020-07-23 15:05:21 +00:00
|
|
|
webhookTokenAuthenticator, err := config.NewWebhook(cfg.WebhookConfig)
|
2020-07-14 15:50:14 +00:00
|
|
|
if err != nil {
|
2020-07-27 12:55:33 +00:00
|
|
|
return fmt.Errorf("could not create webhook client: %w", err)
|
2020-07-14 15:50:14 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Discover in which namespace we are installed.
|
2020-08-11 01:53:53 +00:00
|
|
|
podInfo, err := downward.Load(a.downwardAPIPath)
|
2020-07-16 19:24:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not read pod metadata: %w", err)
|
|
|
|
}
|
2020-08-07 21:49:04 +00:00
|
|
|
serverInstallationNamespace := podInfo.Namespace
|
2020-07-23 15:05:21 +00:00
|
|
|
|
2020-08-09 17:04:05 +00:00
|
|
|
// This cert provider will provide certs to the API server and will
|
|
|
|
// be mutated by a controller to keep the certs up to date with what
|
|
|
|
// is stored in a k8s Secret. Therefore it also effectively acting as
|
|
|
|
// an in-memory cache of what is stored in the k8s Secret, helping to
|
|
|
|
// keep incoming requests fast.
|
2020-08-11 01:53:53 +00:00
|
|
|
dynamicCertProvider := provider.NewDynamicTLSServingCertProvider()
|
2020-07-13 19:30:16 +00:00
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Prepare to start the controllers, but defer actually starting them until the
|
|
|
|
// post start hook of the aggregated API server.
|
2020-08-09 17:04:05 +00:00
|
|
|
startControllersFunc, err := controllermanager.PrepareControllers(
|
2020-08-03 14:17:11 +00:00
|
|
|
serverInstallationNamespace,
|
2020-08-20 17:54:15 +00:00
|
|
|
cfg.DiscoveryInfo.URL,
|
2020-08-09 17:04:05 +00:00
|
|
|
dynamicCertProvider,
|
2020-08-20 19:17:18 +00:00
|
|
|
time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds)*time.Second,
|
|
|
|
time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds)*time.Second,
|
2020-08-03 14:17:11 +00:00
|
|
|
)
|
2020-08-07 21:49:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not prepare controllers: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the aggregated API server config.
|
|
|
|
aggregatedAPIServerConfig, err := getAggregatedAPIServerConfig(
|
2020-08-09 17:04:05 +00:00
|
|
|
dynamicCertProvider,
|
2020-07-31 16:08:07 +00:00
|
|
|
webhookTokenAuthenticator,
|
2020-08-07 21:49:04 +00:00
|
|
|
k8sClusterCA,
|
|
|
|
startControllersFunc,
|
2020-07-31 16:08:07 +00:00
|
|
|
)
|
2020-07-23 15:05:21 +00:00
|
|
|
if err != nil {
|
2020-08-07 21:49:04 +00:00
|
|
|
return fmt.Errorf("could not configure aggregated API server: %w", err)
|
2020-07-13 19:30:16 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Complete the aggregated API server config and make a server instance.
|
|
|
|
server, err := aggregatedAPIServerConfig.Complete().New()
|
2020-07-23 15:05:21 +00:00
|
|
|
if err != nil {
|
2020-08-07 21:49:04 +00:00
|
|
|
return fmt.Errorf("could not create aggregated API server: %w", err)
|
2020-07-23 15:05:21 +00:00
|
|
|
}
|
2020-07-14 15:50:14 +00:00
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Run the server. Its post-start hook will start the controllers.
|
2020-07-23 15:05:21 +00:00
|
|
|
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
2020-07-13 19:30:16 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 18:21:07 +00:00
|
|
|
func getClusterCASigner() (*kubecertauthority.CA, kubecertauthority.ShutdownFunc, error) {
|
|
|
|
// Load the Kubernetes client configuration.
|
|
|
|
kubeConfig, err := restclient.InClusterConfig()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to the core Kubernetes API.
|
|
|
|
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a clock tick that triggers a periodic refresh.
|
|
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
|
|
|
|
|
|
// Make a CA which uses the Kubernetes cluster API server's signing certs.
|
|
|
|
k8sClusterCA, shutdownCA, err := kubecertauthority.New(
|
|
|
|
kubeClient,
|
|
|
|
kubecertauthority.NewPodCommandExecutor(kubeConfig, kubeClient),
|
|
|
|
ticker.C,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ticker.Stop()
|
|
|
|
return nil, nil, fmt.Errorf("could not load cluster signing CA: %w", err)
|
|
|
|
}
|
|
|
|
return k8sClusterCA, func() { shutdownCA(); ticker.Stop() }, nil
|
|
|
|
}
|
|
|
|
|
2020-08-07 21:49:04 +00:00
|
|
|
// Create a configuration for the aggregated API server.
|
|
|
|
func getAggregatedAPIServerConfig(
|
2020-08-11 01:53:53 +00:00
|
|
|
dynamicCertProvider provider.DynamicTLSServingCertProvider,
|
2020-07-31 16:08:07 +00:00
|
|
|
webhookTokenAuthenticator *webhook.WebhookTokenAuthenticator,
|
2020-08-19 18:21:07 +00:00
|
|
|
ca credentialrequest.CertIssuer,
|
2020-07-31 16:08:07 +00:00
|
|
|
startControllersPostStartHook func(context.Context),
|
|
|
|
) (*apiserver.Config, error) {
|
2020-08-07 21:49:04 +00:00
|
|
|
recommendedOptions := genericoptions.NewRecommendedOptions(
|
|
|
|
defaultEtcdPathPrefix,
|
2020-08-20 17:54:15 +00:00
|
|
|
apiserver.Codecs.LegacyCodec(pinnipedv1alpha1.SchemeGroupVersion),
|
2020-08-07 21:49:04 +00:00
|
|
|
// TODO we should check to see if all the other default settings are acceptable for us
|
|
|
|
)
|
|
|
|
recommendedOptions.Etcd = nil // turn off etcd storage because we don't need it yet
|
2020-08-09 17:04:05 +00:00
|
|
|
recommendedOptions.SecureServing.ServerCert.GeneratedCert = dynamicCertProvider
|
2020-07-13 19:30:16 +00:00
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
serverConfig := genericapiserver.NewRecommendedConfig(apiserver.Codecs)
|
2020-08-09 17:04:05 +00:00
|
|
|
// Note that among other things, this ApplyTo() function copies
|
|
|
|
// `recommendedOptions.SecureServing.ServerCert.GeneratedCert` into
|
|
|
|
// `serverConfig.SecureServing.Cert` thus making `dynamicCertProvider`
|
|
|
|
// the cert provider for the running server. The provider will be called
|
|
|
|
// by the API machinery periodically. When the provider returns nil certs,
|
|
|
|
// the API server will return "the server is currently unable to
|
|
|
|
// handle the request" error responses for all incoming requests.
|
|
|
|
// If the provider later starts returning certs, then the API server
|
|
|
|
// will use them to handle the incoming requests successfully.
|
2020-08-07 21:49:04 +00:00
|
|
|
if err := recommendedOptions.ApplyTo(serverConfig); err != nil {
|
2020-07-23 15:05:21 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-13 19:30:16 +00:00
|
|
|
|
2020-07-23 15:05:21 +00:00
|
|
|
apiServerConfig := &apiserver.Config{
|
|
|
|
GenericConfig: serverConfig,
|
|
|
|
ExtraConfig: apiserver.ExtraConfig{
|
2020-07-31 16:08:07 +00:00
|
|
|
Webhook: webhookTokenAuthenticator,
|
|
|
|
Issuer: ca,
|
|
|
|
StartControllersPostStartHook: startControllersPostStartHook,
|
2020-07-23 15:05:21 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
return apiServerConfig, nil
|
2020-07-13 19:30:16 +00:00
|
|
|
}
|