diff --git a/controllers/remote/cluster_cache_tracker.go b/controllers/remote/cluster_cache_tracker.go index 1931dabeb424..09cc104682d0 100644 --- a/controllers/remote/cluster_cache_tracker.go +++ b/controllers/remote/cluster_cache_tracker.go @@ -18,6 +18,7 @@ package remote import ( "context" + "crypto/rsa" "fmt" "os" "sync" @@ -48,6 +49,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/feature" + "sigs.k8s.io/cluster-api/util/certs" "sigs.k8s.io/cluster-api/util/conditions" ) @@ -166,12 +168,23 @@ func (t *ClusterCacheTracker) GetRESTConfig(ctc context.Context, cluster client. return accessor.config, nil } +// GetEtcdClientCertificateKey returns a cached certificate key to be used for generating certificates for accessing etcd in the given cluster. +func (t *ClusterCacheTracker) GetEtcdClientCertificateKey(ctx context.Context, cluster client.ObjectKey) (*rsa.PrivateKey, error) { + accessor, err := t.getClusterAccessor(ctx, cluster, t.indexes...) + if err != nil { + return nil, err + } + + return accessor.etcdClientCertificateKey, nil +} + // clusterAccessor represents the combination of a delegating client, cache, and watches for a remote cluster. type clusterAccessor struct { - cache *stoppableCache - client client.Client - watches sets.Set[string] - config *rest.Config + cache *stoppableCache + client client.Client + watches sets.Set[string] + config *rest.Config + etcdClientCertificateKey *rsa.PrivateKey } // clusterAccessorExists returns true if a clusterAccessor exists for cluster. @@ -335,11 +348,20 @@ func (t *ClusterCacheTracker) newClusterAccessor(ctx context.Context, cluster cl return nil, err } + // Generating a new private key to be used for generating temporary certificates for used for connecting to + // etcd on the target cluster. + // NOTE: Generating a private key is an expensive operation, so we store it in the cluster accessor. + etcdKey, err := certs.NewPrivateKey() + if err != nil { + return nil, err + } + return &clusterAccessor{ - cache: cache, - config: config, - client: delegatingClient, - watches: sets.Set[string]{}, + cache: cache, + config: config, + client: delegatingClient, + watches: sets.Set[string]{}, + etcdClientCertificateKey: etcdKey, }, nil } diff --git a/controlplane/kubeadm/internal/cluster.go b/controlplane/kubeadm/internal/cluster.go index 47c4e419b946..60b71604cff1 100644 --- a/controlplane/kubeadm/internal/cluster.go +++ b/controlplane/kubeadm/internal/cluster.go @@ -141,7 +141,12 @@ func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.O // TODO: consider if we can detect if we are using external etcd in a more explicit way (e.g. looking at the config instead of deriving from the existing certificates) var clientCert tls.Certificate if keyData != nil { - clientCert, err = generateClientCert(crtData, keyData) + clientKey, err := m.Tracker.GetEtcdClientCertificateKey(ctx, clusterKey) + if err != nil { + return nil, err + } + + clientCert, err = generateClientCert(crtData, keyData, clientKey) if err != nil { return nil, err } diff --git a/controlplane/kubeadm/internal/workload_cluster.go b/controlplane/kubeadm/internal/workload_cluster.go index 7ffacda66193..4112106f570e 100644 --- a/controlplane/kubeadm/internal/workload_cluster.go +++ b/controlplane/kubeadm/internal/workload_cluster.go @@ -489,11 +489,7 @@ func calculateAPIServerPort(config *bootstrapv1.KubeadmConfig) int32 { return 6443 } -func generateClientCert(caCertEncoded, caKeyEncoded []byte) (tls.Certificate, error) { - privKey, err := certs.NewPrivateKey() - if err != nil { - return tls.Certificate{}, err - } +func generateClientCert(caCertEncoded, caKeyEncoded []byte, clientKey *rsa.PrivateKey) (tls.Certificate, error) { caCert, err := certs.DecodeCertPEM(caCertEncoded) if err != nil { return tls.Certificate{}, err @@ -502,11 +498,11 @@ func generateClientCert(caCertEncoded, caKeyEncoded []byte) (tls.Certificate, er if err != nil { return tls.Certificate{}, err } - x509Cert, err := newClientCert(caCert, privKey, caKey) + x509Cert, err := newClientCert(caCert, clientKey, caKey) if err != nil { return tls.Certificate{}, err } - return tls.X509KeyPair(certs.EncodeCertPEM(x509Cert), certs.EncodePrivateKeyPEM(privKey)) + return tls.X509KeyPair(certs.EncodeCertPEM(x509Cert), certs.EncodePrivateKeyPEM(clientKey)) } func newClientCert(caCert *x509.Certificate, key *rsa.PrivateKey, caKey crypto.Signer) (*x509.Certificate, error) {