Skip to content

Commit 0ba5891

Browse files
committed
kubeadm: update embedded CA in kubeconfig files on renewal
While kubeadm does not support CA rotation, the users might still attempt to perform this manually. For kubeconfig files, updating to a new CA is not reflected and users need to embed new CA PEM manually. On kubeconfig cert renewal, always keep the embedded CA in sync with the one on disk. Includes a couple of typo fixes.
1 parent f9250c4 commit 0ba5891

File tree

4 files changed

+47
-10
lines changed

4 files changed

+47
-10
lines changed

cmd/kubeadm/app/phases/certs/renewal/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ go_test(
4141
embed = [":go_default_library"],
4242
deps = [
4343
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
44+
"//cmd/kubeadm/app/constants:go_default_library",
4445
"//cmd/kubeadm/app/util/certs:go_default_library",
4546
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
4647
"//cmd/kubeadm/app/util/pkiutil:go_default_library",

cmd/kubeadm/app/phases/certs/renewal/manager.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ func NewManager(cfg *kubeadmapi.ClusterConfiguration, kubernetesDir string) (*Ma
154154
// create a CertificateRenewHandler for each kubeConfig file
155155
for _, kubeConfig := range kubeConfigs {
156156
// create a ReadWriter for certificates embedded in kubeConfig files
157-
kubeConfigReadWriter := newKubeconfigReadWriter(kubernetesDir, kubeConfig.fileName)
157+
kubeConfigReadWriter := newKubeconfigReadWriter(kubernetesDir, kubeConfig.fileName,
158+
rm.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
158159

159160
// adds the certificateRenewHandler.
160161
// Certificates embedded kubeConfig files in are indexed by fileName, that is a well know constant defined

cmd/kubeadm/app/phases/certs/renewal/readwriter.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2929
certutil "k8s.io/client-go/util/cert"
3030
"k8s.io/client-go/util/keyutil"
31+
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
3132
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
3233
)
3334

@@ -104,14 +105,19 @@ type kubeConfigReadWriter struct {
104105
kubeConfigFileName string
105106
kubeConfigFilePath string
106107
kubeConfig *clientcmdapi.Config
108+
baseName string
109+
certificateDir string
110+
caCert *x509.Certificate
107111
}
108112

109113
// newKubeconfigReadWriter return a new kubeConfigReadWriter
110-
func newKubeconfigReadWriter(kubernetesDir string, kubeConfigFileName string) *kubeConfigReadWriter {
114+
func newKubeconfigReadWriter(kubernetesDir string, kubeConfigFileName string, certificateDir, baseName string) *kubeConfigReadWriter {
111115
return &kubeConfigReadWriter{
112116
kubernetesDir: kubernetesDir,
113117
kubeConfigFileName: kubeConfigFileName,
114118
kubeConfigFilePath: filepath.Join(kubernetesDir, kubeConfigFileName),
119+
certificateDir: certificateDir,
120+
baseName: baseName,
115121
}
116122
}
117123

@@ -130,6 +136,16 @@ func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
130136
return nil, errors.Wrapf(err, "failed to load kubeConfig file %s", rw.kubeConfigFilePath)
131137
}
132138

139+
// The CA cert is required for updating kubeconfig files.
140+
// For local CA renewal, the local CA on disk could have changed, thus a reload is needed.
141+
// For CSR renewal we assume the same CA on disk is mounted for usage with KCM's
142+
// '--cluster-signing-cert-file' flag.
143+
caCert, _, err := certsphase.LoadCertificateAuthority(rw.certificateDir, rw.baseName)
144+
if err != nil {
145+
return nil, err
146+
}
147+
rw.caCert = caCert
148+
133149
// get current context
134150
if _, ok := kubeConfig.Contexts[kubeConfig.CurrentContext]; !ok {
135151
return nil, errors.Errorf("invalid kubeConfig file %s: missing context %s", rw.kubeConfigFilePath, kubeConfig.CurrentContext)
@@ -143,7 +159,7 @@ func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
143159

144160
cluster := kubeConfig.Clusters[clusterName]
145161
if len(cluster.CertificateAuthorityData) == 0 {
146-
return nil, errors.Errorf("kubeConfig file %s does not have and embedded server certificate", rw.kubeConfigFilePath)
162+
return nil, errors.Errorf("kubeConfig file %s does not have an embedded server certificate", rw.kubeConfigFilePath)
147163
}
148164

149165
// get auth info for current context and ensure a client certificate is embedded in it
@@ -154,7 +170,7 @@ func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
154170

155171
authInfo := kubeConfig.AuthInfos[authInfoName]
156172
if len(authInfo.ClientCertificateData) == 0 {
157-
return nil, errors.Errorf("kubeConfig file %s does not have and embedded client certificate", rw.kubeConfigFilePath)
173+
return nil, errors.Errorf("kubeConfig file %s does not have an embedded client certificate", rw.kubeConfigFilePath)
158174
}
159175

160176
// parse the client certificate, retrive the cert config and then renew it
@@ -174,7 +190,7 @@ func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
174190
func (rw *kubeConfigReadWriter) Write(newCert *x509.Certificate, newKey crypto.Signer) error {
175191
// check if Read was called before Write
176192
if rw.kubeConfig == nil {
177-
return errors.Errorf("failed to Write kubeConfig file with renewd certs. It is necessary to call Read before Write")
193+
return errors.Errorf("failed to Write kubeConfig file with renewed certs. It is necessary to call Read before Write")
178194
}
179195

180196
// encodes the new key
@@ -183,6 +199,12 @@ func (rw *kubeConfigReadWriter) Write(newCert *x509.Certificate, newKey crypto.S
183199
return errors.Wrapf(err, "failed to marshal private key to PEM")
184200
}
185201

202+
// Update the embedded CA in the kubeconfig file.
203+
// This assumes that the user has kept the current context to the desired one.
204+
clusterName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].Cluster
205+
cluster := rw.kubeConfig.Clusters[clusterName]
206+
cluster.CertificateAuthorityData = pkiutil.EncodeCertPEM(rw.caCert)
207+
186208
// get auth info for current context and ensure a client certificate is embedded in it
187209
authInfoName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].AuthInfo
188210

cmd/kubeadm/app/phases/certs/renewal/readwriter_test.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"k8s.io/client-go/tools/clientcmd"
2828
certutil "k8s.io/client-go/util/cert"
2929
"k8s.io/client-go/util/keyutil"
30+
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
3031
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
3132
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
3233
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
@@ -79,15 +80,27 @@ func TestPKICertificateReadWriter(t *testing.T) {
7980
}
8081

8182
func TestKubeconfigReadWriter(t *testing.T) {
82-
// creates a tmp folder
83-
dir := testutil.SetupTempDir(t)
84-
defer os.RemoveAll(dir)
83+
// creates tmp folders
84+
dirKubernetes := testutil.SetupTempDir(t)
85+
defer os.RemoveAll(dirKubernetes)
86+
dirPKI := testutil.SetupTempDir(t)
87+
defer os.RemoveAll(dirPKI)
88+
89+
// write the CA cert and key to the temporary PKI dir
90+
caName := kubeadmconstants.CACertAndKeyBaseName
91+
if err := pkiutil.WriteCertAndKey(
92+
dirPKI,
93+
caName,
94+
testCACert,
95+
testCAKey); err != nil {
96+
t.Fatalf("couldn't write out certificate %s to %s", caName, dirPKI)
97+
}
8598

8699
// creates a certificate and then embeds it into a kubeconfig file
87-
cert := writeTestKubeconfig(t, dir, "test", testCACert, testCAKey)
100+
cert := writeTestKubeconfig(t, dirKubernetes, "test", testCACert, testCAKey)
88101

89102
// Creates a KubeconfigReadWriter
90-
kubeconfigReadWriter := newKubeconfigReadWriter(dir, "test")
103+
kubeconfigReadWriter := newKubeconfigReadWriter(dirKubernetes, "test", dirPKI, caName)
91104

92105
// Reads the certificate embedded in a kubeconfig
93106
readCert, err := kubeconfigReadWriter.Read()

0 commit comments

Comments
 (0)