Skip to content
This repository was archived by the owner on Jul 30, 2021. It is now read-only.

Commit 3aceccd

Browse files
committed
Separate certificate logic for joins
With the introduction of support for external etcd comes better certificate management. This commit separates the worker join certificate logic from the control plane join certificate logic. Signed-off-by: Chuck Ha <[email protected]>
1 parent 074c001 commit 3aceccd

File tree

4 files changed

+120
-59
lines changed

4 files changed

+120
-59
lines changed

controllers/kubeadmconfig_controller.go

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func (r *KubeadmConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
227227
return ctrl.Result{}, err
228228
}
229229

230-
certificates := internalcluster.NewCertificatesForControlPlane(config.Spec.ClusterConfiguration)
230+
certificates := internalcluster.NewCertificatesForInitialControlPlane(config.Spec.ClusterConfiguration)
231231
if err := certificates.LookupOrGenerate(ctx, r.Client, cluster, config); err != nil {
232232
log.Error(err, "unable to lookup or create cluster certificates")
233233
return ctrl.Result{}, err
@@ -266,56 +266,41 @@ func (r *KubeadmConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
266266
if config.Spec.JoinConfiguration == nil {
267267
log.Info("Creating default JoinConfiguration")
268268
config.Spec.JoinConfiguration = &kubeadmv1beta1.JoinConfiguration{}
269-
if util.IsControlPlaneMachine(machine) {
270-
config.Spec.JoinConfiguration.ControlPlane = &kubeadmv1beta1.JoinControlPlane{}
271-
}
272-
}
273-
274-
certificates := internalcluster.NewCertificatesForWorker(config.Spec.JoinConfiguration.CACertPath)
275-
if err := certificates.Lookup(ctx, r.Client, cluster); err != nil {
276-
log.Error(err, "unable to lookup cluster certificates")
277-
return ctrl.Result{}, err
278-
}
279-
if err := certificates.EnsureAllExist(); err != nil {
280-
return ctrl.Result{}, err
281269
}
282270

283-
hashes, err := certificates.GetByPurpose(secret.ClusterCA).Hashes()
284-
if err != nil {
285-
log.Error(err, "Unable to generate Cluster CA certificate hashes")
286-
return ctrl.Result{}, err
287-
}
288-
// TODO: move this into reconcile.Discovery so that defaults for the Discovery are all in the same place
289-
if config.Spec.JoinConfiguration.Discovery.BootstrapToken == nil {
290-
config.Spec.JoinConfiguration.Discovery.BootstrapToken = &kubeadmv1beta1.BootstrapTokenDiscovery{}
291-
}
292-
config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes = hashes
271+
// it's a control plane join
272+
if util.IsControlPlaneMachine(machine) {
273+
if config.Spec.JoinConfiguration.ControlPlane == nil {
274+
config.Spec.JoinConfiguration.ControlPlane = &kubeadmv1beta1.JoinControlPlane{}
275+
}
293276

294-
// ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster
295-
if err := r.reconcileDiscovery(cluster, config); err != nil {
296-
if requeueErr, ok := errors.Cause(err).(capierrors.HasRequeueAfterError); ok {
297-
log.Info(err.Error())
298-
return ctrl.Result{RequeueAfter: requeueErr.GetRequeueAfter()}, nil
277+
certificates := internalcluster.NewCertificatesForJoiningControlPlane()
278+
if err := certificates.Lookup(ctx, r.Client, cluster); err != nil {
279+
log.Error(err, "unable to lookup cluster certificates")
280+
return ctrl.Result{}, err
281+
}
282+
if err := certificates.EnsureAllExist(); err != nil {
283+
return ctrl.Result{}, err
299284
}
300-
return ctrl.Result{}, err
301-
}
302285

303-
joindata, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.JoinConfiguration)
304-
if err != nil {
305-
log.Error(err, "failed to marshal join configuration")
306-
return ctrl.Result{}, err
307-
}
286+
// ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster
287+
if err := r.reconcileDiscovery(cluster, config, certificates); err != nil {
288+
if requeueErr, ok := errors.Cause(err).(capierrors.HasRequeueAfterError); ok {
289+
log.Info(err.Error())
290+
return ctrl.Result{RequeueAfter: requeueErr.GetRequeueAfter()}, nil
291+
}
292+
return ctrl.Result{}, err
293+
}
308294

309-
// it's a control plane join
310-
if util.IsControlPlaneMachine(machine) {
311-
if config.Spec.JoinConfiguration.ControlPlane == nil {
312-
return ctrl.Result{}, errors.New("Machine is a ControlPlane, but JoinConfiguration.ControlPlane is not set in the KubeadmConfig object")
295+
joinData, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.JoinConfiguration)
296+
if err != nil {
297+
log.Error(err, "failed to marshal join configuration")
298+
return ctrl.Result{}, err
313299
}
314300

315301
log.Info("Creating BootstrapData for the join control plane")
316-
317302
cloudJoinData, err := cloudinit.NewJoinControlPlane(&cloudinit.ControlPlaneJoinInput{
318-
JoinConfiguration: joindata,
303+
JoinConfiguration: joinData,
319304
Certificates: certificates,
320305
BaseUserData: cloudinit.BaseUserData{
321306
AdditionalFiles: config.Spec.Files,
@@ -335,7 +320,32 @@ func (r *KubeadmConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
335320
return ctrl.Result{}, nil
336321
}
337322

338-
// otherwise it is a node
323+
// It's a worker join
324+
certificates := internalcluster.NewCertificatesForWorker(config.Spec.JoinConfiguration.CACertPath)
325+
if err := certificates.Lookup(ctx, r.Client, cluster); err != nil {
326+
log.Error(err, "unable to lookup cluster certificates")
327+
return ctrl.Result{}, err
328+
}
329+
if err := certificates.EnsureAllExist(); err != nil {
330+
log.Error(err, "Missing certificates")
331+
return ctrl.Result{}, err
332+
}
333+
334+
// ensure that joinConfiguration.Discovery is properly set for joining node on the current cluster
335+
if err := r.reconcileDiscovery(cluster, config, certificates); err != nil {
336+
if requeueErr, ok := errors.Cause(err).(capierrors.HasRequeueAfterError); ok {
337+
log.Info(err.Error())
338+
return ctrl.Result{RequeueAfter: requeueErr.GetRequeueAfter()}, nil
339+
}
340+
return ctrl.Result{}, err
341+
}
342+
343+
joinData, err := kubeadmv1beta1.ConfigurationToYAML(config.Spec.JoinConfiguration)
344+
if err != nil {
345+
log.Error(err, "failed to marshal join configuration")
346+
return ctrl.Result{}, err
347+
}
348+
339349
if config.Spec.JoinConfiguration.ControlPlane != nil {
340350
return ctrl.Result{}, errors.New("Machine is a Worker, but JoinConfiguration.ControlPlane is set in the KubeadmConfig object")
341351
}
@@ -350,7 +360,7 @@ func (r *KubeadmConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
350360
PostKubeadmCommands: config.Spec.PostKubeadmCommands,
351361
Users: config.Spec.Users,
352362
},
353-
JoinConfiguration: joindata,
363+
JoinConfiguration: joinData,
354364
})
355365
if err != nil {
356366
log.Error(err, "failed to create a worker join configuration")
@@ -416,7 +426,7 @@ func (r *KubeadmConfigReconciler) MachineToBootstrapMapFunc(o handler.MapObject)
416426
// The implementation func respect user provided discovery configurations, but in case some of them are missing, a valid BootstrapToken object
417427
// is automatically injected into config.JoinConfiguration.Discovery.
418428
// This allows to simplify configuration UX, by providing the option to delegate to CABPK the configuration of kubeadm join discovery.
419-
func (r *KubeadmConfigReconciler) reconcileDiscovery(cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig) error {
429+
func (r *KubeadmConfigReconciler) reconcileDiscovery(cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig, certificates internalcluster.Certificates) error {
420430
log := r.Log.WithValues("kubeadmconfig", fmt.Sprintf("%s/%s", config.Namespace, config.Name))
421431

422432
// if config already contains a file discovery configuration, respect it without further validations
@@ -429,6 +439,16 @@ func (r *KubeadmConfigReconciler) reconcileDiscovery(cluster *clusterv1.Cluster,
429439
config.Spec.JoinConfiguration.Discovery.BootstrapToken = &kubeadmv1beta1.BootstrapTokenDiscovery{}
430440
}
431441

442+
// calculate the ca cert hashes if they are not already set
443+
if len(config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes) == 0 {
444+
hashes, err := certificates.GetByPurpose(secret.ClusterCA).Hashes()
445+
if err != nil {
446+
log.Error(err, "Unable to generate Cluster CA certificate hashes")
447+
return err
448+
}
449+
config.Spec.JoinConfiguration.Discovery.BootstrapToken.CACertHashes = hashes
450+
}
451+
432452
// if BootstrapToken already contains an APIServerEndpoint, respect it; otherwise inject the APIServerEndpoint endpoint defined in cluster status
433453
apiServerEndpoint := config.Spec.JoinConfiguration.Discovery.BootstrapToken.APIServerEndpoint
434454
if apiServerEndpoint == "" {

controllers/kubeadmconfig_controller_test.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ func TestKubeadmConfigReconciler_Reconcile_GenerateCloudConfigData(t *testing.T)
400400
}
401401
}
402402

403-
// If a controlplane has an invalid JoinConfiguration then user intervention is required.
403+
// If a control plane has no JoinConfiguration, then we will create a default and no error will occur
404404
func TestKubeadmConfigReconciler_Reconcile_ErrorIfJoiningControlPlaneHasInvalidConfiguration(t *testing.T) {
405405
// TODO: extract this kind of code into a setup function that puts the state of objects into an initialized controlplane (implies secrets exist)
406406
cluster := newCluster("cluster")
@@ -436,8 +436,8 @@ func TestKubeadmConfigReconciler_Reconcile_ErrorIfJoiningControlPlaneHasInvalidC
436436
},
437437
}
438438
_, err := k.Reconcile(request)
439-
if err == nil {
440-
t.Fatal("Expected error, got nil")
439+
if err != nil {
440+
t.Fatalf("Expected no error but got %v", err)
441441
}
442442
}
443443

@@ -598,6 +598,11 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
598598
}
599599

600600
dummyCAHash := []string{"...."}
601+
bootstrapToken := kubeadmv1beta1.Discovery{
602+
BootstrapToken: &kubeadmv1beta1.BootstrapTokenDiscovery{
603+
CACertHashes: dummyCAHash,
604+
},
605+
}
601606
goodcluster := &clusterv1.Cluster{
602607
Status: clusterv1.ClusterStatus{
603608
APIEndpoints: []clusterv1.APIEndpoint{
@@ -619,7 +624,9 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
619624
cluster: goodcluster,
620625
config: &bootstrapv1.KubeadmConfig{
621626
Spec: bootstrapv1.KubeadmConfigSpec{
622-
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{},
627+
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{
628+
Discovery: bootstrapToken,
629+
},
623630
},
624631
},
625632
validateDiscovery: func(c *bootstrapv1.KubeadmConfig) error {
@@ -633,8 +640,8 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
633640
if d.BootstrapToken.APIServerEndpoint != "example.com:6443" {
634641
return errors.Errorf("BootstrapToken.APIServerEndpoint=example.com:6443 expected, got %q", d.BootstrapToken.APIServerEndpoint)
635642
}
636-
if d.BootstrapToken.UnsafeSkipCAVerification != true {
637-
return errors.Errorf("BootstrapToken.UnsafeSkipCAVerification=true expected, got false")
643+
if d.BootstrapToken.UnsafeSkipCAVerification == true {
644+
return errors.Errorf("BootstrapToken.UnsafeSkipCAVerification=false expected, got true")
638645
}
639646
return nil
640647
},
@@ -667,6 +674,7 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
667674
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{
668675
Discovery: kubeadmv1beta1.Discovery{
669676
BootstrapToken: &kubeadmv1beta1.BootstrapTokenDiscovery{
677+
CACertHashes: dummyCAHash,
670678
APIServerEndpoint: "bar.com:6443",
671679
},
672680
},
@@ -689,7 +697,8 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
689697
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{
690698
Discovery: kubeadmv1beta1.Discovery{
691699
BootstrapToken: &kubeadmv1beta1.BootstrapTokenDiscovery{
692-
Token: "abcdef.0123456789abcdef",
700+
CACertHashes: dummyCAHash,
701+
Token: "abcdef.0123456789abcdef",
693702
},
694703
},
695704
},
@@ -729,7 +738,7 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileBehaviors(t *testin
729738

730739
for _, tc := range testcases {
731740
t.Run(tc.name, func(t *testing.T) {
732-
err := k.reconcileDiscovery(tc.cluster, tc.config)
741+
err := k.reconcileDiscovery(tc.cluster, tc.config, internalcluster.Certificates{})
733742
if err != nil {
734743
t.Errorf("expected nil, got error %v", err)
735744
}
@@ -758,15 +767,21 @@ func TestKubeadmConfigReconciler_Reconcile_DisocveryReconcileFailureBehaviors(t
758767
cluster: &clusterv1.Cluster{}, // cluster without endpoints
759768
config: &bootstrapv1.KubeadmConfig{
760769
Spec: bootstrapv1.KubeadmConfigSpec{
761-
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{},
770+
JoinConfiguration: &kubeadmv1beta1.JoinConfiguration{
771+
Discovery: kubeadmv1beta1.Discovery{
772+
BootstrapToken: &kubeadmv1beta1.BootstrapTokenDiscovery{
773+
CACertHashes: []string{"item"},
774+
},
775+
},
776+
},
762777
},
763778
},
764779
},
765780
}
766781

767782
for _, tc := range testcases {
768783
t.Run(tc.name, func(t *testing.T) {
769-
err := k.reconcileDiscovery(tc.cluster, tc.config)
784+
err := k.reconcileDiscovery(tc.cluster, tc.config, internalcluster.Certificates{})
770785
if err == nil {
771786
t.Error("expected error, got nil")
772787
}
@@ -1270,7 +1285,7 @@ func createSecrets(t *testing.T, cluster *clusterv1.Cluster, owner *bootstrapv1.
12701285
if owner.Spec.ClusterConfiguration == nil {
12711286
owner.Spec.ClusterConfiguration = &kubeadmv1beta1.ClusterConfiguration{}
12721287
}
1273-
certificates := internalcluster.NewCertificatesForControlPlane(owner.Spec.ClusterConfiguration)
1288+
certificates := internalcluster.NewCertificatesForInitialControlPlane(owner.Spec.ClusterConfiguration)
12741289
if err := certificates.Generate(); err != nil {
12751290
t.Fatal(err)
12761291
}

internal/cluster/certificates.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ var (
7474
// Certificates are the certificates necessary to bootstrap a cluster.
7575
type Certificates []*Certificate
7676

77-
// NewCertificatesForControlPlane returns a list of certificates configured for a control plane node
78-
func NewCertificatesForControlPlane(config *v1beta1.ClusterConfiguration) Certificates {
77+
// NewCertificatesForInitialControlPlane returns a list of certificates configured for a control plane node
78+
func NewCertificatesForInitialControlPlane(config *v1beta1.ClusterConfiguration) Certificates {
7979
if config.CertificatesDir == "" {
8080
config.CertificatesDir = defaultCertificatesDir
8181
}
@@ -122,6 +122,32 @@ func NewCertificatesForControlPlane(config *v1beta1.ClusterConfiguration) Certif
122122
return certificates
123123
}
124124

125+
// NewCertificatesForJoiningControlPlane gets any certs that exist and writes them to disk
126+
func NewCertificatesForJoiningControlPlane() Certificates {
127+
return Certificates{
128+
&Certificate{
129+
Purpose: secret.ClusterCA,
130+
CertFile: filepath.Join(defaultCertificatesDir, "ca.crt"),
131+
KeyFile: filepath.Join(defaultCertificatesDir, "ca.key"),
132+
},
133+
&Certificate{
134+
Purpose: ServiceAccount,
135+
CertFile: filepath.Join(defaultCertificatesDir, "sa.pub"),
136+
KeyFile: filepath.Join(defaultCertificatesDir, "sa.key"),
137+
},
138+
&Certificate{
139+
Purpose: FrontProxyCA,
140+
CertFile: filepath.Join(defaultCertificatesDir, "front-proxy-ca.crt"),
141+
KeyFile: filepath.Join(defaultCertificatesDir, "front-proxy-ca.key"),
142+
},
143+
&Certificate{
144+
Purpose: EtcdCA,
145+
CertFile: filepath.Join(defaultCertificatesDir, "etcd", "ca.crt"),
146+
KeyFile: filepath.Join(defaultCertificatesDir, "etcd", "ca.key"),
147+
},
148+
}
149+
}
150+
125151
// NewCertificatesForWorker return an initialized but empty set of CA certificates needed to bootstrap a cluster.
126152
func NewCertificatesForWorker(caCertPath string) Certificates {
127153
if caCertPath == "" {

internal/cluster/certificates_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
func TestNewCertificatesForControlPlane_Stacked(t *testing.T) {
2626
config := &v1beta1.ClusterConfiguration{}
27-
certs := NewCertificatesForControlPlane(config)
27+
certs := NewCertificatesForInitialControlPlane(config)
2828
if certs.GetByPurpose(EtcdCA).KeyFile == "" {
2929
t.Fatal("stacked control planes must define etcd CA key file")
3030
}
@@ -37,7 +37,7 @@ func TestNewCertificatesForControlPlane_External(t *testing.T) {
3737
},
3838
}
3939

40-
certs := NewCertificatesForControlPlane(config)
40+
certs := NewCertificatesForInitialControlPlane(config)
4141
if certs.GetByPurpose(EtcdCA).KeyFile != "" {
4242
t.Fatal("control planes with external etcd must *not* define the etcd key file")
4343
}

0 commit comments

Comments
 (0)