/
readwriter.go
218 lines (181 loc) · 8.07 KB
/
readwriter.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package renewal
import (
"crypto"
"crypto/x509"
"os"
"path/filepath"
"github.com/pkg/errors"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
)
// certificateReadWriter defines the behavior of a component that
// read or write a certificate stored/embedded in a file
type certificateReadWriter interface {
//Exists return true if the certificate exists
Exists() bool
// Read a certificate stored/embedded in a file
Read() (*x509.Certificate, error)
// Write (update) a certificate stored/embedded in a file
Write(*x509.Certificate, crypto.Signer) error
}
// pkiCertificateReadWriter defines a certificateReadWriter for certificate files
// in the K8s pki managed by kubeadm
type pkiCertificateReadWriter struct {
baseName string
certificateDir string
}
// newPKICertificateReadWriter return a new pkiCertificateReadWriter
func newPKICertificateReadWriter(certificateDir string, baseName string) *pkiCertificateReadWriter {
return &pkiCertificateReadWriter{
baseName: baseName,
certificateDir: certificateDir,
}
}
// Exists checks if a certificate exist
func (rw *pkiCertificateReadWriter) Exists() bool {
certificatePath, _ := pkiutil.PathsForCertAndKey(rw.certificateDir, rw.baseName)
return fileExists(certificatePath)
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// Read a certificate from a file the K8s pki managed by kubeadm
func (rw *pkiCertificateReadWriter) Read() (*x509.Certificate, error) {
certificatePath, _ := pkiutil.PathsForCertAndKey(rw.certificateDir, rw.baseName)
certs, err := certutil.CertsFromFile(certificatePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to load existing certificate %s", rw.baseName)
}
if len(certs) != 1 {
return nil, errors.Errorf("wanted exactly one certificate, got %d", len(certs))
}
return certs[0], nil
}
// Write a certificate to files in the K8s pki managed by kubeadm
func (rw *pkiCertificateReadWriter) Write(newCert *x509.Certificate, newKey crypto.Signer) error {
if err := pkiutil.WriteCertAndKey(rw.certificateDir, rw.baseName, newCert, newKey); err != nil {
return errors.Wrapf(err, "failed to write new certificate %s", rw.baseName)
}
return nil
}
// kubeConfigReadWriter defines a certificateReadWriter for certificate files
// embedded in the kubeConfig files managed by kubeadm, and more specifically
// for the client certificate of the AuthInfo
type kubeConfigReadWriter struct {
kubernetesDir string
kubeConfigFileName string
kubeConfigFilePath string
kubeConfig *clientcmdapi.Config
baseName string
certificateDir string
caCert *x509.Certificate
}
// newKubeconfigReadWriter return a new kubeConfigReadWriter
func newKubeconfigReadWriter(kubernetesDir string, kubeConfigFileName string, certificateDir, baseName string) *kubeConfigReadWriter {
return &kubeConfigReadWriter{
kubernetesDir: kubernetesDir,
kubeConfigFileName: kubeConfigFileName,
kubeConfigFilePath: filepath.Join(kubernetesDir, kubeConfigFileName),
certificateDir: certificateDir,
baseName: baseName,
}
}
// Exists checks if a certificate embedded in kubeConfig file exists
func (rw *kubeConfigReadWriter) Exists() bool {
return fileExists(rw.kubeConfigFilePath)
}
// Read a certificate embedded in kubeConfig file managed by kubeadm.
// Please note that the kubeConfig file itself is kept in the ReadWriter state thus allowing
// to preserve the attributes (Context, Servers, AuthInfo etc.)
func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
// try to load the kubeConfig file
kubeConfig, err := clientcmd.LoadFromFile(rw.kubeConfigFilePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to load kubeConfig file %s", rw.kubeConfigFilePath)
}
// The CA cert is required for updating kubeconfig files.
// For local CA renewal, the local CA on disk could have changed, thus a reload is needed.
// For CSR renewal we assume the same CA on disk is mounted for usage with KCM's
// '--cluster-signing-cert-file' flag.
caCert, _, err := certsphase.LoadCertificateAuthority(rw.certificateDir, rw.baseName)
if err != nil {
return nil, err
}
rw.caCert = caCert
// get current context
if _, ok := kubeConfig.Contexts[kubeConfig.CurrentContext]; !ok {
return nil, errors.Errorf("invalid kubeConfig file %s: missing context %s", rw.kubeConfigFilePath, kubeConfig.CurrentContext)
}
// get cluster info for current context and ensure a server certificate is embedded in it
clusterName := kubeConfig.Contexts[kubeConfig.CurrentContext].Cluster
if _, ok := kubeConfig.Clusters[clusterName]; !ok {
return nil, errors.Errorf("invalid kubeConfig file %s: missing cluster %s", rw.kubeConfigFilePath, clusterName)
}
cluster := kubeConfig.Clusters[clusterName]
if len(cluster.CertificateAuthorityData) == 0 {
return nil, errors.Errorf("kubeConfig file %s does not have an embedded server certificate", rw.kubeConfigFilePath)
}
// get auth info for current context and ensure a client certificate is embedded in it
authInfoName := kubeConfig.Contexts[kubeConfig.CurrentContext].AuthInfo
if _, ok := kubeConfig.AuthInfos[authInfoName]; !ok {
return nil, errors.Errorf("invalid kubeConfig file %s: missing authInfo %s", rw.kubeConfigFilePath, authInfoName)
}
authInfo := kubeConfig.AuthInfos[authInfoName]
if len(authInfo.ClientCertificateData) == 0 {
return nil, errors.Errorf("kubeConfig file %s does not have an embedded client certificate", rw.kubeConfigFilePath)
}
// parse the client certificate, retrive the cert config and then renew it
certs, err := certutil.ParseCertsPEM(authInfo.ClientCertificateData)
if err != nil {
return nil, errors.Wrapf(err, "kubeConfig file %s does not contain a valid client certificate", rw.kubeConfigFilePath)
}
rw.kubeConfig = kubeConfig
return certs[0], nil
}
// Write a certificate embedded in kubeConfig file managed by kubeadm
// Please note that all the other attribute of the kubeConfig file are preserved, but this
// requires to call Read before Write
func (rw *kubeConfigReadWriter) Write(newCert *x509.Certificate, newKey crypto.Signer) error {
// check if Read was called before Write
if rw.kubeConfig == nil {
return errors.Errorf("failed to Write kubeConfig file with renewed certs. It is necessary to call Read before Write")
}
// encodes the new key
encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(newKey)
if err != nil {
return errors.Wrapf(err, "failed to marshal private key to PEM")
}
// Update the embedded CA in the kubeconfig file.
// This assumes that the user has kept the current context to the desired one.
clusterName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].Cluster
cluster := rw.kubeConfig.Clusters[clusterName]
cluster.CertificateAuthorityData = pkiutil.EncodeCertPEM(rw.caCert)
// get auth info for current context and ensure a client certificate is embedded in it
authInfoName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].AuthInfo
// create a kubeConfig copy with the new client certs
newConfig := rw.kubeConfig.DeepCopy()
newConfig.AuthInfos[authInfoName].ClientKeyData = encodedClientKey
newConfig.AuthInfos[authInfoName].ClientCertificateData = pkiutil.EncodeCertPEM(newCert)
// writes the kubeConfig to disk
return clientcmd.WriteToFile(*newConfig, rw.kubeConfigFilePath)
}