This uses a rest.Config to bootstrap TLS for the http server, webhook auth, and the client. This can be expanded later to do other kinds of TLS bootstrapping. For now this seems to get the job done in terms of what VK expects for permissions on the cluster. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
151 lines
4.5 KiB
Go
151 lines
4.5 KiB
Go
package nodeutil
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
|
"github.com/virtual-kubelet/virtual-kubelet/node/api"
|
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
)
|
|
|
|
// WithNodeConfig returns a NodeOpt which replaces the NodeConfig with the passed in value.
|
|
func WithNodeConfig(c NodeConfig) NodeOpt {
|
|
return func(orig *NodeConfig) error {
|
|
*orig = c
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithClient return a NodeOpt that sets the client that will be used to create/manage the node.
|
|
func WithClient(c kubernetes.Interface) NodeOpt {
|
|
return func(cfg *NodeConfig) error {
|
|
cfg.Client = c
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// BootstrapConfig is the configuration for bootstrapping a node.
|
|
type BootstrapConfig struct {
|
|
// ClientCAConfigMapSpec is the config map information for getting the CA for authenticating clients
|
|
// If not set, the CA in the rest config will be used.
|
|
ClientCAConfigMapSpec *ConfigMapCASpec
|
|
// A set of options to pass when creating the webhook auth.
|
|
WebhookAuthOpts []WebhookAuthOption
|
|
// RestConfig is the rest config to use for the client
|
|
// If it is not provided a default one from the environment will be created.
|
|
RestConfig *rest.Config
|
|
}
|
|
|
|
// ConfigMapCASpec is the spec for a config map that contains a CA.
|
|
type ConfigMapCASpec struct {
|
|
Namespace string
|
|
Name string
|
|
Key string
|
|
}
|
|
|
|
// BootstrapOpt is a functional option used to configure node bootstrapping
|
|
type BootstrapOpt func(*BootstrapConfig) error
|
|
|
|
// WithBootstrapFromRestConfig takes a reset config (or a default one from the environment) and returns a NodeOpt that will:
|
|
// 1. Create a client from the rest config (if no client is set)
|
|
// 2. Create webhook authn/authz from the rest config
|
|
// 3. Configure the TLS config for the HTTP server from the certs in the rest config.
|
|
func WithBootstrapFromRestConfig(opts ...BootstrapOpt) NodeOpt {
|
|
return func(cfg *NodeConfig) error {
|
|
var bOpts BootstrapConfig
|
|
if rCfg, err := RestConfigFromEnv(cfg.KubeconfigPath); err == nil {
|
|
bOpts.RestConfig = rCfg
|
|
}
|
|
|
|
for _, o := range opts {
|
|
if err := o(&bOpts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if bOpts.RestConfig == nil {
|
|
return fmt.Errorf("no rest config provided")
|
|
}
|
|
|
|
if cfg.Client == nil {
|
|
client, err := kubernetes.NewForConfig(bOpts.RestConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg.Client = client
|
|
}
|
|
|
|
if err := WithTLSConfig(tlsFromRestConfig(bOpts.RestConfig))(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := configureWebhookCA(cfg, &bOpts); err != nil {
|
|
return fmt.Errorf("error configure webhook auth: %w", err)
|
|
}
|
|
|
|
var mux *http.ServeMux
|
|
if cfg.Handler == nil {
|
|
mux = http.NewServeMux()
|
|
cfg.Handler = mux
|
|
}
|
|
if cfg.routeAttacher == nil && mux != nil {
|
|
if err := AttachProviderRoutes(mux)(cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
auth, err := WebhookAuth(cfg.Client, cfg.NodeSpec.Name, bOpts.WebhookAuthOpts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg.Handler = api.InstrumentHandler(WithAuth(auth, cfg.Handler))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func configureWebhookCA(cfg *NodeConfig, bCfg *BootstrapConfig) error {
|
|
if bCfg.ClientCAConfigMapSpec != nil {
|
|
bCfg.WebhookAuthOpts = append(bCfg.WebhookAuthOpts, func(auth *WebhookAuthConfig) error {
|
|
cmCA, err := dynamiccertificates.NewDynamicCAFromConfigMapController("client-ca", bCfg.ClientCAConfigMapSpec.Namespace, bCfg.ClientCAConfigMapSpec.Name, bCfg.ClientCAConfigMapSpec.Key, cfg.Client)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading dynamic CA from config map: %w", err)
|
|
}
|
|
auth.AuthnConfig.ClientCertificateCAContentProvider = cmCA
|
|
cfg.caController = cmCA
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
if bCfg.RestConfig.CAFile != "" {
|
|
bCfg.WebhookAuthOpts = append(bCfg.WebhookAuthOpts, func(auth *WebhookAuthConfig) error {
|
|
caFile, err := dynamiccertificates.NewDynamicCAContentFromFile("ca-file", bCfg.RestConfig.CAFile)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading dynamic CA file from rest config: %w", err)
|
|
}
|
|
auth.AuthnConfig.ClientCertificateCAContentProvider = caFile
|
|
cfg.caController = caFile
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
if bCfg.RestConfig.CAData != nil {
|
|
bCfg.WebhookAuthOpts = append(bCfg.WebhookAuthOpts, func(auth *WebhookAuthConfig) error {
|
|
caData, err := dynamiccertificates.NewStaticCAContent("ca-data", bCfg.RestConfig.CAData)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading static ca from rest config: %w", err)
|
|
}
|
|
auth.AuthnConfig.ClientCertificateCAContentProvider = caData
|
|
return nil
|
|
})
|
|
return nil
|
|
}
|
|
|
|
return errdefs.InvalidInput("no client CA found")
|
|
}
|