Merge pull request #49654 from jcbsmpsn/move-certificate-manager

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Move certificate manager to client.

Fixes https://github.com/kubernetes/kubernetes/issues/53452

**What this PR does / why we need it**:
Migrate the certificate_manager to a location where it can be shared.

```release-note
NONE
```
pull/6/head
Kubernetes Submit Queue 2017-10-06 15:00:07 -07:00 committed by GitHub
commit f321a16af4
16 changed files with 226 additions and 122 deletions

View File

@ -121,6 +121,7 @@ go_library(
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/client-go/util/certificate:go_default_library",
] + select({ ] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [ "@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/golang.org/x/exp/inotify:go_default_library", "//vendor/golang.org/x/exp/inotify:go_default_library",

View File

@ -52,6 +52,7 @@ import (
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
certutil "k8s.io/client-go/util/cert" certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/certificate"
"k8s.io/kubernetes/cmd/kubelet/app/options" "k8s.io/kubernetes/cmd/kubelet/app/options"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/capabilities"
@ -64,7 +65,7 @@ import (
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
"k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/cadvisor"
"k8s.io/kubernetes/pkg/kubelet/certificate" kubeletcertificate "k8s.io/kubernetes/pkg/kubelet/certificate"
"k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap" "k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap"
"k8s.io/kubernetes/pkg/kubelet/cm" "k8s.io/kubernetes/pkg/kubelet/cm"
"k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/config"
@ -336,11 +337,11 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) {
var clientCertificateManager certificate.Manager var clientCertificateManager certificate.Manager
if err == nil { if err == nil {
if s.RotateCertificates && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletClientCertificate) { if s.RotateCertificates && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletClientCertificate) {
clientCertificateManager, err = certificate.NewKubeletClientCertificateManager(s.CertDirectory, nodeName, clientConfig.CertData, clientConfig.KeyData, clientConfig.CertFile, clientConfig.KeyFile) clientCertificateManager, err = kubeletcertificate.NewKubeletClientCertificateManager(s.CertDirectory, nodeName, clientConfig.CertData, clientConfig.KeyData, clientConfig.CertFile, clientConfig.KeyFile)
if err != nil { if err != nil {
return err return err
} }
if err := certificate.UpdateTransport(wait.NeverStop, clientConfig, clientCertificateManager); err != nil { if err := kubeletcertificate.UpdateTransport(wait.NeverStop, clientConfig, clientCertificateManager); err != nil {
return err return err
} }
} }

View File

@ -216,7 +216,6 @@ pkg/kubelet/apis/kubeletconfig
pkg/kubelet/apis/kubeletconfig/v1alpha1 pkg/kubelet/apis/kubeletconfig/v1alpha1
pkg/kubelet/cadvisor pkg/kubelet/cadvisor
pkg/kubelet/cadvisor/testing pkg/kubelet/cadvisor/testing
pkg/kubelet/certificate
pkg/kubelet/client pkg/kubelet/client
pkg/kubelet/cm pkg/kubelet/cm
pkg/kubelet/cm/util pkg/kubelet/cm/util

View File

@ -136,6 +136,7 @@ go_library(
"//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/util/certificate:go_default_library",
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
"//vendor/k8s.io/client-go/util/integer:go_default_library", "//vendor/k8s.io/client-go/util/integer:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",

View File

@ -9,49 +9,34 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"certificate_manager.go",
"certificate_store.go",
"kubelet.go", "kubelet.go",
"transport.go", "transport.go",
], ],
deps = [ deps = [
"//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library",
"//pkg/kubelet/metrics:go_default_library", "//pkg/kubelet/metrics:go_default_library",
"//pkg/util/file:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library", "//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/client-go/util/certificate:go_default_library",
], ],
) )
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = ["transport_test.go"],
"certificate_manager_test.go",
"certificate_store_test.go",
"transport_test.go",
],
library = ":go_default_library", library = ":go_default_library",
deps = [ deps = [
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
], ],
) )

