Skip to content

Commit 3cd93fb

Browse files
Add etcd certificate generation
Signed-off-by: Danil Grigorev <[email protected]>
1 parent 6fc88d9 commit 3cd93fb

File tree

6 files changed

+117
-10
lines changed

6 files changed

+117
-10
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ GOLANGCI_LINT_VER := v1.55.1
9999
GOLANGCI_LINT_BIN := golangci-lint
100100
GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN))
101101

102-
GINKGO_VER := v2.14.0
102+
GINKGO_VER := v2.16.0
103103
GINKGO_BIN := ginkgo
104104
GINKGO := $(abspath $(TOOLS_BIN_DIR)/$(GINKGO_BIN)-$(GINKGO_VER))
105105
GINKGO_PKG := github.com/onsi/ginkgo/v2/ginkgo

controlplane/internal/controllers/rke2controlplane_controller.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"sigs.k8s.io/controller-runtime/pkg/source"
4141

4242
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
43+
"sigs.k8s.io/cluster-api/controllers/remote"
4344
"sigs.k8s.io/cluster-api/util"
4445
"sigs.k8s.io/cluster-api/util/annotations"
4546
"sigs.k8s.io/cluster-api/util/collections"
@@ -236,10 +237,26 @@ func (r *RKE2ControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr c
236237
r.controller = c
237238
r.recorder = mgr.GetEventRecorderFor("rke2-control-plane-controller")
238239

240+
// Set up a ClusterCacheTracker and ClusterCacheReconciler to provide to controllers
241+
// requiring a connection to a remote cluster
242+
tracker, err := remote.NewClusterCacheTracker(
243+
mgr,
244+
remote.ClusterCacheTrackerOptions{
245+
SecretCachingClient: r.SecretCachingClient,
246+
ControllerName: "rke2-control-plane-controller",
247+
Log: &ctrl.Log,
248+
Indexes: []remote.Index{},
249+
},
250+
)
251+
if err != nil {
252+
return errors.Wrap(err, "unable to create cluster cache tracker")
253+
}
254+
239255
if r.managementCluster == nil {
240256
r.managementCluster = &rke2.Management{
241257
Client: r.Client,
242258
SecretCachingClient: r.SecretCachingClient,
259+
Tracker: tracker,
243260
}
244261
}
245262

pkg/rke2/management_cluster.go

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ package rke2
1818

1919
import (
2020
"context"
21+
"crypto"
22+
"crypto/rand"
23+
"crypto/rsa"
24+
"crypto/tls"
25+
"crypto/x509"
26+
"crypto/x509/pkix"
27+
"math/big"
2128
"time"
2229

2330
"github.com/pkg/errors"
@@ -123,18 +130,18 @@ func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey ctrlclie
123130
}
124131

125132
func (m *Management) getEtcdCAKeyPair(ctx context.Context, clusterKey ctrlclient.ObjectKey) (*certs.KeyPair, error) {
126-
etcd := &secret.ManagedCertificate{
127-
Purpose: secret.EtcdCA,
128-
}
133+
certificates := secret.Certificates([]secret.Certificate{&secret.ManagedCertificate{
134+
Purpose: secret.EtcdServerCA,
135+
}})
129136

130137
// Try to get the certificate via the cached ctrlclient.
131-
s, err := etcd.Lookup(ctx, m.SecretCachingClient, clusterKey)
132-
if err != nil || s == nil {
138+
err := certificates.Lookup(ctx, m.SecretCachingClient, clusterKey)
139+
if err != nil {
133140
// Return error if we got an errors which is not a NotFound error.
134141
return nil, errors.Wrapf(err, "failed to get secret; etcd CA bundle %s/%s", clusterKey.Namespace, secret.Name(clusterKey.Name, secret.EtcdCA))
135142
}
136143

137-
return etcd.KeyPair, nil
144+
return certificates[0].GetKeyPair(), nil
138145
}
139146

140147
func (m *Management) getRemoteKeyPair(ctx context.Context, remoteClient ctrlclient.Client, clusterKey ctrlclient.ObjectKey) (*certs.KeyPair, error) {
@@ -152,3 +159,51 @@ func (m *Management) getRemoteKeyPair(ctx context.Context, remoteClient ctrlclie
152159

153160
return etcdCertificate.KeyPair, nil
154161
}
162+
163+
func generateClientCert(caCertEncoded, caKeyEncoded []byte, clientKey *rsa.PrivateKey) (tls.Certificate, error) {
164+
caCert, err := certs.DecodeCertPEM(caCertEncoded)
165+
if err != nil {
166+
return tls.Certificate{}, err
167+
}
168+
169+
caKey, err := certs.DecodePrivateKeyPEM(caKeyEncoded)
170+
if err != nil {
171+
return tls.Certificate{}, err
172+
}
173+
174+
x509Cert, err := newClientCert(caCert, clientKey, caKey)
175+
if err != nil {
176+
return tls.Certificate{}, err
177+
}
178+
179+
return tls.X509KeyPair(certs.EncodeCertPEM(x509Cert), certs.EncodePrivateKeyPEM(clientKey))
180+
}
181+
182+
func newClientCert(caCert *x509.Certificate, key *rsa.PrivateKey, caKey crypto.Signer) (*x509.Certificate, error) {
183+
cfg := certs.Config{
184+
CommonName: "cluster-api.x-k8s.io",
185+
}
186+
187+
now := time.Now().UTC()
188+
189+
tmpl := x509.Certificate{
190+
SerialNumber: new(big.Int).SetInt64(0),
191+
Subject: pkix.Name{
192+
CommonName: cfg.CommonName,
193+
Organization: cfg.Organization,
194+
},
195+
NotBefore: now.Add(time.Minute * -5),
196+
NotAfter: now.Add(secret.TenYears), // 10 years
197+
KeyUsage: x509.KeyUsageDigitalSignature,
198+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
199+
}
200+
201+
b, err := x509.CreateCertificate(rand.Reader, &tmpl, caCert, key.Public(), caKey)
202+
if err != nil {
203+
return nil, errors.Wrapf(err, "failed to create signed client certificate: %+v", tmpl)
204+
}
205+
206+
c, err := x509.ParseCertificate(b)
207+
208+
return c, errors.WithStack(err)
209+
}

pkg/rke2/workload_cluster.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3838
"sigs.k8s.io/cluster-api/util"
3939
"sigs.k8s.io/cluster-api/util/annotations"
40+
"sigs.k8s.io/cluster-api/util/certs"
4041
"sigs.k8s.io/cluster-api/util/collections"
4142
"sigs.k8s.io/cluster-api/util/conditions"
4243
"sigs.k8s.io/cluster-api/util/patch"
@@ -122,6 +123,18 @@ func (m *Management) NewWorkload(
122123
return nil, err
123124
}
124125

126+
if _, err := certs.DecodePrivateKeyPEM(keyPair.Key); err == nil {
127+
clientKey, err := m.Tracker.GetEtcdClientCertificateKey(ctx, clusterKey)
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
clientCert, err = generateClientCert(keyPair.Cert, keyPair.Key, clientKey)
133+
if err != nil {
134+
return nil, err
135+
}
136+
}
137+
125138
caPool := x509.NewCertPool()
126139
caPool.AppendCertsFromPEM(keyPair.Cert)
127140
tlsConfig := &tls.Config{

pkg/secret/certificates.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,21 @@ const (
4646
// be automatically used by RKE2 to use the pre-defined certificates instead of generating them.
4747
DefaultCertificatesDir = "/var/lib/rancher/rke2/server/tls"
4848

49+
// DefaultETCDCertificatesDir is the default location (file path) where the provider will put the etcd certificates, this location will then
50+
// be automatically used by RKE2 to use the pre-defined certificates instead of generating them.
51+
DefaultETCDCertificatesDir = DefaultCertificatesDir + "/etcd"
52+
4953
// Kubeconfig is the secret name suffix storing the Cluster Kubeconfig.
5054
Kubeconfig = Purpose("kubeconfig")
5155

5256
// KubeconfigDataName is the data entry name for the Kubeconfig file content.
5357
KubeconfigDataName string = "value"
5458

5559
// EtcdCA is the secret name suffix for the Etcd CA.
56-
EtcdCA Purpose = "etcd"
60+
EtcdCA Purpose = "peer-etcd"
61+
62+
// EtcdServerCA is the secret name suffix for the Etcd CA.
63+
EtcdServerCA Purpose = "etcd"
5764

5865
// ClusterCA is the secret name suffix for APIServer CA.
5966
ClusterCA = Purpose("ca")
@@ -165,6 +172,16 @@ func NewCertificatesForInitialControlPlane() Certificates {
165172
CertFile: filepath.Join(certificatesDir, "client-ca.crt"),
166173
KeyFile: filepath.Join(certificatesDir, "client-ca.key"),
167174
},
175+
&ManagedCertificate{
176+
Purpose: EtcdCA,
177+
CertFile: filepath.Join(DefaultETCDCertificatesDir, "peer-ca.crt"),
178+
KeyFile: filepath.Join(DefaultETCDCertificatesDir, "peer-ca.key"),
179+
},
180+
&ManagedCertificate{
181+
Purpose: EtcdServerCA,
182+
CertFile: filepath.Join(DefaultETCDCertificatesDir, "server-ca.crt"),
183+
KeyFile: filepath.Join(DefaultETCDCertificatesDir, "server-ca.key"),
184+
},
168185
}
169186

170187
return certificates
@@ -419,6 +436,7 @@ func (c Certificates) AsFiles() []bootstrapv1.File {
419436
clientClusterCA := c.GetByPurpose(ClientClusterCA)
420437

421438
etcdCA := c.GetByPurpose(EtcdCA)
439+
etcdServerCA := c.GetByPurpose(EtcdServerCA)
422440

423441
certFiles := make([]bootstrapv1.File, 0)
424442
if clusterCA != nil {
@@ -433,6 +451,10 @@ func (c Certificates) AsFiles() []bootstrapv1.File {
433451
certFiles = append(certFiles, etcdCA.AsFiles()...)
434452
}
435453

454+
if etcdServerCA != nil {
455+
certFiles = append(certFiles, etcdServerCA.AsFiles()...)
456+
}
457+
436458
// these will only exist if external etcd was defined and supplied by the user
437459
apiserverEtcdClientCert := c.GetByPurpose(APIServerEtcdClient)
438460
if apiserverEtcdClientCert != nil {

test/e2e/e2e_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ var _ = Describe("Workload cluster creation", func() {
218218
Flavor: "docker",
219219
Namespace: namespace.Name,
220220
ClusterName: clusterName,
221-
KubernetesVersion: e2eConfig.GetVariable(KubernetesVersion),
222-
ControlPlaneMachineCount: pointer.Int64Ptr(3), //TODO: change this back to 1 when scaling is supported
221+
KubernetesVersion: e2eConfig.GetVariable(KubernetesVersionUpgradeTo),
222+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
223223
WorkerMachineCount: pointer.Int64Ptr(1),
224224
},
225225
WaitForClusterIntervals: e2eConfig.GetIntervals(specName, "wait-cluster"),

0 commit comments

Comments
 (0)