Skip to content

Commit 5771222

Browse files
committed
Add new helper functions for creating keys, kubeconfig and CSR files
Signed-off-by: Richard Wall <[email protected]>
1 parent 21153e7 commit 5771222

File tree

3 files changed

+217
-56
lines changed

3 files changed

+217
-56
lines changed

cmd/kubeadm/app/phases/certs/certlist.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ import (
2828
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
2929
)
3030

31+
const (
32+
errInvalid = "invalid argument"
33+
errExist = "file already exists"
34+
)
35+
3136
type configMutatorsFunc func(*kubeadmapi.InitConfiguration, *pkiutil.CertConfig) error
3237

3338
// KubeadmCert represents a certificate that Kubeadm will create to function properly.
@@ -397,3 +402,63 @@ func setCommonNameToNodeName() configMutatorsFunc {
397402
return nil
398403
}
399404
}
405+
406+
// leafCertificates returns non-CA certificates from the supplied Certificates.
407+
func leafCertificates(c Certificates) (Certificates, error) {
408+
certTree, err := c.AsMap().CertTree()
409+
if err != nil {
410+
return nil, err
411+
}
412+
413+
var out Certificates
414+
for _, leafCertificates := range certTree {
415+
out = append(out, leafCertificates...)
416+
}
417+
return out, nil
418+
}
419+
420+
func createKeyAndCSR(kubeadmConfig *kubeadmapi.InitConfiguration, cert *KubeadmCert) error {
421+
if kubeadmConfig == nil {
422+
return errors.Errorf("%s: kubeadmConfig was nil", errInvalid)
423+
}
424+
if cert == nil {
425+
return errors.Errorf("%s: cert was nil", errInvalid)
426+
}
427+
certDir := kubeadmConfig.CertificatesDir
428+
name := cert.BaseName
429+
if pkiutil.CSROrKeyExist(certDir, name) {
430+
return errors.Errorf("%s: key or CSR %s/%s", errExist, certDir, name)
431+
}
432+
cfg, err := cert.GetConfig(kubeadmConfig)
433+
if err != nil {
434+
return err
435+
}
436+
csr, key, err := pkiutil.NewCSRAndKey(cfg)
437+
if err != nil {
438+
return err
439+
}
440+
err = pkiutil.WriteKey(certDir, name, key)
441+
if err != nil {
442+
return err
443+
}
444+
err = pkiutil.WriteCSR(certDir, name, csr)
445+
if err != nil {
446+
return err
447+
}
448+
return nil
449+
}
450+
451+
// CreateDefaultKeysAndCSRFiles is used in ExternalCA mode to create key files
452+
// and adjacent CSR files.
453+
func CreateDefaultKeysAndCSRFiles(config *kubeadmapi.InitConfiguration) error {
454+
certificates, err := leafCertificates(GetDefaultCertList())
455+
if err != nil {
456+
return err
457+
}
458+
for _, cert := range certificates {
459+
if err := createKeyAndCSR(config, cert); err != nil {
460+
return err
461+
}
462+
}
463+
return nil
464+
}

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

Lines changed: 137 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"path/filepath"
2727

2828
"github.com/pkg/errors"
29+
2930
"k8s.io/client-go/tools/clientcmd"
3031
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
3132
certutil "k8s.io/client-go/util/cert"
@@ -34,9 +35,13 @@ import (
3435
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
3536
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
3637
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
38+
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
3739
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
40+
)
3841

39-
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
42+
const (
43+
errInvalid = "invalid argument"
44+
errExist = "file already exists"
4045
)
4146

4247
// clientCertAuth struct holds info required to build a client certificate to provide authentication info in a kubeconfig object
@@ -103,7 +108,7 @@ func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kub
103108
return err
104109
}
105110

106-
// writes the kubeconfig to disk if it not exists
111+
// writes the kubeconfig to disk if it does not exist
107112
if err = createKubeConfigFileIfNotExists(outDir, kubeConfigFileName, config); err != nil {
108113
return err
109114
}
@@ -113,57 +118,21 @@ func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kub
113118
}
114119

115120
// getKubeConfigSpecs returns all KubeConfigSpecs actualized to the context of the current InitConfiguration
116-
// NB. this methods holds the information about how kubeadm creates kubeconfig files.
121+
// NB. this method holds the information about how kubeadm creates kubeconfig files.
117122
func getKubeConfigSpecs(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
118-
119123
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
120124
if err != nil {
121125
return nil, errors.Wrap(err, "couldn't create a kubeconfig; the CA files couldn't be loaded")
122126
}
123-
124-
controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
127+
configs, err := getKubeConfigSpecsBase(cfg)
125128
if err != nil {
126129
return nil, err
127130
}
128-
129-
var kubeConfigSpec = map[string]*kubeConfigSpec{
130-
kubeadmconstants.AdminKubeConfigFileName: {
131-
CACert: caCert,
132-
APIServer: controlPlaneEndpoint,
133-
ClientName: "kubernetes-admin",
134-
ClientCertAuth: &clientCertAuth{
135-
CAKey: caKey,
136-
Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
137-
},
138-
},
139-
kubeadmconstants.KubeletKubeConfigFileName: {
140-
CACert: caCert,
141-
APIServer: controlPlaneEndpoint,
142-
ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name),
143-
ClientCertAuth: &clientCertAuth{
144-
CAKey: caKey,
145-
Organizations: []string{kubeadmconstants.NodesGroup},
146-
},
147-
},
148-
kubeadmconstants.ControllerManagerKubeConfigFileName: {
149-
CACert: caCert,
150-
APIServer: controlPlaneEndpoint,
151-
ClientName: kubeadmconstants.ControllerManagerUser,
152-
ClientCertAuth: &clientCertAuth{
153-
CAKey: caKey,
154-
},
155-
},
156-
kubeadmconstants.SchedulerKubeConfigFileName: {
157-
CACert: caCert,
158-
APIServer: controlPlaneEndpoint,
159-
ClientName: kubeadmconstants.SchedulerUser,
160-
ClientCertAuth: &clientCertAuth{
161-
CAKey: caKey,
162-
},
163-
},
131+
for _, spec := range configs {
132+
spec.CACert = caCert
133+
spec.ClientCertAuth.CAKey = caKey
164134
}
165-
166-
return kubeConfigSpec, nil
135+
return configs, nil
167136
}
168137

169138
// buildKubeConfigFromSpec creates a kubeconfig object for the given kubeConfigSpec
@@ -182,13 +151,8 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc
182151
}
183152

184153
// otherwise, create a client certs
185-
clientCertConfig := pkiutil.CertConfig{
186-
Config: certutil.Config{
187-
CommonName: spec.ClientName,
188-
Organization: spec.ClientCertAuth.Organizations,
189-
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
190-
},
191-
}
154+
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
155+
192156
clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig)
193157
if err != nil {
194158
return nil, errors.Wrapf(err, "failure while creating %s client certificate", spec.ClientName)
@@ -209,6 +173,16 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc
209173
), nil
210174
}
211175

