vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

59
vendor/k8s.io/kubernetes/pkg/kubelet/certificate/BUILD generated vendored Normal file
View File

@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"kubelet.go",
"transport.go",
],
importpath = "k8s.io/kubernetes/pkg/kubelet/certificate",
deps = [
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
"//pkg/kubelet/metrics:go_default_library",
"//vendor/github.com/golang/glog: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/apimachinery/pkg/types: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/client-go/kubernetes: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/util/certificate:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["transport_test.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/certificate",
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/certificate/bootstrap:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -0,0 +1,6 @@
reviewers:
- mikedanese
- liggitt
approvers:
- mikedanese
- liggitt

View File

@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["bootstrap_test.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap",
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["bootstrap.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/certificate/bootstrap",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime: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/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/transport:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/client-go/util/certificate/csr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,209 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bootstrap
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
certificates "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/certificate/csr"
)
const (
defaultKubeletClientCertificateFile = "kubelet-client.crt"
defaultKubeletClientKeyFile = "kubelet-client.key"
)
// 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.
// The certificate and key file are stored in certDir.
func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string, nodeName types.NodeName) error {
// Short-circuit if the kubeconfig file exists and is valid.
ok, err := verifyBootstrapClientConfig(kubeconfigPath)
if err != nil {
return err
}
if ok {
glog.V(2).Infof("Kubeconfig %s exists and is valid, skipping bootstrap", kubeconfigPath)
return nil
}
glog.V(2).Info("Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file")
bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
if err != nil {
return fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
}
bootstrapClient, err := certificates.NewForConfig(bootstrapClientConfig)
if err != nil {
return fmt.Errorf("unable to create certificates signing request client: %v", err)
}
success := false
// Get the private key.
keyPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientKeyFile))
if err != nil {
return fmt.Errorf("unable to build bootstrap key path: %v", err)
}
// If we are unable to generate a CSR, we remove our key file and start fresh.
// This method is used before enabling client rotation and so we must ensure we
// can make forward progress if we crash and exit when a CSR exists but the cert
// it is signed for has expired.
defer func() {
if !success {
if err := os.Remove(keyPath); err != nil && !os.IsNotExist(err) {
glog.Warningf("Cannot clean up the key file %q: %v", keyPath, err)
}
}
}()
keyData, _, err := certutil.LoadOrGenerateKeyFile(keyPath)
if err != nil {
return err
}
// Get the cert.
certPath, err := filepath.Abs(filepath.Join(certDir, defaultKubeletClientCertificateFile))
if err != nil {
return fmt.Errorf("unable to build bootstrap client cert path: %v", err)
}
defer func() {
if !success {
if err := os.Remove(certPath); err != nil && !os.IsNotExist(err) {
glog.Warningf("Cannot clean up the cert file %q: %v", certPath, err)
}
}
}()
certData, err := csr.RequestNodeCertificate(bootstrapClient.CertificateSigningRequests(), keyData, nodeName)
if err != nil {
return err
}
if err := certutil.WriteCert(certPath, certData); err != nil {
return err
}
// Get the CA data from the bootstrap client config.
caFile, caData := bootstrapClientConfig.CAFile, []byte{}
if len(caFile) == 0 {
caData = bootstrapClientConfig.CAData
}
// Build resulting kubeconfig.
kubeconfigData := clientcmdapi.Config{
// Define a cluster stanza based on the bootstrap kubeconfig.
Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": {
Server: bootstrapClientConfig.Host,
InsecureSkipTLSVerify: bootstrapClientConfig.Insecure,
CertificateAuthority: caFile,
CertificateAuthorityData: caData,
}},
// Define auth based on the obtained client cert.
AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": {
ClientCertificate: certPath,
ClientKey: keyPath,
}},
// Define a context that connects the auth info and cluster, and set it as the default
Contexts: map[string]*clientcmdapi.Context{"default-context": {
Cluster: "default-cluster",
AuthInfo: "default-auth",
Namespace: "default",
}},
CurrentContext: "default-context",
}
// Marshal to disk
if err := clientcmd.WriteToFile(kubeconfigData, kubeconfigPath); err != nil {
return err
}
success = true
return nil
}
func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) {
// Load structured kubeconfig data from the given path.
loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}
loadedConfig, err := loader.Load()
if err != nil {
return nil, err
}
// Flatten the loaded data to a particular restclient.Config based on the current context.
return clientcmd.NewNonInteractiveClientConfig(
*loadedConfig,
loadedConfig.CurrentContext,
&clientcmd.ConfigOverrides{},
loader,
).ClientConfig()
}
// verifyBootstrapClientConfig 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
// should stop immediately.
func verifyBootstrapClientConfig(kubeconfigPath string) (bool, error) {
_, err := os.Stat(kubeconfigPath)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("error reading existing bootstrap kubeconfig %s: %v", kubeconfigPath, err)
}
bootstrapClientConfig, err := loadRESTClientConfig(kubeconfigPath)
if err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to read existing bootstrap client config: %v", err))
return false, nil
}
transportConfig, err := bootstrapClientConfig.TransportConfig()
if err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to load transport configuration from existing bootstrap client config: %v", err))
return false, nil
}
// has side effect of populating transport config data fields
if _, err := transport.TLSConfigFor(transportConfig); err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to load TLS configuration from existing bootstrap client config: %v", err))
return false, nil
}
certs, err := certutil.ParseCertsPEM(transportConfig.TLS.CertData)
if err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to load TLS certificates from existing bootstrap client config: %v", err))
return false, nil
}
if len(certs) == 0 {
utilruntime.HandleError(fmt.Errorf("Unable to read TLS certificates from existing bootstrap client config: %v", err))
return false, nil
}
now := time.Now()
for _, cert := range certs {
if now.After(cert.NotAfter) {
utilruntime.HandleError(fmt.Errorf("Part of the existing bootstrap client certificate is expired: %s", cert.NotAfter))
return false, nil
}
}
return true, nil
}

