Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions controllers/clustercache/cluster_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ type ClusterCache interface {
// cert to communicate with etcd.
// This private key is stored and cached in the ClusterCache because it's expensive to generate a new
// private key in every single Reconcile.
//
// Deprecated: This method is deprecated and will be removed in a future release as caching a rsa.PrivateKey
// is outside the scope of the ClusterCache.
GetClientCertificatePrivateKey(ctx context.Context, cluster client.ObjectKey) (*rsa.PrivateKey, error)

// Watch watches a workload cluster for events.
Expand Down
33 changes: 25 additions & 8 deletions controlplane/kubeadm/internal/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/controllers/clustercache"
"sigs.k8s.io/cluster-api/util/cache"
"sigs.k8s.io/cluster-api/util/collections"
"sigs.k8s.io/cluster-api/util/secret"
)
Expand All @@ -53,6 +54,18 @@ type Management struct {
EtcdDialTimeout time.Duration
EtcdCallTimeout time.Duration
EtcdLogger *zap.Logger
ClientCertCache cache.Cache[ClientCertEntry]
}

// ClientCertEntry is an Entry for the Cache that stores the client cert.
type ClientCertEntry struct {
Cluster client.ObjectKey
ClientCert *tls.Certificate
}

// Key returns the cache key of a ClientCertEntry.
func (r ClientCertEntry) Key() string {
return r.Cluster.String()
}

// RemoteClusterConnectionError represents a failure to connect to a remote cluster.
Expand Down Expand Up @@ -126,14 +139,18 @@ 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 {
clientKey, err := m.ClusterCache.GetClientCertificatePrivateKey(ctx, clusterKey)
if err != nil {
return nil, err
}

clientCert, err = generateClientCert(crtData, keyData, clientKey)
if err != nil {
return nil, err
// Get client cert from cache if possible, otherwise generate it and add it to the cache.
// TODO: When we implement ClusterConfiguration.EncryptionAlgorithm we should add it to
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be done as part of #10077 (comment)

// the ClientCertEntries and make it part of the key.
if entry, ok := m.ClientCertCache.Has(ClientCertEntry{Cluster: clusterKey}.Key()); ok {
clientCert = *entry.ClientCert
} else {
// The client cert expires after 10 years, but that's okay as the cache has a TTL of 1 day.
clientCert, err = generateClientCert(crtData, keyData)
if err != nil {
return nil, err
}
m.ClientCertCache.Add(ClientCertEntry{Cluster: clusterKey, ClientCert: &clientCert})
}
} else {
clientCert, err = m.getAPIServerEtcdClientCert(ctx, clusterKey)
Expand Down
2 changes: 2 additions & 0 deletions controlplane/kubeadm/internal/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/controllers/clustercache"
"sigs.k8s.io/cluster-api/controllers/remote"
"sigs.k8s.io/cluster-api/util/cache"
"sigs.k8s.io/cluster-api/util/certs"
"sigs.k8s.io/cluster-api/util/collections"
"sigs.k8s.io/cluster-api/util/kubeconfig"
Expand Down Expand Up @@ -239,6 +240,7 @@ func TestGetWorkloadCluster(t *testing.T) {
Client: env.GetClient(),
SecretCachingClient: secretCachingClient,
ClusterCache: clusterCache,
ClientCertCache: cache.New[ClientCertEntry](24 * time.Hour),
}

// Ensure the ClusterCache reconciled at least once (and if possible created a clusterAccessor).
Expand Down
2 changes: 2 additions & 0 deletions controlplane/kubeadm/internal/controllers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"sigs.k8s.io/cluster-api/internal/contract"
"sigs.k8s.io/cluster-api/internal/util/ssa"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/cache"
"sigs.k8s.io/cluster-api/util/collections"
"sigs.k8s.io/cluster-api/util/conditions"
v1beta1conditions "sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1"
Expand Down Expand Up @@ -149,6 +150,7 @@ func (r *KubeadmControlPlaneReconciler) SetupWithManager(ctx context.Context, mg
EtcdDialTimeout: r.EtcdDialTimeout,
EtcdCallTimeout: r.EtcdCallTimeout,
EtcdLogger: r.EtcdLogger,
ClientCertCache: cache.New[internal.ClientCertEntry](24 * time.Hour),
}
}

Expand Down
6 changes: 5 additions & 1 deletion controlplane/kubeadm/internal/workload_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func calculateAPIServerPort(config *bootstrapv1.KubeadmConfig) int32 {
return 6443
}

func generateClientCert(caCertEncoded, caKeyEncoded []byte, clientKey *rsa.PrivateKey) (tls.Certificate, error) {
func generateClientCert(caCertEncoded, caKeyEncoded []byte) (tls.Certificate, error) {
caCert, err := certs.DecodeCertPEM(caCertEncoded)
if err != nil {
return tls.Certificate{}, err
Expand All @@ -356,6 +356,10 @@ func generateClientCert(caCertEncoded, caKeyEncoded []byte, clientKey *rsa.Priva
if err != nil {
return tls.Certificate{}, err
}
clientKey, err := certs.NewPrivateKey()
if err != nil {
return tls.Certificate{}, err
}
x509Cert, err := newClientCert(caCert, clientKey, caKey)
if err != nil {
return tls.Certificate{}, err
Expand Down