Skip to content

Commit 930022c

Browse files
authored
Merge pull request kubernetes#121841 from SataQiu/fix-renew-20231110
kubeadm: support updating certificate organization during 'kubeadm certs renew'
2 parents 1f3256b + bda722b commit 930022c

File tree

5 files changed

+135
-33
lines changed

5 files changed

+135
-33
lines changed

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

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ type Manager struct {
4646
cas map[string]*CAExpirationHandler
4747
}
4848

49+
type certConfigMutatorFunc func(*certutil.Config) error
50+
4951
// CertificateRenewHandler defines required info for renewing a certificate
5052
type CertificateRenewHandler struct {
5153
// Name of the certificate to be used for UX.
@@ -66,6 +68,10 @@ type CertificateRenewHandler struct {
6668

6769
// readwriter defines a CertificateReadWriter to be used for certificate renewal
6870
readwriter certificateReadWriter
71+
72+
// certConfigMutators holds the mutator functions that can be applied to the input cert config object
73+
// These functions will be run in series.
74+
certConfigMutators []certConfigMutatorFunc
6975
}
7076

7177
// CAExpirationHandler defines required info for CA expiration check
@@ -109,17 +115,19 @@ func NewManager(cfg *kubeadmapi.ClusterConfiguration, kubernetesDir string) (*Ma
109115
for _, cert := range certs {
110116
// create a ReadWriter for certificates stored in the K8s local PKI
111117
pkiReadWriter := newPKICertificateReadWriter(rm.cfg.CertificatesDir, cert.BaseName)
118+
certConfigMutators := loadCertConfigMutators(cert.BaseName)
112119

113120
// adds the certificateRenewHandler.
114121
// PKI certificates are indexed by name, that is a well know constant defined
115122
// in the certsphase package and that can be reused across all the kubeadm codebase
116123
rm.certificates[cert.Name] = &CertificateRenewHandler{
117-
Name: cert.Name,
118-
LongName: cert.LongName,
119-
FileName: cert.BaseName,
120-
CAName: ca.Name,
121-
CABaseName: ca.BaseName, //Nb. this is a path for etcd certs (they are stored in a subfolder)
122-
readwriter: pkiReadWriter,
124+
Name: cert.Name,
125+
LongName: cert.LongName,
126+
FileName: cert.BaseName,
127+
CAName: ca.Name,
128+
CABaseName: ca.BaseName, // Nb. this is a path for etcd certs (they are stored in a subfolder)
129+
readwriter: pkiReadWriter,
130+
certConfigMutators: certConfigMutators,
123131
}
124132
}
125133

@@ -230,8 +238,15 @@ func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) {
230238
}
231239

232240
// extract the certificate config
241+
certConfig := certToConfig(cert)
242+
for _, f := range handler.certConfigMutators {
243+
if err := f(&certConfig); err != nil {
244+
return false, err
245+
}
246+
}
247+
233248
cfg := &pkiutil.CertConfig{
234-
Config: certToConfig(cert),
249+
Config: certConfig,
235250
EncryptionAlgorithm: rm.cfg.EncryptionAlgorithmType(),
236251
}
237252

@@ -273,8 +288,14 @@ func (rm *Manager) CreateRenewCSR(name, outdir string) error {
273288
}
274289

275290
// extracts the certificate config
291+
certConfig := certToConfig(cert)
292+
for _, f := range handler.certConfigMutators {
293+
if err := f(&certConfig); err != nil {
294+
return err
295+
}
296+
}
276297
cfg := &pkiutil.CertConfig{
277-
Config: certToConfig(cert),
298+
Config: certConfig,
278299
EncryptionAlgorithm: rm.cfg.EncryptionAlgorithmType(),
279300
}
280301

@@ -400,3 +421,50 @@ func certToConfig(cert *x509.Certificate) certutil.Config {
400421
Usages: cert.ExtKeyUsage,
401422
}
402423
}
424+
425+
func loadCertConfigMutators(certBaseName string) []certConfigMutatorFunc {
426+
// TODO: Remove these mutators after the organization migration is complete in a future release
427+
// https://github.com/kubernetes/kubeadm/issues/2414
428+
switch certBaseName {
429+
case kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName,
430+
kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName:
431+
return []certConfigMutatorFunc{
432+
removeSystemPrivilegedGroupMutator(),
433+
}
434+
case kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName:
435+
return []certConfigMutatorFunc{
436+
removeSystemPrivilegedGroupMutator(),
437+
addClusterAdminsGroupMutator(),
438+
}
439+
}
440+
return nil
441+
}
442+
443+
func removeSystemPrivilegedGroupMutator() certConfigMutatorFunc {
444+
return func(c *certutil.Config) error {
445+
organizations := make([]string, 0, len(c.Organization))
446+
for _, org := range c.Organization {
447+
if org != kubeadmconstants.SystemPrivilegedGroup {
448+
organizations = append(organizations, org)
449+
}
450+
}
451+
c.Organization = organizations
452+
return nil
453+
}
454+
}
455+
456+
func addClusterAdminsGroupMutator() certConfigMutatorFunc {
457+
return func(c *certutil.Config) error {
458+
found := false
459+
for _, org := range c.Organization {
460+
if org == kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding {
461+
found = true
462+
break
463+
}
464+
}
465+
if !found {
466+
c.Organization = append(c.Organization, kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding)
467+
}
468+
return nil
469+
}
470+
}

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

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
netutils "k8s.io/utils/net"
3131

3232
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
33+
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
3334
certtestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs"
3435
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
3536
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
@@ -42,17 +43,9 @@ var (
4243

4344
testCACert, testCAKey, _ = pkiutil.NewCertificateAuthority(testCACertCfg)
4445

45-
testCertCfg = &pkiutil.CertConfig{
46-
Config: certutil.Config{
47-
CommonName: "test-common-name",
48-
Organization: []string{"sig-cluster-lifecycle"},
49-
AltNames: certutil.AltNames{
50-
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
51-
DNSNames: []string{"test-domain.space"},
52-
},
53-
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
54-
},
55-
}
46+
testCertOrganization = []string{"sig-cluster-lifecycle"}
47+
48+
testCertCfg = makeTestCertConfig(testCertOrganization)
5649
)
5750

5851
func TestNewManager(t *testing.T) {
@@ -99,6 +92,11 @@ func TestRenewUsingLocalCA(t *testing.T) {
9992
t.Fatalf("couldn't write out CA certificate to %s", dir)
10093
}
10194

95+
etcdDir := filepath.Join(dir, "etcd")
96+
if err := pkiutil.WriteCertAndKey(etcdDir, "ca", testCACert, testCAKey); err != nil {
97+
t.Fatalf("couldn't write out CA certificate to %s", etcdDir)
98+
}
99+
102100
cfg := &kubeadmapi.ClusterConfiguration{
103101
CertificatesDir: dir,
104102
}
@@ -108,23 +106,42 @@ func TestRenewUsingLocalCA(t *testing.T) {
108106
}
109107

110108
tests := []struct {
111-
name string
112-
certName string
113-
createCertFunc func() *x509.Certificate
109+
name string
110+
certName string
111+
createCertFunc func() *x509.Certificate
112+
expectedOrganization []string
114113
}{
115114
{
116115
name: "Certificate renewal for a PKI certificate",
117116
certName: "apiserver",
118117
createCertFunc: func() *x509.Certificate {
119-
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey)
118+
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
120119
},
120+
expectedOrganization: testCertOrganization,
121121
},
122122
{
123123
name: "Certificate renewal for a certificate embedded in a kubeconfig file",
124124
certName: "admin.conf",
125125
createCertFunc: func() *x509.Certificate {
126126
return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey)
127127
},
128+
expectedOrganization: testCertOrganization,
129+
},
130+
{
131+
name: "apiserver-etcd-client cert should not contain SystemPrivilegedGroup after renewal",
132+
certName: "apiserver-etcd-client",
133+
createCertFunc: func() *x509.Certificate {
134+
return writeTestCertificate(t, dir, "apiserver-etcd-client", testCACert, testCAKey, []string{kubeadmconstants.SystemPrivilegedGroup})
135+
},
136+
expectedOrganization: []string{},
137+
},
138+
{
139+
name: "apiserver-kubelet-client cert should replace SystemPrivilegedGroup with ClusterAdminsGroup after renewal",
140+
certName: "apiserver-kubelet-client",
141+
createCertFunc: func() *x509.Certificate {
142+
return writeTestCertificate(t, dir, "apiserver-kubelet-client", testCACert, testCAKey, []string{kubeadmconstants.SystemPrivilegedGroup})
143+
},
144+
expectedOrganization: []string{kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding},
128145
},
129146
}
130147