View File

@ -0,0 +1,85 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bootstrap
import (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/util/diff"
restclient "k8s.io/client-go/rest"
)
func TestLoadRESTClientConfig(t *testing.T) {
testData := []byte(`
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: ca-a.crt
server: https://cluster-a.com
name: cluster-a
- cluster:
certificate-authority-data: VGVzdA==
server: https://cluster-b.com
name: cluster-b
contexts:
- context:
cluster: cluster-a
namespace: ns-a
user: user-a
name: context-a
- context:
cluster: cluster-b
namespace: ns-b
user: user-b
name: context-b
current-context: context-b
users:
- name: user-a
user:
token: mytoken-a
- name: user-b
user:
token: mytoken-b
`)
f, err := ioutil.TempFile("", "kubeconfig")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
ioutil.WriteFile(f.Name(), testData, os.FileMode(0755))
config, err := loadRESTClientConfig(f.Name())
if err != nil {
t.Fatal(err)
}
expectedConfig := &restclient.Config{
Host: "https://cluster-b.com",
TLSClientConfig: restclient.TLSClientConfig{
CAData: []byte(`Test`),
},
BearerToken: "mytoken-b",
}
if !reflect.DeepEqual(config, expectedConfig) {
t.Errorf("Unexpected config: %s", diff.ObjectDiff(config, expectedConfig))
}
}

View File

