in controllers/certs.go [34:128]
func (r *EtcdadmClusterReconciler) generateCAandClientCertSecrets(ctx context.Context, cluster *clusterv1.Cluster, etcdCluster *etcdv1.EtcdadmCluster) error {
log := r.Log
// Generate external etcd CA cert + key pair
CACertKeyPair := etcdCACertKeyPair()
err := CACertKeyPair.LookupOrGenerate(
ctx,
r.Client,
util.ObjectKey(cluster),
*metav1.NewControllerRef(etcdCluster, etcdv1.GroupVersion.WithKind("EtcdadmCluster")),
)
if err != nil {
log.Error(err, "Failed to look up or generate CA cert key pair")
return err
}
caCertKey := CACertKeyPair.GetByPurpose(secret.ManagedExternalEtcdCA)
if caCertKey == nil {
return fmt.Errorf("nil returned from getting etcd CA certificate by purpose %s", secret.ManagedExternalEtcdCA)
}
// Use the generated CA cert+key pair to generate and sign etcd client cert+key pair
caCertDecoded, _ := pem.Decode(caCertKey.KeyPair.Cert)
caCert, err := x509.ParseCertificate(caCertDecoded.Bytes)
if err != nil {
log.Error(err, "Failed to parse etcd CA cert")
return err
}
caKeyDecoded, _ := pem.Decode(caCertKey.KeyPair.Key)
caKey, err := x509.ParsePKCS1PrivateKey(caKeyDecoded.Bytes)
if err != nil {
log.Error(err, "Failed to parse etcd CA key")
return err
}
commonName := fmt.Sprintf("%s-kube-apiserver-etcd-client", cluster.Name)
// This certConfig is what etcdadm uses to generate client certs https://github.com/kubernetes-sigs/etcdadm/blob/master/certs/certs.go#L233
certConfig := certutil.Config{
CommonName: commonName,
Organization: []string{constants.MastersGroup},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, certConfig)
if err != nil {
return fmt.Errorf("failure while creating %q etcd client key and certificate: %v", commonName, err)
}
// Now generate two Secrets, one containing the client cert+key pair and other containing the etcd CA cert. Ech control plane provider should
// use these two Secrets for communicating with etcd.
apiServerClientCertKeyPair := secret.Certificate{
Purpose: secret.APIServerEtcdClient,
KeyPair: &certs.KeyPair{
Cert: certs.EncodeCertPEM(apiClientCert),
Key: certs.EncodePrivateKeyPEM(apiClientKey),
},
Generated: true,
}
s := apiServerClientCertKeyPair.AsSecret(client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}, *metav1.NewControllerRef(etcdCluster, etcdv1.GroupVersion.WithKind("EtcdadmCluster")))
secretToPatch := s.DeepCopy()
// CreateOrPatch performs a create operation when the object is not found.
// But if an object is found, the function expects to reconcile the fields we want patched in a callback func.
// CreateOrPatch does a GET call and overwrites the object we pass in with whats on the cluster.
// Hence we keep a copy of the newly generated secret and update the secret Data field in a callback func.
// Ex; https://github.com/kubernetes-sigs/controller-runtime/blob/v0.14.5/pkg/controller/controllerutil/example_test.go
if _, err := controllerutil.CreateOrPatch(ctx, r.Client, s, func() error {
s.Data = secretToPatch.Data
return nil
}); err != nil {
return fmt.Errorf("failure while saving etcd client key and certificate: %v", err)
}
log.Info("Saved apiserver client cert key as secret")
s = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace,
Name: secret.Name(cluster.Name, secret.EtcdCA),
Labels: map[string]string{
clusterv1.ClusterNameLabel: cluster.Name,
},
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(etcdCluster, etcdv1.GroupVersion.WithKind("EtcdadmCluster"))},
},
Data: map[string][]byte{
secret.TLSCrtDataName: caCertKey.KeyPair.Cert,
},
Type: clusterv1.ClusterSecretType,
}
if err := r.Client.Create(ctx, s); err != nil && !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("failure while saving etcd CA certificate: %v", err)
}
log.Info("Saved etcd ca cert as secret")
conditions.MarkTrue(etcdCluster, etcdv1.EtcdCertificatesAvailableCondition)
return nil
}