View File

@ -22,21 +22,25 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/prometheus/client_golang/prometheus"
certificates "k8s.io/api/certificates/v1beta1" certificates "k8s.io/api/certificates/v1beta1"
"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" clientcertificates "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/util/certificate"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
"k8s.io/kubernetes/pkg/kubelet/metrics"
) )
// 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, ips []net.IP, hostnames []string, certDirectory string) (Manager, error) { func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, ips []net.IP, hostnames []string, certDirectory string) (certificate.Manager, error) {
var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface
if kubeClient != nil && kubeClient.Certificates() != nil { if kubeClient != nil && kubeClient.Certificates() != nil {
certSigningRequestClient = kubeClient.Certificates().CertificateSigningRequests() certSigningRequestClient = kubeClient.Certificates().CertificateSigningRequests()
} }
certificateStore, err := NewFileStore( certificateStore, err := certificate.NewFileStore(
"kubelet-server", "kubelet-server",
certDirectory, certDirectory,
certDirectory, certDirectory,
@ -45,8 +49,17 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize server certificate store: %v", err) return nil, fmt.Errorf("failed to initialize server certificate store: %v", err)
} }
m, err := NewManager(&Config{ var certificateExpiration = prometheus.NewGauge(
Name: "server", prometheus.GaugeOpts{
Namespace: metrics.KubeletSubsystem,
Subsystem: "certificate_manager",
Name: "server_expiration_seconds",
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)
m, err := certificate.NewManager(&certificate.Config{
CertificateSigningRequestClient: certSigningRequestClient, CertificateSigningRequestClient: certSigningRequestClient,
Template: &x509.CertificateRequest{ Template: &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
@ -70,7 +83,8 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
// authenticate itself to a TLS client. // authenticate itself to a TLS client.
certificates.UsageServerAuth, certificates.UsageServerAuth,
}, },
CertificateStore: certificateStore, CertificateStore: certificateStore,
CertificateExpiration: certificateExpiration,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize server certificate manager: %v", err) return nil, fmt.Errorf("failed to initialize server certificate manager: %v", err)
@ -82,8 +96,8 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
// 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). It answers with
// whatever certificate it is initialized with. If a CSR client is set later, it // whatever certificate it is initialized with. If a CSR client is set later, it
// may begin rotating/renewing the client cert // may begin rotating/renewing the client cert
func NewKubeletClientCertificateManager(certDirectory string, nodeName types.NodeName, certData []byte, keyData []byte, certFile string, keyFile string) (Manager, error) { func NewKubeletClientCertificateManager(certDirectory string, nodeName types.NodeName, certData []byte, keyData []byte, certFile string, keyFile string) (certificate.Manager, error) {
certificateStore, err := NewFileStore( certificateStore, err := certificate.NewFileStore(
"kubelet-client", "kubelet-client",
certDirectory, certDirectory,
certDirectory, certDirectory,
@ -92,8 +106,17 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize client certificate store: %v", err) return nil, fmt.Errorf("failed to initialize client certificate store: %v", err)
} }
m, err := NewManager(&Config{ var certificateExpiration = prometheus.NewGauge(
Name: "client", prometheus.GaugeOpts{
Namespace: metrics.KubeletSubsystem,
Subsystem: "certificate_manager",
Name: "client_expiration_seconds",
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)
m, err := certificate.NewManager(&certificate.Config{
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),
@ -118,6 +141,7 @@ func NewKubeletClientCertificateManager(certDirectory string, nodeName types.Nod
CertificateStore: certificateStore, CertificateStore: certificateStore,
BootstrapCertificatePEM: certData, BootstrapCertificatePEM: certData,
BootstrapKeyPEM: keyData, BootstrapKeyPEM: keyData,
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)

View File

@ -30,6 +30,7 @@ import (
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/util/certificate"
) )
// UpdateTransport instruments a restconfig with a transport that dynamically uses // UpdateTransport instruments a restconfig with a transport that dynamically uses
@ -44,13 +45,13 @@ import (
// //
// stopCh should be used to indicate when the transport is unused and doesn't need // stopCh should be used to indicate when the transport is unused and doesn't need
// to continue checking the manager. // to continue checking the manager.
func UpdateTransport(stopCh <-chan struct{}, clientConfig *restclient.Config, clientCertificateManager Manager) error { func UpdateTransport(stopCh <-chan struct{}, clientConfig *restclient.Config, clientCertificateManager certificate.Manager) error {
return updateTransport(stopCh, 10*time.Second, clientConfig, clientCertificateManager) return updateTransport(stopCh, 10*time.Second, clientConfig, clientCertificateManager)
} }
// updateTransport is an internal method that exposes how often this method checks that the // updateTransport is an internal method that exposes how often this method checks that the
// client cert has changed. Intended for testing. // client cert has changed. Intended for testing.
func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager Manager) error { func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager) error {
if clientConfig.Transport != nil { if clientConfig.Transport != nil {
return fmt.Errorf("there is already a transport configured") return fmt.Errorf("there is already a transport configured")
} }

View File

@ -19,6 +19,7 @@ package certificate
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt"
"math/big" "math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -89,6 +90,29 @@ uC6Jo2eLcSV1sSdzTjaaWdM6XeYj6yHOAm8ZBIQs7m6V
-----END RSA PRIVATE KEY-----`) -----END RSA PRIVATE KEY-----`)
) )
type certificateData struct {
keyPEM []byte
certificatePEM []byte
certificate *tls.Certificate
}
func newCertificateData(certificatePEM string, keyPEM string) *certificateData {
certificate, err := tls.X509KeyPair([]byte(certificatePEM), []byte(keyPEM))
if err != nil {
panic(fmt.Sprintf("Unable to initialize certificate: %v", err))
}
certs, err := x509.ParseCertificates(certificate.Certificate[0])
if err != nil {
panic(fmt.Sprintf("Unable to initialize certificate leaf: %v", err))
}
certificate.Leaf = certs[0]
return &certificateData{
keyPEM: []byte(keyPEM),
certificatePEM: []byte(certificatePEM),
certificate: &certificate,
}
}
type fakeManager struct { type fakeManager struct {
cert atomic.Value // Always a *tls.Certificate cert atomic.Value // Always a *tls.Certificate
} }

View File

@ -54,6 +54,7 @@ import (
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/certificate"
"k8s.io/client-go/util/flowcontrol" "k8s.io/client-go/util/flowcontrol"
"k8s.io/client-go/util/integer" "k8s.io/client-go/util/integer"
"k8s.io/kubernetes/cmd/kubelet/app/options" "k8s.io/kubernetes/cmd/kubelet/app/options"
@ -64,7 +65,7 @@ import (
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
"k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/cadvisor"
"k8s.io/kubernetes/pkg/kubelet/certificate" kubeletcertificate "k8s.io/kubernetes/pkg/kubelet/certificate"
"k8s.io/kubernetes/pkg/kubelet/cm" "k8s.io/kubernetes/pkg/kubelet/cm"
"k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/config"
"k8s.io/kubernetes/pkg/kubelet/configmap" "k8s.io/kubernetes/pkg/kubelet/configmap"
@ -743,7 +744,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
ips = append(ips, cloudIPs...) ips = append(ips, cloudIPs...)
names := append([]string{klet.GetHostname(), hostnameOverride}, cloudNames...) names := append([]string{klet.GetHostname(), hostnameOverride}, cloudNames...)
klet.serverCertificateManager, err = certificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, ips, names, certDirectory) klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, ips, names, certDirectory)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize certificate manager: %v", err) return nil, fmt.Errorf("failed to initialize certificate manager: %v", err)
} }

View File

@ -182,6 +182,7 @@ filegroup(
"//staging/src/k8s.io/client-go/transport:all-srcs", "//staging/src/k8s.io/client-go/transport:all-srcs",
"//staging/src/k8s.io/client-go/util/buffer:all-srcs", "//staging/src/k8s.io/client-go/util/buffer:all-srcs",
"//staging/src/k8s.io/client-go/util/cert:all-srcs", "//staging/src/k8s.io/client-go/util/cert:all-srcs",
"//staging/src/k8s.io/client-go/util/certificate:all-srcs",
"//staging/src/k8s.io/client-go/util/exec:all-srcs", "//staging/src/k8s.io/client-go/util/exec:all-srcs",
"//staging/src/k8s.io/client-go/util/flowcontrol:all-srcs", "//staging/src/k8s.io/client-go/util/flowcontrol:all-srcs",
"//staging/src/k8s.io/client-go/util/homedir:all-srcs", "//staging/src/k8s.io/client-go/util/homedir:all-srcs",

View File

@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"certificate_manager_test.go",
"certificate_store_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"certificate_manager.go",
"certificate_store.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,8 @@
reviewers:
- mikedanese
- liggit
- smarterclayton
approvers:
- mikedanese
- liggit
- smarterclayton

View File

@ -28,7 +28,6 @@ import (
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
certificates "k8s.io/api/certificates/v1beta1" certificates "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -37,12 +36,6 @@ import (
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/util/cert" "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/kubelet/metrics"
)
const (
certificateManagerSubsystem = "certificate_manager"
certificateExpirationKey = "expiration_seconds"
) )
// Manager maintains and updates the certificates in use by this certificate // Manager maintains and updates the certificates in use by this certificate
@ -62,10 +55,6 @@ 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 {
// Name is a name describing the certificate being managed by this
// certificate manager. It will be used for recording metrics relevant to
// the certificate.
Name string
// CertificateSigningRequestClient will be used for signing new certificate // CertificateSigningRequestClient will be used for signing new certificate
// requests generated when a key rotation occurs. It must be set either at // requests generated when a key rotation occurs. It must be set either at
// initialization or by using CertificateSigningRequestClient before // initialization or by using CertificateSigningRequestClient before
@ -103,6 +92,9 @@ type Config struct {
// initialized using a generic, multi-use cert/key pair which will be // initialized using a generic, multi-use cert/key pair which will be
// quickly replaced with a unique cert/key pair. // quickly replaced with a unique cert/key pair.
BootstrapKeyPEM []byte BootstrapKeyPEM []byte
// CertificateExpiration will record a metric that shows the remaining
// lifetime of the certificate.
CertificateExpiration Gauge
} }
// Store is responsible for getting and updating the current certificate. // Store is responsible for getting and updating the current certificate.
@ -121,6 +113,12 @@ type Store interface {
Update(cert, key []byte) (*tls.Certificate, error) Update(cert, key []byte) (*tls.Certificate, error)
} }
// Gauge will record the remaining lifetime of the certificate each time it is
// updated.
type Gauge interface {
Set(float64)
}
// NoCertKeyError indicates there is no cert/key currently available. // NoCertKeyError indicates there is no cert/key currently available.
type NoCertKeyError string type NoCertKeyError string
@ -135,17 +133,13 @@ type manager struct {
cert *tls.Certificate cert *tls.Certificate
rotationDeadline time.Time rotationDeadline time.Time
forceRotation bool forceRotation bool
certificateExpiration prometheus.Gauge certificateExpiration Gauge
} }
// NewManager returns a new certificate manager. A certificate manager is // NewManager returns a new certificate manager. A certificate manager is
// responsible for being the authoritative source of certificates in the // responsible for being the authoritative source of certificates in the
// Kubelet and handling updates due to rotation. // Kubelet and handling updates due to rotation.
func NewManager(config *Config) (Manager, error) { func NewManager(config *Config) (Manager, error) {
if config.Name == "" {
return nil, fmt.Errorf("the 'Name' is required to disambiguate metric values of different certificate manager instances")
}
cert, forceRotation, err := getCurrentCertificateOrBootstrap( cert, forceRotation, err := getCurrentCertificateOrBootstrap(
config.CertificateStore, config.CertificateStore,
config.BootstrapCertificatePEM, config.BootstrapCertificatePEM,
@ -154,17 +148,6 @@ func NewManager(config *Config) (Manager, error) {
return nil, err return nil, err
} }
var certificateExpiration = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: metrics.KubeletSubsystem,
Subsystem: certificateManagerSubsystem,
Name: fmt.Sprintf("%s_%s", config.Name, certificateExpirationKey),
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)
m := manager{ m := manager{
certSigningRequestClient: config.CertificateSigningRequestClient, certSigningRequestClient: config.CertificateSigningRequestClient,
template: config.Template, template: config.Template,
@ -172,7 +155,7 @@ func NewManager(config *Config) (Manager, error) {
certStore: config.CertificateStore, certStore: config.CertificateStore,
cert: cert, cert: cert,
forceRotation: forceRotation, forceRotation: forceRotation,
certificateExpiration: certificateExpiration, certificateExpiration: config.CertificateExpiration,
} }
return &m, nil return &m, nil
@ -199,7 +182,7 @@ func (m *manager) SetCertificateSigningRequestClient(certSigningRequestClient ce
m.certSigningRequestClient = certSigningRequestClient m.certSigningRequestClient = certSigningRequestClient
return nil return nil
} }
return fmt.Errorf("CertificateSigningRequestClient is already set.") return fmt.Errorf("property CertificateSigningRequestClient is already set")
} }
// Start will start the background work of rotating the certificates. // Start will start the background work of rotating the certificates.
@ -335,16 +318,23 @@ func (m *manager) setRotationDeadline() {
notAfter := m.cert.Leaf.NotAfter notAfter := m.cert.Leaf.NotAfter
totalDuration := float64(notAfter.Sub(m.cert.Leaf.NotBefore)) totalDuration := float64(notAfter.Sub(m.cert.Leaf.NotBefore))
// Use some jitter to set the rotation threshold so each node will rotate m.rotationDeadline = m.cert.Leaf.NotBefore.Add(jitteryDuration(totalDuration))
// at approximately 70-90% of the total lifetime of the certificate. With
// jitter, if a number of nodes are added to a cluster at approximately the
// same time (such as cluster creation time), they won't all try to rotate
// certificates at the same time for the rest of the life of the cluster.
jitteryDuration := wait.Jitter(time.Duration(totalDuration), 0.2) - time.Duration(totalDuration*0.3)
m.rotationDeadline = m.cert.Leaf.NotBefore.Add(jitteryDuration)
glog.V(2).Infof("Certificate expiration is %v, rotation deadline is %v", notAfter, m.rotationDeadline) glog.V(2).Infof("Certificate expiration is %v, rotation deadline is %v", notAfter, m.rotationDeadline)
m.certificateExpiration.Set(float64(notAfter.Unix())) if m.certificateExpiration != nil {
m.certificateExpiration.Set(float64(notAfter.Unix()))
}
}
// jitteryDuration uses some jitter to set the rotation threshold so each node
// will rotate at approximately 70-90% of the total lifetime of the
// certificate. With jitter, if a number of nodes are added to a cluster at
// approximately the same time (such as cluster creation time), they won't all
// try to rotate certificates at the same time for the rest of the life of the
// cluster.
//
// This function is represented as a variable to allow replacement during testing.
var jitteryDuration = func(totalDuration float64) time.Duration {
return wait.Jitter(time.Duration(totalDuration), 0.2) - time.Duration(totalDuration*0.3)
} }
func (m *manager) updateCached(cert *tls.Certificate) { func (m *manager) updateCached(cert *tls.Certificate) {

View File

@ -26,20 +26,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/prometheus/client_golang/prometheus"
certificates "k8s.io/api/certificates/v1beta1" certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
watch "k8s.io/apimachinery/pkg/watch" watch "k8s.io/apimachinery/pkg/watch"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
) )
type certificateData struct {
keyPEM []byte
certificatePEM []byte
certificate *tls.Certificate
}
var storeCertData = newCertificateData(`-----BEGIN CERTIFICATE----- var storeCertData = newCertificateData(`-----BEGIN CERTIFICATE-----
MIICRzCCAfGgAwIBAgIJALMb7ecMIk3MMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV MIICRzCCAfGgAwIBAgIJALMb7ecMIk3MMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEYMBYGA1UE BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEYMBYGA1UE
@ -115,6 +107,12 @@ bDQT1r8Q3Gx+h9LRqQeHgPBQ3F5ylqqBAiBaJ0hkYvrIdWxNlcLqD3065bJpHQ4S
WQkuZUQN1M/Xvg== WQkuZUQN1M/Xvg==
-----END RSA PRIVATE KEY-----`) -----END RSA PRIVATE KEY-----`)
type certificateData struct {
keyPEM []byte
certificatePEM []byte
certificate *tls.Certificate
}
func newCertificateData(certificatePEM string, keyPEM string) *certificateData { func newCertificateData(certificatePEM string, keyPEM string) *certificateData {
certificate, err := tls.X509KeyPair([]byte(certificatePEM), []byte(keyPEM)) certificate, err := tls.X509KeyPair([]byte(certificatePEM), []byte(keyPEM))
if err != nil { if err != nil {
@ -137,7 +135,6 @@ func TestNewManagerNoRotation(t *testing.T) {
cert: storeCertData.certificate, cert: storeCertData.certificate,
} }
if _, err := NewManager(&Config{ if _, err := NewManager(&Config{
Name: "test_no_rotation",
Template: &x509.CertificateRequest{}, Template: &x509.CertificateRequest{},
Usages: []certificates.KeyUsage{}, Usages: []certificates.KeyUsage{},
CertificateStore: store, CertificateStore: store,
@ -173,11 +170,6 @@ func TestShouldRotate(t *testing.T) {
}, },
template: &x509.CertificateRequest{}, template: &x509.CertificateRequest{},
usages: []certificates.KeyUsage{}, usages: []certificates.KeyUsage{},
certificateExpiration: prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "test_gauge_name",
},
),
} }
m.setRotationDeadline() m.setRotationDeadline()
if m.shouldRotate() != test.shouldRotate { if m.shouldRotate() != test.shouldRotate {
@ -191,7 +183,19 @@ func TestShouldRotate(t *testing.T) {
} }
} }
type gaugeMock struct {
calls int
lastValue float64
}
func (g *gaugeMock) Set(v float64) {
g.calls++
g.lastValue = v
}
func TestSetRotationDeadline(t *testing.T) { func TestSetRotationDeadline(t *testing.T) {
defer func(original func(float64) time.Duration) { jitteryDuration = original }(jitteryDuration)
now := time.Now() now := time.Now()
testCases := []struct { testCases := []struct {
name string name string
@ -211,6 +215,7 @@ func TestSetRotationDeadline(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
g := gaugeMock{}
m := manager{ m := manager{
cert: &tls.Certificate{ cert: &tls.Certificate{
Leaf: &x509.Certificate{ Leaf: &x509.Certificate{
@ -218,27 +223,27 @@ func TestSetRotationDeadline(t *testing.T) {
NotAfter: tc.notAfter, NotAfter: tc.notAfter,
}, },
}, },
template: &x509.CertificateRequest{}, template: &x509.CertificateRequest{},
usages: []certificates.KeyUsage{}, usages: []certificates.KeyUsage{},
certificateExpiration: prometheus.NewGauge( certificateExpiration: &g,
prometheus.GaugeOpts{
Name: "test_gauge_name",
},
),
} }
jitteryDuration = func(float64) time.Duration { return time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.7) }
lowerBound := tc.notBefore.Add(time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.7)) lowerBound := tc.notBefore.Add(time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.7))
upperBound := tc.notBefore.Add(time.Duration(float64(tc.notAfter.Sub(tc.notBefore)) * 0.9))
for i := 0; i < 1000; i++ { m.setRotationDeadline()
// setRotationDeadline includes jitter, so this needs to run many times for validation.
m.setRotationDeadline() if !m.rotationDeadline.Equal(lowerBound) {
if m.rotationDeadline.Before(lowerBound) || m.rotationDeadline.After(upperBound) { t.Errorf("For notBefore %v, notAfter %v, the rotationDeadline %v should be %v.",
t.Errorf("For notBefore %v, notAfter %v, the rotationDeadline %v should be between %v and %v.", tc.notBefore,
tc.notBefore, tc.notAfter,
tc.notAfter, m.rotationDeadline,
m.rotationDeadline, lowerBound)
lowerBound, }
upperBound) if g.calls != 1 {
} t.Errorf("%d metrics were recorded, wanted %d", g.calls, 1)
}
if g.lastValue != float64(tc.notAfter.Unix()) {
t.Errorf("%d value for metric was recorded, wanted %d", g.lastValue, tc.notAfter.Unix())
} }
}) })
} }
@ -295,7 +300,6 @@ func TestNewManagerBootstrap(t *testing.T) {
var cm Manager var cm Manager
cm, err := NewManager(&Config{ cm, err := NewManager(&Config{
Name: "test_bootstrap",
Template: &x509.CertificateRequest{}, Template: &x509.CertificateRequest{},
Usages: []certificates.KeyUsage{}, Usages: []certificates.KeyUsage{},
CertificateStore: store, CertificateStore: store,
@ -333,7 +337,6 @@ func TestNewManagerNoBootstrap(t *testing.T) {
} }
cm, err := NewManager(&Config{ cm, err := NewManager(&Config{
Name: "test_no_bootstrap",
Template: &x509.CertificateRequest{}, Template: &x509.CertificateRequest{},
Usages: []certificates.KeyUsage{}, Usages: []certificates.KeyUsage{},
CertificateStore: store, CertificateStore: store,
@ -469,14 +472,13 @@ func TestInitializeCertificateSigningRequestClient(t *testing.T) {
}, },
} }
for i, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
certificateStore := &fakeStore{ certificateStore := &fakeStore{
cert: tc.storeCert.certificate, cert: tc.storeCert.certificate,
} }
certificateManager, err := NewManager(&Config{ certificateManager, err := NewManager(&Config{
Name: fmt.Sprintf("test_initialize_client_%d", i),
Template: &x509.CertificateRequest{ Template: &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{"system:nodes"}, Organization: []string{"system:nodes"},
@ -571,14 +573,13 @@ func TestInitializeOtherRESTClients(t *testing.T) {
}, },
} }
for i, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
certificateStore := &fakeStore{ certificateStore := &fakeStore{
cert: tc.storeCert.certificate, cert: tc.storeCert.certificate,
} }
certificateManager, err := NewManager(&Config{ certificateManager, err := NewManager(&Config{
Name: fmt.Sprintf("test_initialize_other_rest_clients_%d", i),
Template: &x509.CertificateRequest{ Template: &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{"system:nodes"}, Organization: []string{"system:nodes"},

View File

@ -28,8 +28,6 @@ import (
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
utilfile "k8s.io/kubernetes/pkg/util/file"
) )
const ( const (
@ -86,7 +84,7 @@ func NewFileStore(
func (s *fileStore) recover() error { func (s *fileStore) recover() error {
// If the 'current' file doesn't exist, continue on with the recovery process. // If the 'current' file doesn't exist, continue on with the recovery process.
currentPath := filepath.Join(s.certDirectory, s.filename(currentPair)) currentPath := filepath.Join(s.certDirectory, s.filename(currentPair))
if exists, err := utilfile.FileExists(currentPath); err != nil { if exists, err := fileExists(currentPath); err != nil {
return err return err
} else if exists { } else if exists {
return nil return nil
@ -113,18 +111,18 @@ func (s *fileStore) recover() error {
func (s *fileStore) Current() (*tls.Certificate, error) { func (s *fileStore) Current() (*tls.Certificate, error) {
pairFile := filepath.Join(s.certDirectory, s.filename(currentPair)) pairFile := filepath.Join(s.certDirectory, s.filename(currentPair))
if pairFileExists, err := utilfile.FileExists(pairFile); err != nil { if pairFileExists, err := fileExists(pairFile); err != nil {
return nil, err return nil, err
} else if pairFileExists { } else if pairFileExists {
glog.Infof("Loading cert/key pair from %q.", pairFile) glog.Infof("Loading cert/key pair from %q.", pairFile)
return loadFile(pairFile) return loadFile(pairFile)
} }
certFileExists, err := utilfile.FileExists(s.certFile) certFileExists, err := fileExists(s.certFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyFileExists, err := utilfile.FileExists(s.keyFile) keyFileExists, err := fileExists(s.keyFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -135,11 +133,11 @@ func (s *fileStore) Current() (*tls.Certificate, error) {
c := filepath.Join(s.certDirectory, s.pairNamePrefix+certExtension) c := filepath.Join(s.certDirectory, s.pairNamePrefix+certExtension)
k := filepath.Join(s.keyDirectory, s.pairNamePrefix+keyExtension) k := filepath.Join(s.keyDirectory, s.pairNamePrefix+keyExtension)
certFileExists, err = utilfile.FileExists(c) certFileExists, err = fileExists(c)
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyFileExists, err = utilfile.FileExists(k) keyFileExists, err = fileExists(k)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -240,7 +238,7 @@ func (s *fileStore) updateSymlink(filename string) error {
return err return err
} }
} else if fi.Mode()&os.ModeSymlink != os.ModeSymlink { } else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
return fmt.Errorf("expected %q to be a symlink but it is a file.", currentPath) return fmt.Errorf("expected %q to be a symlink but it is a file", currentPath)
} else { } else {
currentPathExists = true currentPathExists = true
} }
@ -253,7 +251,7 @@ func (s *fileStore) updateSymlink(filename string) error {
return err return err
} }
} else if fi.Mode()&os.ModeSymlink != os.ModeSymlink { } else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
return fmt.Errorf("expected %q to be a symlink but it is a file.", updatedPath) return fmt.Errorf("expected %q to be a symlink but it is a file", updatedPath)
} else { } else {
if err := os.Remove(updatedPath); err != nil { if err := os.Remove(updatedPath); err != nil {
return fmt.Errorf("unable to remove %q: %v", updatedPath, err) return fmt.Errorf("unable to remove %q: %v", updatedPath, err)
@ -262,7 +260,7 @@ func (s *fileStore) updateSymlink(filename string) error {
// Check that the new cert/key pair file exists to avoid rotating to an // Check that the new cert/key pair file exists to avoid rotating to an
// invalid cert/key. // invalid cert/key.
if filenameExists, err := utilfile.FileExists(filename); err != nil { if filenameExists, err := fileExists(filename); err != nil {
return err return err
} else if !filenameExists { } else if !filenameExists {
return fmt.Errorf("file %q does not exist so it can not be used as the currently selected cert/key", filename) return fmt.Errorf("file %q does not exist so it can not be used as the currently selected cert/key", filename)
@ -307,3 +305,13 @@ func loadX509KeyPair(certFile, keyFile string) (*tls.Certificate, error) {
cert.Leaf = certs[0] cert.Leaf = certs[0]
return &cert, nil return &cert, nil
} }
// FileExists checks if specified file exists.
func fileExists(filename string) (bool, error) {
if _, err := os.Stat(filename); os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}