@ -0,0 +1,150 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificate
import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net"
"github.com/prometheus/client_golang/prometheus"
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/types"
clientset "k8s.io/client-go/kubernetes"
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/metrics"
)
// NewKubeletServerCertificateManager creates a certificate manager for the kubelet when retrieving a server certificate
// or returns an 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
if kubeClient != nil && kubeClient.CertificatesV1beta1() != nil {
certSigningRequestClient = kubeClient.CertificatesV1beta1().CertificateSigningRequests()
}
certificateStore, err := certificate.NewFileStore(
"kubelet-server",
certDirectory,
certDirectory,
kubeCfg.TLSCertFile,
kubeCfg.TLSPrivateKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to initialize server certificate store: %v", err)
}
var certificateExpiration = prometheus.NewGauge(
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,
Template: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: hostnames,
IPAddresses: ips,
},
Usages: []certificates.KeyUsage{
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
//
// Digital signature allows the certificate to be used to verify
// digital signatures used during TLS negotiation.
certificates.UsageDigitalSignature,
// KeyEncipherment allows the cert/key pair to be used to encrypt
// keys, including the symmetric keys negotiated during TLS setup
// and used for data transfer.
certificates.UsageKeyEncipherment,
// ServerAuth allows the cert to be used by a TLS server to
// authenticate itself to a TLS client.
certificates.UsageServerAuth,
},
CertificateStore: certificateStore,
CertificateExpiration: certificateExpiration,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize server certificate manager: %v", err)
}
return m, nil
}
// NewKubeletClientCertificateManager sets up a certificate manager without a
// 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
// may begin rotating/renewing the client cert
func NewKubeletClientCertificateManager(certDirectory string, nodeName types.NodeName, certData []byte, keyData []byte, certFile string, keyFile string) (certificate.Manager, error) {
certificateStore, err := certificate.NewFileStore(
"kubelet-client",
certDirectory,
certDirectory,
certFile,
keyFile)
if err != nil {
return nil, fmt.Errorf("failed to initialize client certificate store: %v", err)
}
var certificateExpiration = prometheus.NewGauge(
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{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
},
Usages: []certificates.KeyUsage{
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
//
// DigitalSignature allows the certificate to be used to verify
// digital signatures including signatures used during TLS
// negotiation.
certificates.UsageDigitalSignature,
// KeyEncipherment allows the cert/key pair to be used to encrypt
// keys, including the symmetric keys negotiated during TLS setup
// and used for data transfer..
certificates.UsageKeyEncipherment,
// ClientAuth allows the cert to be used by a TLS client to
// authenticate itself to the TLS server.
certificates.UsageClientAuth,
},
CertificateStore: certificateStore,
BootstrapCertificatePEM: certData,
BootstrapKeyPEM: keyData,
CertificateExpiration: certificateExpiration,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize client certificate manager: %v", err)
}
return m, nil
}

View File

@ -0,0 +1,176 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificate
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"sync"
"time"
"github.com/golang/glog"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/util/certificate"
)
// UpdateTransport instruments a restconfig with a transport that dynamically uses
// certificates provided by the manager for TLS client auth.
//
// The config must not already provide an explicit transport.
//
// The returned transport periodically checks the manager to determine if the
// certificate has changed. If it has, the transport shuts down all existing client
// connections, forcing the client to re-handshake with the server and use the
// new certificate.
//
// stopCh should be used to indicate when the transport is unused and doesn't need
// to continue checking the manager.
func UpdateTransport(stopCh <-chan struct{}, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitIfExpired bool) error {
return updateTransport(stopCh, 10*time.Second, clientConfig, clientCertificateManager, exitIfExpired)
}
// updateTransport is an internal method that exposes how often this method checks that the
// client cert has changed. Intended for testing.
func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitIfExpired bool) error {
if clientConfig.Transport != nil {
return fmt.Errorf("there is already a transport configured")
}
tlsConfig, err := restclient.TLSConfigFor(clientConfig)
if err != nil {
return fmt.Errorf("unable to configure TLS for the rest client: %v", err)
}
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
tlsConfig.Certificates = nil
tlsConfig.GetClientCertificate = func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
cert := clientCertificateManager.Current()
if cert == nil {
return &tls.Certificate{Certificate: nil}, nil
}
return cert, nil
}
// Custom dialer that will track all connections it creates.
t := &connTracker{
dialer: &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second},
conns: make(map[*closableConn]struct{}),
}
lastCert := clientCertificateManager.Current()
go wait.Until(func() {
curr := clientCertificateManager.Current()
if exitIfExpired && curr != nil && time.Now().After(curr.Leaf.NotAfter) {
if clientCertificateManager.ServerHealthy() {
glog.Fatalf("The currently active client certificate has expired and the server is responsive, exiting.")
} else {
glog.Errorf("The currently active client certificate has expired, but the server is not responsive. A restart may be necessary to retrieve new initial credentials.")
}
}
if curr == nil || lastCert == curr {
// Cert hasn't been rotated.
return
}
lastCert = curr
glog.Infof("certificate rotation detected, shutting down client connections to start using new credentials")
// The cert has been rotated. Close all existing connections to force the client
// to reperform its TLS handshake with new cert.
//
// See: https://github.com/kubernetes-incubator/bootkube/pull/663#issuecomment-318506493
t.closeAllConns()
}, period, stopCh)
clientConfig.Transport = utilnet.SetTransportDefaults(&http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
MaxIdleConnsPerHost: 25,
DialContext: t.DialContext, // Use custom dialer.
})
// Zero out all existing TLS options since our new transport enforces them.
clientConfig.CertData = nil
clientConfig.KeyData = nil
clientConfig.CertFile = ""
clientConfig.KeyFile = ""
clientConfig.CAData = nil
clientConfig.CAFile = ""
clientConfig.Insecure = false
return nil
}
// connTracker is a dialer that tracks all open connections it creates.
type connTracker struct {
dialer *net.Dialer
mu sync.Mutex
conns map[*closableConn]struct{}
}
// closeAllConns forcibly closes all tracked connections.
func (c *connTracker) closeAllConns() {
c.mu.Lock()
conns := c.conns
c.conns = make(map[*closableConn]struct{})
c.mu.Unlock()
for conn := range conns {
conn.Close()
}
}
func (c *connTracker) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := c.dialer.DialContext(ctx, network, address)
if err != nil {
return nil, err
}
closable := &closableConn{Conn: conn}
// Start tracking the connection
c.mu.Lock()
c.conns[closable] = struct{}{}
c.mu.Unlock()
// When the connection is closed, remove it from the map. This will
// be no-op if the connection isn't in the map, e.g. if closeAllConns()
// is called.
closable.onClose = func() {
c.mu.Lock()
delete(c.conns, closable)
c.mu.Unlock()
}
return closable, nil
}
type closableConn struct {
onClose func()
net.Conn
}
func (c *closableConn) Close() error {
go c.onClose()
return c.Conn.Close()
}

View File

