-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathcerts.go
174 lines (157 loc) · 6.66 KB
/
certs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package controllers
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"sigs.k8s.io/cluster-api/util/conditions"
"path/filepath"
etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
certutil "k8s.io/client-go/util/cert"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/certs"
"sigs.k8s.io/cluster-api/util/secret"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/etcdadm/certs/pkiutil"
"sigs.k8s.io/etcdadm/constants"
)
// etcdadm provisioning works as follows:
// machine one runs etcdadm init, generates CA and client certs
// CA certs are copied over to remaining nodes to run etcdadm join
// This provider is going to generate CA cert-key for etcd, and create two Secrets to store CA cert + client cert-key to be used by kube-apiserver
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.
// 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
}
func etcdCACertKeyPair() secret.Certificates {
certificatesDir := "/etc/etcd/pki"
certificates := secret.Certificates{
&secret.Certificate{
Purpose: secret.ManagedExternalEtcdCA,
CertFile: filepath.Join(certificatesDir, "ca.crt"),
KeyFile: filepath.Join(certificatesDir, "ca.key"),
},
}
return certificates
}
// TODO: save CA and client cert on the reconciler object
func (r *EtcdadmClusterReconciler) getCACert(ctx context.Context, cluster *clusterv1.Cluster) ([]byte, error) {
caCert := &secret.Certificates{
&secret.Certificate{
Purpose: secret.ManagedExternalEtcdCA,
},
}
if err := caCert.Lookup(ctx, r.Client, util.ObjectKey(cluster)); err != nil {
return []byte{}, errors.Wrap(err, "error looking up external etcd CA certs")
}
if caCertKey := caCert.GetByPurpose(secret.ManagedExternalEtcdCA); caCertKey != nil {
if caCertKey.KeyPair == nil {
return []byte{}, errors.New("ca cert key pair not found for cluster")
}
return caCertKey.KeyPair.Cert, nil
}
return []byte{}, fmt.Errorf("nil returned from getting etcd CA certificate by purpose %s", secret.ManagedExternalEtcdCA)
}
func (r *EtcdadmClusterReconciler) getClientCerts(ctx context.Context, cluster *clusterv1.Cluster) (tls.Certificate, error) {
clientCert := &secret.Certificates{
&secret.Certificate{
Purpose: secret.APIServerEtcdClient,
},
}
if err := clientCert.Lookup(ctx, r.Client, util.ObjectKey(cluster)); err != nil {
return tls.Certificate{}, err
}
if clientCertKey := clientCert.GetByPurpose(secret.APIServerEtcdClient); clientCertKey != nil {
return tls.X509KeyPair(clientCertKey.KeyPair.Cert, clientCertKey.KeyPair.Key)
}
return tls.Certificate{}, fmt.Errorf("nil returned from getting etcd CA certificate by purpose %s", secret.APIServerEtcdClient)
}