Unverified Commit dc9261bc authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #71174 from smarterclayton/debug_kubeadm

Restore bootstrap in the background with fix to preserve kubeadm behavior
parents eab977f6 fde87329
...@@ -8,8 +8,22 @@ load( ...@@ -8,8 +8,22 @@ load(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["server_test.go"], srcs = [
"server_bootstrap_test.go",
"server_test.go",
],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//vendor/github.com/cloudflare/cfssl/config:go_default_library",
"//vendor/github.com/cloudflare/cfssl/signer:go_default_library",
"//vendor/github.com/cloudflare/cfssl/signer/local:go_default_library",
],
) )
go_library( go_library(
...@@ -113,6 +127,7 @@ go_library( ...@@ -113,6 +127,7 @@ go_library(
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/authentication/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/authorization/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/authorization/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
......
...@@ -47,13 +47,65 @@ import ( ...@@ -47,13 +47,65 @@ import (
const tmpPrivateKeyFile = "kubelet-client.key.tmp" const tmpPrivateKeyFile = "kubelet-client.key.tmp"
// LoadClientConfig tries to load the appropriate client config for retrieving certs and for use by users.
// If bootstrapPath is empty, only kubeconfigPath is checked. If bootstrap path is set and the contents
// of kubeconfigPath are valid, both certConfig and userConfig will point to that file. Otherwise the
// kubeconfigPath on disk is populated based on bootstrapPath but pointing to the location of the client cert
// in certDir. This preserves the historical behavior of bootstrapping where on subsequent restarts the
// most recent client cert is used to request new client certs instead of the initial token.
func LoadClientConfig(kubeconfigPath, bootstrapPath, certDir string) (certConfig, userConfig *restclient.Config, err error) {
if len(bootstrapPath) == 0 {
clientConfig, err := loadRESTClientConfig(kubeconfigPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to load kubeconfig: %v", err)
}
klog.V(2).Infof("No bootstrapping requested, will use kubeconfig")
return clientConfig, restclient.CopyConfig(clientConfig), nil
}
store, err := certificate.NewFileStore("kubelet-client", certDir, certDir, "", "")
if err != nil {
return nil, nil, fmt.Errorf("unable to build bootstrap cert store")
}
ok, err := isClientConfigStillValid(kubeconfigPath)
if err != nil {
return nil, nil, err
}
// use the current client config
if ok {
clientConfig, err := loadRESTClientConfig(kubeconfigPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to load kubeconfig: %v", err)
}
klog.V(2).Infof("Current kubeconfig file contents are still valid, no bootstrap necessary")
return clientConfig, restclient.CopyConfig(clientConfig), nil
}
bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
}
clientConfig := restclient.AnonymousClientConfig(bootstrapClientConfig)
pemPath := store.CurrentPath()
clientConfig.KeyFile = pemPath
clientConfig.CertFile = pemPath
if err := writeKubeconfigFromBootstrapping(clientConfig, kubeconfigPath, pemPath); err != nil {
return nil, nil, err
}
klog.V(2).Infof("Use the bootstrap credentials to request a cert, and set kubeconfig to point to the certificate dir")
return bootstrapClientConfig, clientConfig, nil
}
// LoadClientCert requests a client cert for kubelet if the kubeconfigPath file does not exist. // 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. // 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. // On success, a kubeconfig file referencing the generated key and obtained certificate is written to kubeconfigPath.
// The certificate and key file are stored in certDir. // The certificate and key file are stored in certDir.
func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string, nodeName types.NodeName) error { func LoadClientCert(kubeconfigPath, bootstrapPath, certDir string, nodeName types.NodeName) error {
// Short-circuit if the kubeconfig file exists and is valid. // Short-circuit if the kubeconfig file exists and is valid.
ok, err := verifyBootstrapClientConfig(kubeconfigPath) ok, err := isClientConfigStillValid(kubeconfigPath)
if err != nil { if err != nil {
return err return err
} }
...@@ -117,8 +169,10 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string, ...@@ -117,8 +169,10 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string,
klog.V(2).Infof("failed cleaning up private key file %q: %v", privKeyPath, err) klog.V(2).Infof("failed cleaning up private key file %q: %v", privKeyPath, err)
} }
pemPath := store.CurrentPath() return writeKubeconfigFromBootstrapping(bootstrapClientConfig, kubeconfigPath, store.CurrentPath())
}
func writeKubeconfigFromBootstrapping(bootstrapClientConfig *restclient.Config, kubeconfigPath, pemPath string) error {
// Get the CA data from the bootstrap client config. // Get the CA data from the bootstrap client config.
caFile, caData := bootstrapClientConfig.CAFile, []byte{} caFile, caData := bootstrapClientConfig.CAFile, []byte{}
if len(caFile) == 0 { if len(caFile) == 0 {
...@@ -168,10 +222,10 @@ func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) { ...@@ -168,10 +222,10 @@ func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) {
).ClientConfig() ).ClientConfig()
} }
// verifyBootstrapClientConfig checks the provided kubeconfig to see if it has a valid // isClientConfigStillValid checks the provided kubeconfig to see if it has a valid
// client certificate. It returns true if the kubeconfig is valid, or an error if bootstrapping // client certificate. It returns true if the kubeconfig is valid, or an error if bootstrapping
// should stop immediately. // should stop immediately.
func verifyBootstrapClientConfig(kubeconfigPath string) (bool, error) { func isClientConfigStillValid(kubeconfigPath string) (bool, error) {
_, err := os.Stat(kubeconfigPath) _, err := os.Stat(kubeconfigPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false, nil return false, nil
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package certificate package certificate
import ( import (
"crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"fmt" "fmt"
...@@ -29,7 +30,7 @@ import ( ...@@ -29,7 +30,7 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
clientcertificates "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/util/certificate" "k8s.io/client-go/util/certificate"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/metrics" "k8s.io/kubernetes/pkg/kubelet/metrics"
...@@ -38,7 +39,7 @@ import ( ...@@ -38,7 +39,7 @@ import (
// NewKubeletServerCertificateManager creates a certificate manager for the kubelet when retrieving a server certificate // NewKubeletServerCertificateManager creates a certificate manager for the kubelet when retrieving a server certificate
// or returns an error. // or returns an error.
func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, getAddresses func() []v1.NodeAddress, certDirectory string) (certificate.Manager, error) { func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, getAddresses func() []v1.NodeAddress, certDirectory string) (certificate.Manager, error) {
var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface var certSigningRequestClient certificatesclient.CertificateSigningRequestInterface
if kubeClient != nil && kubeClient.CertificatesV1beta1() != nil { if kubeClient != nil && kubeClient.CertificatesV1beta1() != nil {
certSigningRequestClient = kubeClient.CertificatesV1beta1().CertificateSigningRequests() certSigningRequestClient = kubeClient.CertificatesV1beta1().CertificateSigningRequests()
} }
...@@ -78,8 +79,10 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg ...@@ -78,8 +79,10 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
} }
m, err := certificate.NewManager(&certificate.Config{ m, err := certificate.NewManager(&certificate.Config{
CertificateSigningRequestClient: certSigningRequestClient, ClientFn: func(current *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
GetTemplate: getTemplate, return certSigningRequestClient, nil
},
GetTemplate: getTemplate,
Usages: []certificates.KeyUsage{ Usages: []certificates.KeyUsage{
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3 // https://tools.ietf.org/html/rfc5280#section-4.2.1.3
// //
...@@ -142,10 +145,18 @@ func addressesToHostnamesAndIPs(addresses []v1.NodeAddress) (dnsNames []string, ...@@ -142,10 +145,18 @@ func addressesToHostnamesAndIPs(addresses []v1.NodeAddress) (dnsNames []string,
} }
// NewKubeletClientCertificateManager sets up a certificate manager without a // NewKubeletClientCertificateManager sets up a certificate manager without a
// client that can be used to sign new certificates (or rotate). It answers with // client that can be used to sign new certificates (or rotate). If a CSR
// whatever certificate it is initialized with. If a CSR client is set later, it // client is set later, it may begin rotating/renewing the client cert.
// may begin rotating/renewing the client cert func NewKubeletClientCertificateManager(
func NewKubeletClientCertificateManager(certDirectory string, nodeName types.NodeName, certData []byte, keyData []byte, certFile string, keyFile string) (certificate.Manager, error) { certDirectory string,
nodeName types.NodeName,
bootstrapCertData []byte,
bootstrapKeyData []byte,
certFile string,
keyFile string,
clientFn certificate.CSRClientFunc,
) (certificate.Manager, error) {
certificateStore, err := certificate.NewFileStore( certificateStore, err := certificate.NewFileStore(
"kubelet-client", "kubelet-client",
certDirectory, certDirectory,
...@@ -163,9 +174,10 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod ...@@ -163,9 +174,10 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod
Help: "Gauge of the lifetime of a certificate. The value is the date the certificate will expire in seconds since January 1, 1970 UTC.", Help: "Gauge of the lifetime of a certificate. The value is the date the certificate will expire in seconds since January 1, 1970 UTC.",
}, },
) )
prometheus.MustRegister(certificateExpiration) prometheus.Register(certificateExpiration)
m, err := certificate.NewManager(&certificate.Config{ m, err := certificate.NewManager(&certificate.Config{
ClientFn: clientFn,
Template: &x509.CertificateRequest{ Template: &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName), CommonName: fmt.Sprintf("system:node:%s", nodeName),
...@@ -187,10 +199,16 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod ...@@ -187,10 +199,16 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod
// authenticate itself to the TLS server. // authenticate itself to the TLS server.
certificates.UsageClientAuth, certificates.UsageClientAuth,
}, },
CertificateStore: certificateStore,
BootstrapCertificatePEM: certData, // For backwards compatibility, the kubelet supports the ability to
BootstrapKeyPEM: keyData, // provide a higher privileged certificate as initial data that will
CertificateExpiration: certificateExpiration, // then be rotated immediately. This code path is used by kubeadm on
// the masters.
BootstrapCertificatePEM: bootstrapCertData,
BootstrapKeyPEM: bootstrapKeyData,
CertificateStore: certificateStore,
CertificateExpiration: certificateExpiration,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize client certificate manager: %v", err) return nil, fmt.Errorf("failed to initialize client certificate manager: %v", err)
......
...@@ -124,7 +124,9 @@ func (f *fakeManager) SetCertificateSigningRequestClient(certificatesclient.Cert ...@@ -124,7 +124,9 @@ func (f *fakeManager) SetCertificateSigningRequestClient(certificatesclient.Cert
func (f *fakeManager) ServerHealthy() bool { return f.healthy } func (f *fakeManager) ServerHealthy() bool { return f.healthy }
func (f *fakeManager) Start() {} func (f *fakeManager) Start() {}
func (f *fakeManager) Stop() {}
func (f *fakeManager) RotateCerts() (bool, error) { return false, nil }
func (f *fakeManager) Current() *tls.Certificate { func (f *fakeManager) Current() *tls.Certificate {
if val := f.cert.Load(); val != nil { if val := f.cert.Load(); val != nil {
......
...@@ -48,11 +48,10 @@ var certificateWaitBackoff = wait.Backoff{Duration: 30 * time.Second, Steps: 4, ...@@ -48,11 +48,10 @@ var certificateWaitBackoff = wait.Backoff{Duration: 30 * time.Second, Steps: 4,
// manager. In the background it communicates with the API server to get new // manager. In the background it communicates with the API server to get new
// certificates for certificates about to expire. // certificates for certificates about to expire.
type Manager interface { type Manager interface {
// CertificateSigningRequestClient sets the client interface that is used for
// signing new certificates generated as part of rotation.
SetCertificateSigningRequestClient(certificatesclient.CertificateSigningRequestInterface) error
// Start the API server status sync loop. // Start the API server status sync loop.
Start() Start()
// Stop the cert manager loop.
Stop()
// Current returns the currently selected certificate from the // Current returns the currently selected certificate from the
// certificate manager, as well as the associated certificate and key data // certificate manager, as well as the associated certificate and key data
// in PEM format. // in PEM format.
...@@ -67,11 +66,11 @@ type Manager interface { ...@@ -67,11 +66,11 @@ type Manager interface {
// Config is the set of configuration parameters available for a new Manager. // Config is the set of configuration parameters available for a new Manager.
type Config struct { type Config struct {
// CertificateSigningRequestClient will be used for signing new certificate // ClientFn will be used to create a client for
// requests generated when a key rotation occurs. It must be set either at // signing new certificate requests generated when a key rotation occurs.
// initialization or by using CertificateSigningRequestClient before // It must be set at initialization. The function will never be invoked
// Manager.Start() is called. // in parallel. It is passed the current client certificate if one exists.
CertificateSigningRequestClient certificatesclient.CertificateSigningRequestInterface ClientFn CSRClientFunc
// Template is the CertificateRequest that will be used as a template for // Template is the CertificateRequest that will be used as a template for
// generating certificate signing requests for all new keys generated as // generating certificate signing requests for all new keys generated as
// part of rotation. It follows the same rules as the template parameter of // part of rotation. It follows the same rules as the template parameter of
...@@ -141,21 +140,34 @@ type Gauge interface { ...@@ -141,21 +140,34 @@ type Gauge interface {
// NoCertKeyError indicates there is no cert/key currently available. // NoCertKeyError indicates there is no cert/key currently available.
type NoCertKeyError string type NoCertKeyError string
// CSRClientFunc returns a new client for requesting CSRs. It passes the
// current certificate if one is available and valid.
type CSRClientFunc func(current *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error)
func (e *NoCertKeyError) Error() string { return string(*e) } func (e *NoCertKeyError) Error() string { return string(*e) }
type manager struct { type manager struct {
certSigningRequestClient certificatesclient.CertificateSigningRequestInterface getTemplate func() *x509.CertificateRequest
getTemplate func() *x509.CertificateRequest lastRequestLock sync.Mutex
lastRequestLock sync.Mutex lastRequest *x509.CertificateRequest
lastRequest *x509.CertificateRequest dynamicTemplate bool
dynamicTemplate bool usages []certificates.KeyUsage
usages []certificates.KeyUsage forceRotation bool
certStore Store
certAccessLock sync.RWMutex certStore Store
cert *tls.Certificate
forceRotation bool certificateExpiration Gauge
certificateExpiration Gauge
serverHealth bool // the following variables must only be accessed under certAccessLock
certAccessLock sync.RWMutex
cert *tls.Certificate
serverHealth bool
// the clientFn must only be accessed under the clientAccessLock
clientAccessLock sync.Mutex
clientFn CSRClientFunc
stopCh chan struct{}
stopped bool
} }
// NewManager returns a new certificate manager. A certificate manager is // NewManager returns a new certificate manager. A certificate manager is
...@@ -176,14 +188,15 @@ func NewManager(config *Config) (Manager, error) { ...@@ -176,14 +188,15 @@ func NewManager(config *Config) (Manager, error) {
} }
m := manager{ m := manager{
certSigningRequestClient: config.CertificateSigningRequestClient, stopCh: make(chan struct{}),
getTemplate: getTemplate, clientFn: config.ClientFn,
dynamicTemplate: config.GetTemplate != nil, getTemplate: getTemplate,
usages: config.Usages, dynamicTemplate: config.GetTemplate != nil,
certStore: config.CertificateStore, usages: config.Usages,
cert: cert, certStore: config.CertificateStore,
forceRotation: forceRotation, cert: cert,
certificateExpiration: config.CertificateExpiration, forceRotation: forceRotation,
certificateExpiration: config.CertificateExpiration,
} }
return &m, nil return &m, nil
...@@ -192,10 +205,14 @@ func NewManager(config *Config) (Manager, error) { ...@@ -192,10 +205,14 @@ func NewManager(config *Config) (Manager, error) {
// Current returns the currently selected certificate from the certificate // Current returns the currently selected certificate from the certificate
// manager. This can be nil if the manager was initialized without a // manager. This can be nil if the manager was initialized without a
// certificate and has not yet received one from the // certificate and has not yet received one from the
// CertificateSigningRequestClient. // CertificateSigningRequestClient, or if the current cert has expired.
func (m *manager) Current() *tls.Certificate { func (m *manager) Current() *tls.Certificate {
m.certAccessLock.RLock() m.certAccessLock.RLock()
defer m.certAccessLock.RUnlock() defer m.certAccessLock.RUnlock()
if m.cert != nil && m.cert.Leaf != nil && time.Now().After(m.cert.Leaf.NotAfter) {
klog.V(2).Infof("Current certificate is expired.")
return nil
}
return m.cert return m.cert
} }
...@@ -207,18 +224,15 @@ func (m *manager) ServerHealthy() bool { ...@@ -207,18 +224,15 @@ func (m *manager) ServerHealthy() bool {
return m.serverHealth return m.serverHealth
} }
// SetCertificateSigningRequestClient sets the client interface that is used // Stop terminates the manager.
// for signing new certificates generated as part of rotation. It must be func (m *manager) Stop() {
// called before Start() and can not be used to change the m.clientAccessLock.Lock()
// CertificateSigningRequestClient that has already been set. This method is to defer m.clientAccessLock.Unlock()
// support the one specific scenario where the CertificateSigningRequestClient if m.stopped {
// uses the CertificateManager. return
func (m *manager) SetCertificateSigningRequestClient(certSigningRequestClient certificatesclient.CertificateSigningRequestInterface) error {
if m.certSigningRequestClient == nil {
m.certSigningRequestClient = certSigningRequestClient
return nil
} }
return fmt.Errorf("property CertificateSigningRequestClient is already set") close(m.stopCh)
m.stopped = true
} }
// Start will start the background work of rotating the certificates. // Start will start the background work of rotating the certificates.
...@@ -226,7 +240,7 @@ func (m *manager) Start() { ...@@ -226,7 +240,7 @@ func (m *manager) Start() {
// Certificate rotation depends on access to the API server certificate // Certificate rotation depends on access to the API server certificate
// signing API, so don't start the certificate manager if we don't have a // signing API, so don't start the certificate manager if we don't have a
// client. // client.
if m.certSigningRequestClient == nil { if m.clientFn == nil {
klog.V(2).Infof("Certificate rotation is not enabled, no connection to the apiserver.") klog.V(2).Infof("Certificate rotation is not enabled, no connection to the apiserver.")
return return
} }
...@@ -234,7 +248,7 @@ func (m *manager) Start() { ...@@ -234,7 +248,7 @@ func (m *manager) Start() {
klog.V(2).Infof("Certificate rotation is enabled.") klog.V(2).Infof("Certificate rotation is enabled.")
templateChanged := make(chan struct{}) templateChanged := make(chan struct{})
go wait.Forever(func() { go wait.Until(func() {
deadline := m.nextRotationDeadline() deadline := m.nextRotationDeadline()
if sleepInterval := deadline.Sub(time.Now()); sleepInterval > 0 { if sleepInterval := deadline.Sub(time.Now()); sleepInterval > 0 {
klog.V(2).Infof("Waiting %v for next certificate rotation", sleepInterval) klog.V(2).Infof("Waiting %v for next certificate rotation", sleepInterval)
...@@ -269,17 +283,17 @@ func (m *manager) Start() { ...@@ -269,17 +283,17 @@ func (m *manager) Start() {
utilruntime.HandleError(fmt.Errorf("Reached backoff limit, still unable to rotate certs: %v", err)) utilruntime.HandleError(fmt.Errorf("Reached backoff limit, still unable to rotate certs: %v", err))
wait.PollInfinite(32*time.Second, m.rotateCerts) wait.PollInfinite(32*time.Second, m.rotateCerts)
} }
}, time.Second) }, time.Second, m.stopCh)
if m.dynamicTemplate { if m.dynamicTemplate {
go wait.Forever(func() { go wait.Until(func() {
// check if the current template matches what we last requested // check if the current template matches what we last requested
if !m.certSatisfiesTemplate() && !reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) { if !m.certSatisfiesTemplate() && !reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) {
// if the template is different, queue up an interrupt of the rotation deadline loop. // if the template is different, queue up an interrupt of the rotation deadline loop.
// if we've requested a CSR that matches the new template by the time the interrupt is handled, the interrupt is disregarded. // if we've requested a CSR that matches the new template by the time the interrupt is handled, the interrupt is disregarded.
templateChanged <- struct{}{} templateChanged <- struct{}{}
} }
}, time.Second) }, time.Second, m.stopCh)
} }
} }
...@@ -327,11 +341,26 @@ func getCurrentCertificateOrBootstrap( ...@@ -327,11 +341,26 @@ func getCurrentCertificateOrBootstrap(
return &bootstrapCert, true, nil return &bootstrapCert, true, nil
} }
func (m *manager) getClient() (certificatesclient.CertificateSigningRequestInterface, error) {
current := m.Current()
m.clientAccessLock.Lock()
defer m.clientAccessLock.Unlock()
return m.clientFn(current)
}
// RotateCerts is exposed for testing only and is not a part of the public interface.
// Returns true if it changed the cert, false otherwise. Error is only returned in
// exceptional cases.
func (m *manager) RotateCerts() (bool, error) {
return m.rotateCerts()
}
// rotateCerts attempts to request a client cert from the server, wait a reasonable // rotateCerts attempts to request a client cert from the server, wait a reasonable
// period of time for it to be signed, and then update the cert on disk. If it cannot // period of time for it to be signed, and then update the cert on disk. If it cannot
// retrieve a cert, it will return false. It will only return error in exceptional cases. // retrieve a cert, it will return false. It will only return error in exceptional cases.
// This method also keeps track of "server health" by interpreting the responses it gets // This method also keeps track of "server health" by interpreting the responses it gets
// from the server on the various calls it makes. // from the server on the various calls it makes.
// TODO: return errors, have callers handle and log them correctly
func (m *manager) rotateCerts() (bool, error) { func (m *manager) rotateCerts() (bool, error) {
klog.V(2).Infof("Rotating certificates") klog.V(2).Infof("Rotating certificates")
...@@ -341,9 +370,16 @@ func (m *manager) rotateCerts() (bool, error) { ...@@ -341,9 +370,16 @@ func (m *manager) rotateCerts() (bool, error) {
return false, nil return false, nil
} }
// request the client each time
client, err := m.getClient()
if err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to load a client to request certificates: %v", err))
return false, nil
}
// Call the Certificate Signing Request API to get a certificate for the // Call the Certificate Signing Request API to get a certificate for the
// new private key. // new private key.
req, err := csr.RequestCertificate(m.certSigningRequestClient, csrPEM, "", m.usages, privateKey) req, err := csr.RequestCertificate(client, csrPEM, "", m.usages, privateKey)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf("Failed while requesting a signed certificate from the master: %v", err)) utilruntime.HandleError(fmt.Errorf("Failed while requesting a signed certificate from the master: %v", err))
return false, m.updateServerError(err) return false, m.updateServerError(err)
...@@ -359,7 +395,7 @@ func (m *manager) rotateCerts() (bool, error) { ...@@ -359,7 +395,7 @@ func (m *manager) rotateCerts() (bool, error) {
var crtPEM []byte var crtPEM []byte
watchDuration := time.Minute watchDuration := time.Minute
if err := wait.ExponentialBackoff(certificateWaitBackoff, func() (bool, error) { if err := wait.ExponentialBackoff(certificateWaitBackoff, func() (bool, error) {
data, err := csr.WaitForCertificate(m.certSigningRequestClient, req, watchDuration) data, err := csr.WaitForCertificate(client, req, watchDuration)
switch { switch {
case err == nil: case err == nil:
crtPEM = data crtPEM = data
......
...@@ -60,6 +60,23 @@ iQIgZX08DA8VfvcA5/Xj1Zjdey9FVY6POLXen6RPiabE97UCICp6eUW7ht+2jjar ...@@ -60,6 +60,23 @@ iQIgZX08DA8VfvcA5/Xj1Zjdey9FVY6POLXen6RPiabE97UCICp6eUW7ht+2jjar
e35EltCRCjoejRHTuN9TC0uCoVipAiAXaJIx/Q47vGwiw6Y8KXsNU6y54gTbOSxX e35EltCRCjoejRHTuN9TC0uCoVipAiAXaJIx/Q47vGwiw6Y8KXsNU6y54gTbOSxX
54LzHNk/+Q== 54LzHNk/+Q==
-----END RSA PRIVATE KEY-----`) -----END RSA PRIVATE KEY-----`)
var expiredStoreCertData = newCertificateData(`-----BEGIN CERTIFICATE-----
MIIBFzCBwgIJALhygXnxXmN1MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCGhv
c3QtMTIzMB4XDTE4MTEwNDIzNTc1NFoXDTE4MTEwNTIzNTc1NFowEzERMA8GA1UE
AwwIaG9zdC0xMjMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAtBMa7NWpv3BVlKTC
PGO/LEsguKqWHBtKzweMY2CVtAL1rQm913huhxF9w+ai76KQ3MHK5IVnLJjYYA5M
zP2H5QIDAQABMA0GCSqGSIb3DQEBCwUAA0EAN2DPFUtCzqnidL+5nh+46Sk6dkMI
T5DD11UuuIjZusKvThsHKVCIsyJ2bDo7cTbI+/nklLRP+FcC2wESFUgXbA==
-----END CERTIFICATE-----`, `-----BEGIN RSA PRIVATE KEY-----
MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAtBMa7NWpv3BVlKTC
PGO/LEsguKqWHBtKzweMY2CVtAL1rQm913huhxF9w+ai76KQ3MHK5IVnLJjYYA5M
zP2H5QIDAQABAkAS9BfXab3OKpK3bIgNNyp+DQJKrZnTJ4Q+OjsqkpXvNltPJosf
G8GsiKu/vAt4HGqI3eU77NvRI+mL4MnHRmXBAiEA3qM4FAtKSRBbcJzPxxLEUSwg
XSCcosCktbkXvpYrS30CIQDPDxgqlwDEJQ0uKuHkZI38/SPWWqfUmkecwlbpXABK
iQIgZX08DA8VfvcA5/Xj1Zjdey9FVY6POLXen6RPiabE97UCICp6eUW7ht+2jjar
e35EltCRCjoejRHTuN9TC0uCoVipAiAXaJIx/Q47vGwiw6Y8KXsNU6y54gTbOSxX
54LzHNk/+Q==
-----END RSA PRIVATE KEY-----`)
var bootstrapCertData = newCertificateData( var bootstrapCertData = newCertificateData(
`-----BEGIN CERTIFICATE----- `-----BEGIN CERTIFICATE-----
MIICRzCCAfGgAwIBAgIJANXr+UzRFq4TMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV MIICRzCCAfGgAwIBAgIJANXr+UzRFq4TMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
...@@ -388,8 +405,8 @@ func TestRotateCertCreateCSRError(t *testing.T) { ...@@ -388,8 +405,8 @@ func TestRotateCertCreateCSRError(t *testing.T) {
}, },
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} }, getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
usages: []certificates.KeyUsage{}, usages: []certificates.KeyUsage{},
certSigningRequestClient: fakeClient{ clientFn: func(_ *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
failureType: createError, return fakeClient{failureType: createError}, nil
}, },
} }
...@@ -411,8 +428,8 @@ func TestRotateCertWaitingForResultError(t *testing.T) { ...@@ -411,8 +428,8 @@ func TestRotateCertWaitingForResultError(t *testing.T) {
}, },
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} }, getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
usages: []certificates.KeyUsage{}, usages: []certificates.KeyUsage{},
certSigningRequestClient: fakeClient{ clientFn: func(_ *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
failureType: watchError, return fakeClient{failureType: watchError}, nil
}, },
} }
...@@ -598,6 +615,14 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) { ...@@ -598,6 +615,14 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) {
expectedCertBeforeStart: storeCertData, expectedCertBeforeStart: storeCertData,
expectedCertAfterStart: storeCertData, expectedCertAfterStart: storeCertData,
}, },
{
description: "Current certificate expired, no bootstrap certificate",
storeCert: expiredStoreCertData,
bootstrapCert: nilCertificate,
apiCert: apiServerCertData,
expectedCertBeforeStart: nil,
expectedCertAfterStart: apiServerCertData,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
...@@ -621,19 +646,25 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) { ...@@ -621,19 +646,25 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) {
CertificateStore: certificateStore, CertificateStore: certificateStore,
BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM, BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM,
BootstrapKeyPEM: tc.bootstrapCert.keyPEM, BootstrapKeyPEM: tc.bootstrapCert.keyPEM,
ClientFn: func(_ *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
return &fakeClient{
certificatePEM: tc.apiCert.certificatePEM,
}, nil
},
}) })
if err != nil { if err != nil {
t.Errorf("Got %v, wanted no error.", err) t.Errorf("Got %v, wanted no error.", err)
} }
certificate := certificateManager.Current() certificate := certificateManager.Current()
if !certificatesEqual(certificate, tc.expectedCertBeforeStart.certificate) { if tc.expectedCertBeforeStart == nil {
t.Errorf("Got %v, wanted %v", certificateString(certificate), certificateString(tc.expectedCertBeforeStart.certificate)) if certificate != nil {
} t.Errorf("Expected certificate to be nil, was %s", certificate.Leaf.NotAfter)
if err := certificateManager.SetCertificateSigningRequestClient(&fakeClient{ }
certificatePEM: tc.apiCert.certificatePEM, } else {
}); err != nil { if !certificatesEqual(certificate, tc.expectedCertBeforeStart.certificate) {
t.Errorf("Got error %v, expected none.", err) t.Errorf("Got %v, wanted %v", certificateString(certificate), certificateString(tc.expectedCertBeforeStart.certificate))
}
} }
if m, ok := certificateManager.(*manager); !ok { if m, ok := certificateManager.(*manager); !ok {
...@@ -649,6 +680,12 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) { ...@@ -649,6 +680,12 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) {
} }
certificate = certificateManager.Current() certificate = certificateManager.Current()
if tc.expectedCertAfterStart == nil {
if certificate != nil {
t.Errorf("Expected certificate to be nil, was %s", certificate.Leaf.NotAfter)
}
return
}
if !certificatesEqual(certificate, tc.expectedCertAfterStart.certificate) { if !certificatesEqual(certificate, tc.expectedCertAfterStart.certificate) {
t.Errorf("Got %v, wanted %v", certificateString(certificate), certificateString(tc.expectedCertAfterStart.certificate)) t.Errorf("Got %v, wanted %v", certificateString(certificate), certificateString(tc.expectedCertAfterStart.certificate))
} }
...@@ -721,8 +758,10 @@ func TestInitializeOtherRESTClients(t *testing.T) { ...@@ -721,8 +758,10 @@ func TestInitializeOtherRESTClients(t *testing.T) {
CertificateStore: certificateStore, CertificateStore: certificateStore,
BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM, BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM,
BootstrapKeyPEM: tc.bootstrapCert.keyPEM, BootstrapKeyPEM: tc.bootstrapCert.keyPEM,
CertificateSigningRequestClient: &fakeClient{ ClientFn: func(_ *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
certificatePEM: tc.apiCert.certificatePEM, return &fakeClient{
certificatePEM: tc.apiCert.certificatePEM,
}, nil
}, },
}) })
if err != nil { if err != nil {
...@@ -873,10 +912,12 @@ func TestServerHealth(t *testing.T) { ...@@ -873,10 +912,12 @@ func TestServerHealth(t *testing.T) {
CertificateStore: certificateStore, CertificateStore: certificateStore,
BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM, BootstrapCertificatePEM: tc.bootstrapCert.certificatePEM,
BootstrapKeyPEM: tc.bootstrapCert.keyPEM, BootstrapKeyPEM: tc.bootstrapCert.keyPEM,
CertificateSigningRequestClient: &fakeClient{ ClientFn: func(_ *tls.Certificate) (certificatesclient.CertificateSigningRequestInterface, error) {
certificatePEM: tc.apiCert.certificatePEM, return &fakeClient{
failureType: tc.failureType, certificatePEM: tc.apiCert.certificatePEM,
err: tc.clientErr, failureType: tc.failureType,
err: tc.clientErr,
}, nil
}, },
}) })
if err != nil { if err != nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment