diff --git a/pkg/kubelet/certificate/bootstrap/bootstrap.go b/pkg/kubelet/certificate/bootstrap/bootstrap.go index 9b7b9b3bff..350e4ed212 100644 --- a/pkg/kubelet/certificate/bootstrap/bootstrap.go +++ b/pkg/kubelet/certificate/bootstrap/bootstrap.go @@ -19,6 +19,7 @@ package bootstrap import ( "fmt" "os" + "path/filepath" "time" "github.com/golang/glog" @@ -35,6 +36,8 @@ import ( "k8s.io/client-go/util/certificate/csr" ) +const tmpPrivateKeyFile = "kubelet-client.key.tmp" + // LoadClientCert requests a client cert for kubelet if the kubeconfigPath file does not exist. // The kubeconfig at bootstrapPath is used to request a client certificate from the API server. // On success, a kubeconfig file referencing the generated key and obtained certificate is written to kubeconfigPath. @@ -75,9 +78,15 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string, } } } + // Cache the private key in a separate file until CSR succeeds. This has to + // be a separate file because store.CurrentPath() points to a symlink + // managed by the store. + privKeyPath := filepath.Join(certDir, tmpPrivateKeyFile) if !verifyKeyData(keyData) { - glog.V(2).Infof("No valid private key found for bootstrapping, creating a new one") - keyData, err = certutil.MakeEllipticPrivateKeyPEM() + glog.V(2).Infof("No valid private key and/or certificate found, reusing existing private key or creating a new one") + // Note: always call LoadOrGenerateKeyFile so that private key is + // reused on next startup if CSR request fails. + keyData, _, err = certutil.LoadOrGenerateKeyFile(privKeyPath) if err != nil { return err } @@ -90,6 +99,9 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string, if _, err := store.Update(certData, keyData); err != nil { return err } + if err := os.Remove(privKeyPath); err != nil && !os.IsNotExist(err) { + glog.V(2).Infof("failed cleaning up private key file %q: %v", privKeyPath, err) + } pemPath := store.CurrentPath() @@ -192,8 +204,6 @@ func verifyKeyData(data []byte) bool { if len(data) == 0 { return false } - if _, err := certutil.ParsePrivateKeyPEM(data); err != nil { - return false - } - return true + _, err := certutil.ParsePrivateKeyPEM(data) + return err == nil } diff --git a/staging/src/k8s.io/client-go/util/cert/io.go b/staging/src/k8s.io/client-go/util/cert/io.go index 374e8cae65..a57bf09d5e 100644 --- a/staging/src/k8s.io/client-go/util/cert/io.go +++ b/staging/src/k8s.io/client-go/util/cert/io.go @@ -88,7 +88,8 @@ func WriteKey(keyPath string, data []byte) error { // can't find one, it will generate a new key and store it there. func LoadOrGenerateKeyFile(keyPath string) (data []byte, wasGenerated bool, err error) { loadedData, err := ioutil.ReadFile(keyPath) - if err == nil { + // Call verifyKeyData to ensure the file wasn't empty/corrupt. + if err == nil && verifyKeyData(loadedData) { return loadedData, false, err } if !os.IsNotExist(err) { @@ -181,3 +182,12 @@ func PublicKeysFromFile(file string) ([]interface{}, error) { } return keys, nil } + +// verifyKeyData returns true if the provided data appears to be a valid private key. +func verifyKeyData(data []byte) bool { + if len(data) == 0 { + return false + } + _, err := ParsePrivateKeyPEM(data) + return err == nil +}