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

Commit b22e54b

Browse files
authored
Merge pull request #263 from chuckha/control-plane-join-certs
🐛 Separate certificate logic for joins
2 parents faeb391 + 3aceccd commit b22e54b

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)