constellation/joinservice/internal/kubeadm/kubeadm.go
miampf f16ccf5679
rewrote packages
keyservice
joinservice
upgrade-agent
measurement-reader
debugd
disk-mapper

rewrote joinservice main

rewrote some unit tests

rewrote upgrade-agent + some grpc functions

rewrote measurement-reader

rewrote debugd

removed unused import

removed forgotten zap reference in measurements reader

rewrote disk-mapper + tests

rewrote packages

verify
disk-mapper
malicious join
bootstrapper
attestationconfigapi
versionapi
internal/cloud/azure
disk-mapper tests
image/upload/internal/cmd

rewrote verify (WIP with loglevel increase)

rewrote forgotten zap references in disk-mapper

rewrote malicious join

rewrote bootstrapper

rewrote parts of internal/

rewrote attestationconfigapi (WIP)

rewrote versionapi cli

rewrote internal/cloud/azure

rewrote disk-mapper tests (untested by me rn)

rewrote image/upload/internal/cmd

removed forgotten zap references in verify/cmd

rewrote packages

hack/oci-pin
hack/qemu-metadata-api
debugd/internal/debugd/deploy
hack/bazel-deps-mirror
cli/internal/cmd
cli-k8s-compatibility

rewrote hack/qemu-metadata-api/server

rewrote debugd/internal/debugd/deploy

rewrote hack/bazel-deps-mirror

rewrote rest of hack/qemu-metadata-api

rewrote forgotten zap references in joinservice server

rewrote cli/internal/cmd

rewrote cli-k8s-compatibility

rewrote packages

internal/staticupload
e2d/internal/upgrade
internal/constellation/helm
internal/attestation/aws/snp
internal/attestation/azure/trustedlaunch
joinservice/internal/certcache/amkds

some missed unit tests

rewrote e2e/internal/upgrade

rewrote internal/constellation/helm

internal/attestation/aws/snp

internal/attestation/azure/trustedlaunch

joinservice/internal/certcache/amkds

search and replace test logging over all left *_test.go
2024-02-08 13:14:14 +01:00

145 lines
4.8 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package kubeadm handles joining of new nodes by creating Kubernetes Join Tokens.
package kubeadm
import (
"errors"
"fmt"
"log/slog"
"path/filepath"
"time"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
certutil "k8s.io/client-go/util/cert"
tokenapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
bootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
"k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
)
// Kubeadm manages joining of new nodes.
type Kubeadm struct {
apiServerEndpoint string
log *slog.Logger
client clientset.Interface
file file.Handler
}
// New creates a new Kubeadm instance.
func New(apiServerEndpoint string, log *slog.Logger) (*Kubeadm, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("failed to get in-cluster config: %w", err)
}
client, err := clientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
}
file := file.NewHandler(afero.NewOsFs())
return &Kubeadm{
apiServerEndpoint: apiServerEndpoint,
log: log,
client: client,
file: file,
}, nil
}
// GetJoinToken creates a new bootstrap (join) token, which a node can use to join the cluster.
func (k *Kubeadm) GetJoinToken(ttl time.Duration) (*kubeadm.BootstrapTokenDiscovery, error) {
k.log.Info("Generating new random bootstrap token")
rawToken, err := bootstraputil.GenerateBootstrapToken()
if err != nil {
return nil, fmt.Errorf("couldn't generate random token: %w", err)
}
tokenStr, err := bootstraptoken.NewBootstrapTokenString(rawToken)
if err != nil {
return nil, fmt.Errorf("invalid token: %w", err)
}
token := bootstraptoken.BootstrapToken{
Token: tokenStr,
Description: "Bootstrap token generated by Constellation's Join service",
TTL: &metav1.Duration{Duration: ttl},
Usages: tokenapi.KnownTokenUsages,
Groups: []string{kubeconstants.NodeBootstrapTokenAuthGroup},
}
// create the token in Kubernetes
k.log.Info("Creating bootstrap token in Kubernetes")
if err := tokenphase.CreateNewTokens(k.client, []bootstraptoken.BootstrapToken{token}); err != nil {
return nil, fmt.Errorf("creating bootstrap token: %w", err)
}
// parse Kubernetes CA certs
k.log.Info("Preparing join token for new node")
rawConfig, err := k.file.Read(constants.ControlPlaneAdminConfFilename)
if err != nil {
return nil, fmt.Errorf("loading kubeconfig file: %w", err)
}
config, err := clientcmd.Load(rawConfig)
if err != nil {
return nil, fmt.Errorf("loading kubeconfig file: %w", err)
}
_, clusterConfig := kubeconfig.GetClusterFromKubeConfig(config)
if clusterConfig == nil {
return nil, errors.New("couldn't get cluster config from kubeconfig file")
}
caCerts, err := certutil.ParseCertsPEM(clusterConfig.CertificateAuthorityData)
if err != nil {
return nil, fmt.Errorf("parsing CA certs: %w", err)
}
publicKeyPins := make([]string, 0, len(caCerts))
for _, caCert := range caCerts {
publicKeyPins = append(publicKeyPins, pubkeypin.Hash(caCert))
}
k.log.Info("Join token creation successful")
return &kubeadm.BootstrapTokenDiscovery{
Token: tokenStr.String(),
APIServerEndpoint: k.apiServerEndpoint,
CACertHashes: publicKeyPins,
}, nil
}
// GetControlPlaneCertificatesAndKeys loads the Kubernetes CA certificates and keys.
func (k *Kubeadm) GetControlPlaneCertificatesAndKeys() (map[string][]byte, error) {
k.log.Info("Loading control plane certificates and keys")
controlPlaneFiles := make(map[string][]byte)
filenames := []string{
kubeconstants.CAKeyName,
kubeconstants.ServiceAccountPrivateKeyName,
kubeconstants.FrontProxyCAKeyName,
kubeconstants.EtcdCAKeyName,
kubeconstants.CACertName,
kubeconstants.ServiceAccountPublicKeyName,
kubeconstants.FrontProxyCACertName,
kubeconstants.EtcdCACertName,
}
for _, filename := range filenames {
key, err := k.file.Read(filepath.Join(kubeconstants.KubernetesDir, kubeconstants.DefaultCertificateDir, filename))
if err != nil {
return nil, err
}
controlPlaneFiles[filename] = key
}
return controlPlaneFiles, nil
}