1
16
17 package copycerts
18
19 import (
20 "context"
21 "encoding/hex"
22 "fmt"
23 "os"
24 "path/filepath"
25 "strings"
26
27 "github.com/pkg/errors"
28
29 v1 "k8s.io/api/core/v1"
30 rbac "k8s.io/api/rbac/v1"
31 apierrors "k8s.io/apimachinery/pkg/api/errors"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 "k8s.io/apimachinery/pkg/runtime/schema"
34 clientset "k8s.io/client-go/kubernetes"
35 certutil "k8s.io/client-go/util/cert"
36 "k8s.io/client-go/util/keyutil"
37 bootstraputil "k8s.io/cluster-bootstrap/token/util"
38 "k8s.io/klog/v2"
39
40 bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
41 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
42 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
43 nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
44 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
45 cryptoutil "k8s.io/kubernetes/cmd/kubeadm/app/util/crypto"
46 )
47
48 const (
49 externalEtcdCA = "external-etcd-ca.crt"
50 externalEtcdCert = "external-etcd.crt"
51 externalEtcdKey = "external-etcd.key"
52 )
53
54
55
56 func createShortLivedBootstrapToken(client clientset.Interface) (string, error) {
57 tokenStr, err := bootstraputil.GenerateBootstrapToken()
58 if err != nil {
59 return "", errors.Wrap(err, "error generating token to upload certs")
60 }
61 token, err := bootstraptokenv1.NewBootstrapTokenString(tokenStr)
62 if err != nil {
63 return "", errors.Wrap(err, "error creating upload certs token")
64 }
65 tokens := []bootstraptokenv1.BootstrapToken{{
66 Token: token,
67 Description: "Proxy for managing TTL for the kubeadm-certs secret",
68 TTL: &metav1.Duration{
69 Duration: kubeadmconstants.DefaultCertTokenDuration,
70 },
71 }}
72
73 if err := nodebootstraptokenphase.CreateNewTokens(client, tokens); err != nil {
74 return "", errors.Wrap(err, "error creating token")
75 }
76 return tokens[0].Token.ID, nil
77 }
78
79
80 func CreateCertificateKey() (string, error) {
81 randBytes, err := cryptoutil.CreateRandBytes(kubeadmconstants.CertificateKeySize)
82 if err != nil {
83 return "", err
84 }
85 return hex.EncodeToString(randBytes), nil
86 }
87
88
89 func UploadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error {
90 fmt.Printf("[upload-certs] Storing the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
91 decodedKey, err := hex.DecodeString(key)
92 if err != nil {
93 return errors.Wrap(err, "error decoding certificate key")
94 }
95 tokenID, err := createShortLivedBootstrapToken(client)
96 if err != nil {
97 return err
98 }
99
100 secretData, err := getDataFromDisk(cfg, decodedKey)
101 if err != nil {
102 return err
103 }
104 ref, err := getSecretOwnerRef(client, tokenID)
105 if err != nil {
106 return err
107 }
108
109 err = apiclient.CreateOrUpdateSecret(client, &v1.Secret{
110 ObjectMeta: metav1.ObjectMeta{
111 Name: kubeadmconstants.KubeadmCertsSecret,
112 Namespace: metav1.NamespaceSystem,
113 OwnerReferences: ref,
114 },
115 Data: secretData,
116 })
117 if err != nil {
118 return err
119 }
120
121 return createRBAC(client)
122 }
123
124 func createRBAC(client clientset.Interface) error {
125 err := apiclient.CreateOrUpdateRole(client, &rbac.Role{
126 ObjectMeta: metav1.ObjectMeta{
127 Name: kubeadmconstants.KubeadmCertsClusterRoleName,
128 Namespace: metav1.NamespaceSystem,
129 },
130 Rules: []rbac.PolicyRule{
131 {
132 Verbs: []string{"get"},
133 APIGroups: []string{""},
134 Resources: []string{"secrets"},
135 ResourceNames: []string{kubeadmconstants.KubeadmCertsSecret},
136 },
137 },
138 })
139 if err != nil {
140 return err
141 }
142
143 return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
144 ObjectMeta: metav1.ObjectMeta{
145 Name: kubeadmconstants.KubeadmCertsClusterRoleName,
146 Namespace: metav1.NamespaceSystem,
147 },
148 RoleRef: rbac.RoleRef{
149 APIGroup: rbac.GroupName,
150 Kind: "Role",
151 Name: kubeadmconstants.KubeadmCertsClusterRoleName,
152 },
153 Subjects: []rbac.Subject{
154 {
155 Kind: rbac.GroupKind,
156 Name: kubeadmconstants.NodeBootstrapTokenAuthGroup,
157 },
158 },
159 })
160 }
161
162 func getSecretOwnerRef(client clientset.Interface, tokenID string) ([]metav1.OwnerReference, error) {
163 secretName := bootstraputil.BootstrapTokenSecretName(tokenID)
164 secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), secretName, metav1.GetOptions{})
165 if err != nil {
166 return nil, errors.Wrap(err, "error to get token reference")
167 }
168
169 gvk := schema.GroupVersionKind{Version: "v1", Kind: "Secret"}
170 ref := metav1.NewControllerRef(secret, gvk)
171 return []metav1.OwnerReference{*ref}, nil
172 }
173
174 func loadAndEncryptCert(certPath string, key []byte) ([]byte, error) {
175 cert, err := os.ReadFile(certPath)
176 if err != nil {
177 return nil, err
178 }
179 return cryptoutil.EncryptBytes(cert, key)
180 }
181
182 func certsToTransfer(cfg *kubeadmapi.InitConfiguration) map[string]string {
183 certsDir := cfg.CertificatesDir
184 certs := map[string]string{
185 kubeadmconstants.CACertName: filepath.Join(certsDir, kubeadmconstants.CACertName),
186 kubeadmconstants.CAKeyName: filepath.Join(certsDir, kubeadmconstants.CAKeyName),
187 kubeadmconstants.FrontProxyCACertName: filepath.Join(certsDir, kubeadmconstants.FrontProxyCACertName),
188 kubeadmconstants.FrontProxyCAKeyName: filepath.Join(certsDir, kubeadmconstants.FrontProxyCAKeyName),
189 kubeadmconstants.ServiceAccountPublicKeyName: filepath.Join(certsDir, kubeadmconstants.ServiceAccountPublicKeyName),
190 kubeadmconstants.ServiceAccountPrivateKeyName: filepath.Join(certsDir, kubeadmconstants.ServiceAccountPrivateKeyName),
191 }
192
193 if cfg.Etcd.External == nil {
194 certs[kubeadmconstants.EtcdCACertName] = filepath.Join(certsDir, kubeadmconstants.EtcdCACertName)
195 certs[kubeadmconstants.EtcdCAKeyName] = filepath.Join(certsDir, kubeadmconstants.EtcdCAKeyName)
196 } else {
197 certs[externalEtcdCA] = cfg.Etcd.External.CAFile
198 certs[externalEtcdCert] = cfg.Etcd.External.CertFile
199 certs[externalEtcdKey] = cfg.Etcd.External.KeyFile
200 }
201
202 return certs
203 }
204
205 func getDataFromDisk(cfg *kubeadmapi.InitConfiguration, key []byte) (map[string][]byte, error) {
206 secretData := map[string][]byte{}
207 for certName, certPath := range certsToTransfer(cfg) {
208 cert, err := loadAndEncryptCert(certPath, key)
209 if err == nil || os.IsNotExist(err) {
210 secretData[certOrKeyNameToSecretName(certName)] = cert
211 } else {
212 return nil, err
213 }
214 }
215 return secretData, nil
216 }
217
218
219 func DownloadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error {
220 fmt.Printf("[download-certs] Downloading the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
221
222 decodedKey, err := hex.DecodeString(key)
223 if err != nil {
224 return errors.Wrap(err, "error decoding certificate key")
225 }
226
227 secret, err := getSecret(client)
228 if err != nil {
229 return errors.Wrap(err, "error downloading the secret")
230 }
231
232 secretData, err := getDataFromSecret(secret, decodedKey)
233 if err != nil {
234 return errors.Wrap(err, "error decoding secret data with provided key")
235 }
236
237 fmt.Printf("[download-certs] Saving the certificates to the folder: %q\n", cfg.CertificatesDir)
238
239 for certOrKeyName, certOrKeyPath := range certsToTransfer(cfg) {
240 certOrKeyData, found := secretData[certOrKeyNameToSecretName(certOrKeyName)]
241 if !found {
242 return errors.Errorf("the Secret does not include the required certificate or key - name: %s, path: %s", certOrKeyName, certOrKeyPath)
243 }
244 if len(certOrKeyData) == 0 {
245 klog.V(1).Infof("[download-certs] Not saving %q to disk, since it is empty in the %q Secret\n", certOrKeyName, kubeadmconstants.KubeadmCertsSecret)
246 continue
247 }
248 if err := writeCertOrKey(certOrKeyPath, certOrKeyData); err != nil {
249 return err
250 }
251 }
252
253 return nil
254 }
255
256 func writeCertOrKey(certOrKeyPath string, certOrKeyData []byte) error {
257 if _, err := keyutil.ParsePrivateKeyPEM(certOrKeyData); err == nil {
258 return keyutil.WriteKey(certOrKeyPath, certOrKeyData)
259 } else if _, err := keyutil.ParsePublicKeysPEM(certOrKeyData); err == nil {
260 return certutil.WriteCert(certOrKeyPath, certOrKeyData)
261 }
262 return errors.New("unknown data found in Secret entry")
263 }
264
265 func getSecret(client clientset.Interface) (*v1.Secret, error) {
266 secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{})
267 if err != nil {
268 if apierrors.IsNotFound(err) {
269 return nil, errors.Errorf("Secret %q was not found in the %q Namespace. This Secret might have expired. Please, run `kubeadm init phase upload-certs --upload-certs` on a control plane to generate a new one", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem)
270 }
271 return nil, err
272 }
273 return secret, nil
274 }
275
276 func getDataFromSecret(secret *v1.Secret, key []byte) (map[string][]byte, error) {
277 secretData := map[string][]byte{}
278 for secretName, encryptedSecret := range secret.Data {
279
280
281 if len(encryptedSecret) > 0 {
282 cert, err := cryptoutil.DecryptBytes(encryptedSecret, key)
283 if err != nil {
284
285
286 return map[string][]byte{}, err
287 }
288 secretData[secretName] = cert
289 } else {
290 secretData[secretName] = []byte{}
291 }
292 }
293 return secretData, nil
294 }
295
296 func certOrKeyNameToSecretName(certOrKeyName string) string {
297 return strings.Replace(certOrKeyName, "/", "-", -1)
298 }
299
View as plain text