@@ -154,7 +171,7 @@ func TestRenewUsingLocalCA(t *testing.T) {
154171

155172
certtestutil.AssertCertificateIsSignedByCa(t, newCert, testCACert)
156173
certtestutil.AssertCertificateHasClientAuthUsage(t, newCert)
157-
certtestutil.AssertCertificateHasOrganizations(t, newCert, testCertCfg.Organization...)
174+
certtestutil.AssertCertificateHasOrganizations(t, newCert, test.expectedOrganization...)
158175
certtestutil.AssertCertificateHasCommonName(t, newCert, testCertCfg.CommonName)
159176
certtestutil.AssertCertificateHasDNSNames(t, newCert, testCertCfg.AltNames.DNSNames...)
160177
certtestutil.AssertCertificateHasIPAddresses(t, newCert, testCertCfg.AltNames.IPs...)
@@ -193,7 +210,7 @@ func TestCreateRenewCSR(t *testing.T) {
193210
name: "Creation of a CSR request for renewal of a PKI certificate",
194211
certName: "apiserver",
195212
createCertFunc: func() *x509.Certificate {
196-
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey)
213+
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
197214
},
198215
},
199216
{
@@ -233,7 +250,7 @@ func TestCreateRenewCSR(t *testing.T) {
233250
func TestCertToConfig(t *testing.T) {
234251
expectedConfig := &certutil.Config{
235252
CommonName: "test-common-name",
236-
Organization: []string{"sig-cluster-lifecycle"},
253+
Organization: testCertOrganization,
237254
AltNames: certutil.AltNames{
238255
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
239256
DNSNames: []string{"test-domain.space"},
@@ -244,7 +261,7 @@ func TestCertToConfig(t *testing.T) {
244261
cert := &x509.Certificate{
245262
Subject: pkix.Name{
246263
CommonName: "test-common-name",
247-
Organization: []string{"sig-cluster-lifecycle"},
264+
Organization: testCertOrganization,
248265
},
249266
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
250267
DNSNames: []string{"test-domain.space"},
@@ -274,3 +291,17 @@ func TestCertToConfig(t *testing.T) {
274291
t.Errorf("expected SAN DNSNames %v, got %v", expectedConfig.AltNames.DNSNames, cfg.AltNames.DNSNames)
275292
}
276293
}
294+
295+
func makeTestCertConfig(organization []string) *pkiutil.CertConfig {
296+
return &pkiutil.CertConfig{
297+
Config: certutil.Config{
298+
CommonName: "test-common-name",
299+
Organization: organization,
300+
AltNames: certutil.AltNames{
301+
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
302+
DNSNames: []string{"test-domain.space"},
303+
},
304+
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
305+
},
306+
}
307+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestPKICertificateReadWriter(t *testing.T) {
4141
defer os.RemoveAll(dir)
4242

4343
// creates a certificate
44-
cert := writeTestCertificate(t, dir, "test", testCACert, testCAKey)
44+
cert := writeTestCertificate(t, dir, "test", testCACert, testCAKey, testCertOrganization)
4545

4646
// Creates a pkiCertificateReadWriter
4747
pkiReadWriter := newPKICertificateReadWriter(dir, "test")
@@ -145,8 +145,8 @@ func TestKubeconfigReadWriter(t *testing.T) {
145145
}
146146

147147
// writeTestCertificate is a utility for creating a test certificate
148-
func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer) *x509.Certificate {
149-
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, testCertCfg)
148+
func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer, organization []string) *x509.Certificate {
149+
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, makeTestCertConfig(organization))
150150
if err != nil {
151151
t.Fatalf("couldn't generate certificate: %v", err)
152152
}
@@ -164,7 +164,7 @@ func writeTestKubeconfig(t *testing.T, dir, name string, caCert *x509.Certificat
164164
cfg := &pkiutil.CertConfig{
165165
Config: certutil.Config{
166166
CommonName: "test-common-name",
167-
Organization: []string{"sig-cluster-lifecycle"},
167+
Organization: testCertOrganization,
168168
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
169169
AltNames: certutil.AltNames{
170170
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},

cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ func TestWriteKubeConfig(t *testing.T) {
480480

481481
if test.withClientCert {
482482
// checks that kubeconfig files have expected client cert
483-
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myUser")
483+
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myUser", "myOrg")
484484
}
485485

486486
if test.withToken {

cmd/kubeadm/app/util/certs/util.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ func AssertCertificateHasCommonName(t *testing.T, cert *x509.Certificate, common
6060
}
6161

6262
// AssertCertificateHasOrganizations is a utility function for kubeadm testing that asserts if a given certificate has
63-
// the expected Subject.Organization
63+
// and only has the expected Subject.Organization
6464
func AssertCertificateHasOrganizations(t *testing.T, cert *x509.Certificate, organizations ...string) {
65+
if len(cert.Subject.Organization) != len(organizations) {
66+
t.Fatalf("cert contains a different number of Subject.Organization, expected %v, got %v", organizations, cert.Subject.Organization)
67+
}
6568
for _, organization := range organizations {
6669
found := false
6770
for i := range cert.Subject.Organization {

0 commit comments

Comments
 (0)