2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-05-23 05:36:54 -04:00
|
|
|
package kubeadm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-07-11 07:29:22 -04:00
|
|
|
"path/filepath"
|
2022-05-23 05:36:54 -04:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/edgelesssys/constellation/internal/constants"
|
|
|
|
"github.com/edgelesssys/constellation/internal/file"
|
2022-06-28 10:51:30 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/logger"
|
2022-05-23 05:36:54 -04:00
|
|
|
"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"
|
|
|
|
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"
|
2022-07-11 07:29:22 -04:00
|
|
|
kubeconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
2022-05-23 05:36:54 -04:00
|
|
|
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 {
|
2022-07-05 08:14:11 -04:00
|
|
|
apiServerEndpoint string
|
|
|
|
log *logger.Logger
|
|
|
|
client clientset.Interface
|
|
|
|
file file.Handler
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Kubeadm instance.
|
2022-07-05 08:14:11 -04:00
|
|
|
func New(apiServerEndpoint string, log *logger.Logger) (*Kubeadm, error) {
|
2022-05-23 05:36:54 -04:00
|
|
|
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{
|
2022-07-05 08:14:11 -04:00
|
|
|
apiServerEndpoint: apiServerEndpoint,
|
|
|
|
log: log,
|
|
|
|
client: client,
|
|
|
|
file: file,
|
2022-05-23 05:36:54 -04:00
|
|
|
}, 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) {
|
2022-06-28 10:51:30 -04:00
|
|
|
k.log.Infof("Generating new random bootstrap token")
|
2022-05-23 05:36:54 -04:00
|
|
|
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,
|
2022-07-05 08:13:19 -04:00
|
|
|
Description: "Bootstrap token generated by Constellation's Join service",
|
2022-05-23 05:36:54 -04:00
|
|
|
TTL: &metav1.Duration{Duration: ttl},
|
2022-07-14 09:45:04 -04:00
|
|
|
Usages: kubeconstants.DefaultTokenUsages,
|
|
|
|
Groups: kubeconstants.DefaultTokenGroups,
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// create the token in Kubernetes
|
2022-06-28 10:51:30 -04:00
|
|
|
k.log.Infof("Creating bootstrap token in Kubernetes")
|
2022-05-23 05:36:54 -04:00
|
|
|
if err := tokenphase.CreateNewTokens(k.client, []bootstraptoken.BootstrapToken{token}); err != nil {
|
|
|
|
return nil, fmt.Errorf("creating bootstrap token: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse Kubernetes CA certs
|
2022-06-28 10:51:30 -04:00
|
|
|
k.log.Infof("Preparing join token for new node")
|
2022-05-23 05:36:54 -04:00
|
|
|
rawConfig, err := k.file.Read(constants.CoreOSAdminConfFilename)
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
k.log.Infof("Join token creation successful")
|
2022-05-23 05:36:54 -04:00
|
|
|
return &kubeadm.BootstrapTokenDiscovery{
|
|
|
|
Token: tokenStr.String(),
|
2022-07-05 08:14:11 -04:00
|
|
|
APIServerEndpoint: k.apiServerEndpoint,
|
2022-05-23 05:36:54 -04:00
|
|
|
CACertHashes: publicKeyPins,
|
|
|
|
}, nil
|
|
|
|
}
|
2022-06-21 05:10:32 -04:00
|
|
|
|
2022-07-11 07:29:22 -04:00
|
|
|
// GetControlPlaneCertificatesAndKeys loads the Kubernetes CA certificates and keys.
|
|
|
|
func (k *Kubeadm) GetControlPlaneCertificatesAndKeys() (map[string][]byte, error) {
|
|
|
|
k.log.Infof("Loading control plane certificates and keys")
|
|
|
|
controlPlaneFiles := make(map[string][]byte)
|
|
|
|
|
2022-07-14 09:45:04 -04:00
|
|
|
filenames := []string{
|
2022-07-11 07:29:22 -04:00
|
|
|
kubeconstants.CAKeyName,
|
|
|
|
kubeconstants.ServiceAccountPrivateKeyName,
|
|
|
|
kubeconstants.FrontProxyCAKeyName,
|
|
|
|
kubeconstants.EtcdCAKeyName,
|
|
|
|
kubeconstants.CACertName,
|
|
|
|
kubeconstants.ServiceAccountPublicKeyName,
|
|
|
|
kubeconstants.FrontProxyCACertName,
|
|
|
|
kubeconstants.EtcdCACertName,
|
|
|
|
}
|
|
|
|
|
2022-07-14 09:45:04 -04:00
|
|
|
for _, filename := range filenames {
|
|
|
|
key, err := k.file.Read(filepath.Join(kubeconstants.KubernetesDir, kubeconstants.DefaultCertificateDir, filename))
|
2022-07-11 07:29:22 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-14 09:45:04 -04:00
|
|
|
controlPlaneFiles[filename] = key
|
2022-06-21 05:10:32 -04:00
|
|
|
}
|
2022-07-11 07:29:22 -04:00
|
|
|
|
|
|
|
return controlPlaneFiles, nil
|
2022-06-21 05:10:32 -04:00
|
|
|
}
|