176+
func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec) pkiutil.CertConfig {
177+
return pkiutil.CertConfig{
178+
Config: certutil.Config{
179+
CommonName: spec.ClientName,
180+
Organization: spec.ClientCertAuth.Organizations,
181+
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
182+
},
183+
}
184+
}
185+
212186
// validateKubeConfig check if the kubeconfig file exist and has the expected CA and server URL
213187
func validateKubeConfig(outDir, filename string, config *clientcmdapi.Config) error {
214188
kubeConfigFilePath := filepath.Join(outDir, filename)
@@ -386,3 +360,115 @@ func ValidateKubeconfigsForExternalCA(outDir string, cfg *kubeadmapi.InitConfigu
386360
}
387361
return nil
388362
}
363+
364+
func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
365+
apiServer, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
366+
if err != nil {
367+
return nil, err
368+
}
369+
return map[string]*kubeConfigSpec{
370+
kubeadmconstants.AdminKubeConfigFileName: {
371+
APIServer: apiServer,
372+
ClientName: "kubernetes-admin",
373+
ClientCertAuth: &clientCertAuth{
374+
Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
375+
},
376+
},
377+
kubeadmconstants.KubeletKubeConfigFileName: {
378+
APIServer: apiServer,
379+
ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name),
380+
ClientCertAuth: &clientCertAuth{
381+
Organizations: []string{kubeadmconstants.NodesGroup},
382+
},
383+
},
384+
kubeadmconstants.ControllerManagerKubeConfigFileName: {
385+
APIServer: apiServer,
386+
ClientName: kubeadmconstants.ControllerManagerUser,
387+
ClientCertAuth: &clientCertAuth{},
388+
},
389+
kubeadmconstants.SchedulerKubeConfigFileName: {
390+
APIServer: apiServer,
391+
ClientName: kubeadmconstants.SchedulerUser,
392+
ClientCertAuth: &clientCertAuth{},
393+
},
394+
}, nil
395+
}
396+
397+
func createKubeConfigAndCSR(kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration, name string, spec *kubeConfigSpec) error {
398+
if kubeConfigDir == "" {
399+
return errors.Errorf("%s: kubeConfigDir was empty", errInvalid)
400+
}
401+
if kubeadmConfig == nil {
402+
return errors.Errorf("%s: kubeadmConfig was nil", errInvalid)
403+
}
404+
if name == "" {
405+
return errors.Errorf("%s: name was empty", errInvalid)
406+
}
407+
if spec == nil {
408+
return errors.Errorf("%s: spec was nil", errInvalid)
409+
}
410+
kubeConfigPath := filepath.Join(kubeConfigDir, name)
411+
if _, err := os.Stat(kubeConfigPath); err == nil {
412+
return errors.Errorf("%s: kube config: %s", errExist, kubeConfigPath)
413+
} else if !os.IsNotExist(err) {
414+
return errors.Wrapf(err, "unexpected error while checking if file exists: %s", kubeConfigPath)
415+
}
416+
if pkiutil.CSROrKeyExist(kubeConfigDir, name) {
417+
return errors.Errorf("%s: csr: %s", errExist, kubeConfigPath)
418+
}
419+
420+
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
421+
422+
clientKey, err := pkiutil.NewPrivateKey(clientCertConfig.PublicKeyAlgorithm)
423+
if err != nil {
424+
return err
425+
}
426+
clientCSR, err := pkiutil.NewCSR(clientCertConfig, clientKey)
427+
if err != nil {
428+
return err
429+
}
430+
431+
encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(clientKey)
432+
if err != nil {
433+
return err
434+
}
435+
436+
var (
437+
emptyCACert []byte
438+
emptyClientCert []byte
439+
)
440+
441+
// create a kubeconfig with the client certs
442+
config := kubeconfigutil.CreateWithCerts(
443+
spec.APIServer,
444+
kubeadmConfig.ClusterName,
445+
spec.ClientName,
446+
emptyCACert,
447+
encodedClientKey,
448+
emptyClientCert,
449+
)
450+
451+
if err := kubeconfigutil.WriteToDisk(kubeConfigPath, config); err != nil {
452+
return err
453+
}
454+
// Write CSR to disk
455+
if err := pkiutil.WriteCSR(kubeConfigDir, name, clientCSR); err != nil {
456+
return err
457+
}
458+
return nil
459+
}
460+
461+
// CreateDefaultKubeConfigsAndCSRFiles is used in ExternalCA mode to create
462+
// kubeconfig files and adjacent CSR files.
463+
func CreateDefaultKubeConfigsAndCSRFiles(kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration) error {
464+
kubeConfigs, err := getKubeConfigSpecsBase(kubeadmConfig)
465+
if err != nil {
466+
return err
467+
}
468+
for name, spec := range kubeConfigs {
469+
if err := createKubeConfigAndCSR(kubeConfigDir, kubeadmConfig, name, spec); err != nil {
470+
return err
471+
}
472+
}
473+
return nil
474+
}

cmd/kubeadm/app/util/pkiutil/pki_helpers.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,16 +290,14 @@ func TryLoadKeyFromDisk(pkiPath, name string) (crypto.Signer, error) {
290290

291291
// TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk
292292
func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, crypto.Signer, error) {
293-
csrPath := pathForCSR(pkiPath, name)
294-
295-
csr, err := CertificateRequestFromFile(csrPath)
293+
csr, err := TryLoadCSRFromDisk(pkiPath, name)
296294
if err != nil {
297-
return nil, nil, errors.Wrapf(err, "couldn't load the certificate request %s", csrPath)
295+
return nil, nil, errors.Wrap(err, "could not load CSR file")
298296
}
299297

300298
key, err := TryLoadKeyFromDisk(pkiPath, name)
301299
if err != nil {
302-
return nil, nil, errors.Wrap(err, "couldn't load key file")
300+
return nil, nil, errors.Wrap(err, "could not load key file")
303301
}
304302

305303
return csr, key, nil
@@ -334,6 +332,18 @@ func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rs
334332
return k, p, nil
335333
}
336334

335+
// TryLoadCSRFromDisk tries to load the CSR from the disk
336+
func TryLoadCSRFromDisk(pkiPath, name string) (*x509.CertificateRequest, error) {
337+
csrPath := pathForCSR(pkiPath, name)
338+
339+
csr, err := CertificateRequestFromFile(csrPath)
340+
if err != nil {
341+
return nil, errors.Wrapf(err, "could not load the CSR %s", csrPath)
342+
}
343+
344+
return csr, nil
345+
}
346+
337347
// PathsForCertAndKey returns the paths for the certificate and key given the path and basename.
338348
func PathsForCertAndKey(pkiPath, name string) (string, string) {
339349
return pathForCert(pkiPath, name), pathForKey(pkiPath, name)

0 commit comments

Comments
 (0)