@ -0,0 +1,218 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificate
import (
"crypto/tls"
"crypto/x509"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/rest"
)
var (
client1CertData = newCertificateData(`-----BEGIN CERTIFICATE-----
MIICBDCCAW2gAwIBAgIJAPgVBh+4xbGoMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
BAMMEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwNzI4MjMxNTI4WhgPMjI5MTA1MTMy
MzE1MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfdGVzdHNfY2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQDkGXXSm6Yun5o3Jlmx45rItcQ2pmnoDk4eZfl0
rmPa674s2pfYo3KywkXQ1Fp3BC8GUgzPLSfJ8xXya9Lg1Wo8sHrDln0iRg5HXxGu
uFNhRBvj2S0sIff0ZG/IatB9I6WXVOUYuQj6+A0CdULNj1vBqH9+7uWbLZ6lrD4b
a44x/wIDAQABo0owSDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAU
BggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0B
AQsFAAOBgQCpN27uh/LjUVCaBK7Noko25iih/JSSoWzlvc8CaipvSPofNWyGx3Vu
OdcSwNGYX/pp4ZoAzFij/Y5u0vKTVLkWXATeTMVmlPvhmpYjj9gPkCSY6j/SiKlY
kGy0xr+0M5UQkMBcfIh9oAp9um1fZHVWAJAGP/ikZgkcUey0LmBn8w==
-----END CERTIFICATE-----`, `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDkGXXSm6Yun5o3Jlmx45rItcQ2pmnoDk4eZfl0rmPa674s2pfY
o3KywkXQ1Fp3BC8GUgzPLSfJ8xXya9Lg1Wo8sHrDln0iRg5HXxGuuFNhRBvj2S0s
Iff0ZG/IatB9I6WXVOUYuQj6+A0CdULNj1vBqH9+7uWbLZ6lrD4ba44x/wIDAQAB
AoGAZbWwowvCq1GBq4vPPRI3h739Uz0bRl1ymf1woYXNguXRtCB4yyH+2BTmmrrF
6AIWkePuUEdbUaKyK5nGu3iOWM+/i6NP3kopQANtbAYJ2ray3kwvFlhqyn1bxX4n
gl/Cbdw1If4zrDrB66y8mYDsjzK7n/gFaDNcY4GArjvOXKkCQQD9Lgv+WD73y4RP
yS+cRarlEeLLWVsX/pg2oEBLM50jsdUnrLSW071MjBgP37oOXzqynF9SoDbP2Y5C
x+aGux9LAkEA5qPlQPv0cv8Wc3qTI+LixZ/86PPHKWnOnwaHm3b9vQjZAkuVQg3n
Wgg9YDmPM87t3UFH7ZbDihUreUxwr9ZjnQJAZ9Z95shMsxbOYmbSVxafu6m1Sc+R
M+sghK7/D5jQpzYlhUspGf8n0YBX0hLhXUmjamQGGH5LXL4Owcb4/mM6twJAEVio
SF/qva9jv+GrKVrKFXT374lOJFY53Qn/rvifEtWUhLCslCA5kzLlctRBafMZPrfH
Mh5RrJP1BhVysDbenQJASGcc+DiF7rB6K++ZGyC11E2AP29DcZ0pgPESSV7npOGg
+NqPRZNVCSZOiVmNuejZqmwKhZNGZnBFx1Y+ChAAgw==
-----END RSA PRIVATE KEY-----`)
client2CertData = newCertificateData(`-----BEGIN CERTIFICATE-----
MIICBDCCAW2gAwIBAgIJAPgVBh+4xbGnMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
BAMMEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwNzI4MjMxNTI4WhgPMjI5MTA1MTMy
MzE1MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfdGVzdHNfY2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQDQQLzbrmHbtlxE7wViaoXFp5tQx7zzM2Ed7O1E
gs3JUws5KkPbNrejLwixvLkzzU152M43UGsyKDn7HPyjXDogTZSW6C257XpYodk3
S/gZS9oZtPss4UJuJioQk/M8X1ZjYP8kCTArOvVRJeNQL8GM7h5QQ6J5LUq+IdZb
T0retQIDAQABo0owSDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAU
BggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0B
AQsFAAOBgQBdAxoU5YAmp0d+5b4qg/xOGC5rKcnksQEXYoGwFBWwaKvh9oUlGGxI
A5Ykf2TEl24br4tLmicpdxUX4H4PbkdPxOjM9ghIKlmgHo8vBRC0iVIwYgQsw1W8
ETY34Or+PJqaeslqx/t7kUKY5UIF9DLVolsIiAHveJNR2uBWiP0KiQ==
-----END CERTIFICATE-----`, `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDQQLzbrmHbtlxE7wViaoXFp5tQx7zzM2Ed7O1Egs3JUws5KkPb
NrejLwixvLkzzU152M43UGsyKDn7HPyjXDogTZSW6C257XpYodk3S/gZS9oZtPss
4UJuJioQk/M8X1ZjYP8kCTArOvVRJeNQL8GM7h5QQ6J5LUq+IdZbT0retQIDAQAB
AoGBAMFjTL4IKvG4X+jXub1RxFXvNkkGos2Jaec7TH5xpZ4OUv7L4+We41tTYxSC
d83GGetLzPwK3vDd8DHkEiu1incket78rwmQ89LnQNyM0B5ejaTjW2zHcvKJ0Mtn
nM32juQfq8St9JZVweS87k8RkLt9cOrg6219MRbFO+1Vn8WhAkEA+/rqHCspBdXr
7RL+H63k7RjqBllVEYlw1ukqTw1gp5IImmeOwgl3aRrJJfFV6gxxEqQ4CCb2vf9M
yjrGEvP9KQJBANOTPcpskT/0dyipsAkvLFZTKjN+4fdfq37H3dVgMR6oQcMJwukd
cEio1Hx+XzXuD0RHXighq7bUzel+IqzRuq0CQBJkzpIf1G7InuA/cq19VCi6mNq9
yqftEH+fpab/ov6YemhLBvDDICRcADL02wCqx9ZEhpKRxZE5AbIBeFQJ24ECQG4f
9cmnOPNRC7TengIpy6ojH5QuNu/LnDghUBYAO5D5g0FBk3JDIG6xceha3rPzdX7U
pu28mORRX9xpCyNpBwECQQCtDNZoehdPVuZA3Wocno31Rjmuy83ajgRRuEzqv0tj
uC6Jo2eLcSV1sSdzTjaaWdM6XeYj6yHOAm8ZBIQs7m6V
-----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 {
cert atomic.Value // Always a *tls.Certificate
healthy bool
}
func (f *fakeManager) SetCertificateSigningRequestClient(certificatesclient.CertificateSigningRequestInterface) error {
return nil
}
func (f *fakeManager) ServerHealthy() bool { return f.healthy }
func (f *fakeManager) Start() {}
func (f *fakeManager) Current() *tls.Certificate {
if val := f.cert.Load(); val != nil {
return val.(*tls.Certificate)
}
return nil
}
func (f *fakeManager) setCurrent(cert *tls.Certificate) {
f.cert.Store(cert)
}
func TestRotateShutsDownConnections(t *testing.T) {
// This test fails if you comment out the t.closeAllConns() call in
// transport.go and don't close connections on a rotate.
stop := make(chan struct{})
defer close(stop)
m := new(fakeManager)
m.setCurrent(client1CertData.certificate)
// The last certificate we've seen.
lastSeenLeafCert := new(atomic.Value) // Always *x509.Certificate
lastSerialNumber := func() *big.Int {
if cert := lastSeenLeafCert.Load(); cert != nil {
return cert.(*x509.Certificate).SerialNumber
}
return big.NewInt(0)
}
h := func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil && len(r.TLS.PeerCertificates) != 0 {
// Record the last TLS certificate the client sent.
lastSeenLeafCert.Store(r.TLS.PeerCertificates[0])
}
w.Write([]byte(`{}`))
}
s := httptest.NewUnstartedServer(http.HandlerFunc(h))
s.TLS = &tls.Config{
// Just request a cert, we don't need to verify it.
ClientAuth: tls.RequestClientCert,
}
s.StartTLS()
defer s.Close()
c := &rest.Config{
Host: s.URL,
TLSClientConfig: rest.TLSClientConfig{
// We don't care about the server's cert.
Insecure: true,
},
ContentConfig: rest.ContentConfig{
// This is a hack. We don't actually care about the serializer.
NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{}),
},
}
// Check for a new cert every 10 milliseconds
if err := updateTransport(stop, 10*time.Millisecond, c, m, false); err != nil {
t.Fatal(err)
}
client, err := rest.UnversionedRESTClientFor(c)
if err != nil {
t.Fatal(err)
}
if err := client.Get().Do().Error(); err != nil {
t.Fatal(err)
}
firstCertSerial := lastSerialNumber()
// Change the manager's certificate. This should cause the client to shut down
// its connections to the server.
m.setCurrent(client2CertData.certificate)
for i := 0; i < 5; i++ {
time.Sleep(time.Millisecond * 10)
client.Get().Do()
if firstCertSerial.Cmp(lastSerialNumber()) != 0 {
// The certificate changed!
return
}
}
t.Errorf("certificate rotated but client never reconnected with new cert")
}