Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

View File

@ -14,31 +14,36 @@ go_library(
],
importpath = "k8s.io/kubernetes/pkg/kubelet/certificate",
deps = [
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
"//pkg/kubelet/apis/config:go_default_library",
"//pkg/kubelet/metrics:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/util/certificate:go_default_library",
"//staging/src/k8s.io/client-go/util/connrotation: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",
"//vendor/k8s.io/client-go/util/connrotation:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["transport_test.go"],
srcs = [
"kubelet_test.go",
"transport_test.go",
],
embed = [":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",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
],
)

View File

@ -1,7 +1,7 @@
reviewers:
- mikedanese
- liggitt
- awly
approvers:
- mikedanese
- liggitt
- sig-auth-certificates-approvers
reviewers:
- sig-auth-certificates-reviewers
labels:
- sig/auth

View File

@ -11,8 +11,13 @@ go_test(
srcs = ["bootstrap_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//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/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
],
)
@ -21,17 +26,21 @@ go_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:go_default_library",
"//vendor/k8s.io/client-go/util/certificate/csr:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1: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/api:go_default_library",
"//staging/src/k8s.io/client-go/transport:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//staging/src/k8s.io/client-go/util/certificate:go_default_library",
"//staging/src/k8s.io/client-go/util/certificate/csr:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)

View File

@ -17,16 +17,25 @@ limitations under the License.
package bootstrap
import (
"context"
"crypto/sha512"
"crypto/x509/pkix"
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/golang/glog"
"k8s.io/klog"
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
certificates "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes/scheme"
certificatesv1beta1 "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"
@ -49,17 +58,18 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string,
return err
}
if ok {
glog.V(2).Infof("Kubeconfig %s exists and is valid, skipping bootstrap", kubeconfigPath)
klog.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")
klog.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)
bootstrapClient, err := certificatesv1beta1.NewForConfig(bootstrapClientConfig)
if err != nil {
return fmt.Errorf("unable to create certificates signing request client: %v", err)
}
@ -83,7 +93,7 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string,
// managed by the store.
privKeyPath := filepath.Join(certDir, tmpPrivateKeyFile)
if !verifyKeyData(keyData) {
glog.V(2).Infof("No valid private key and/or certificate found, reusing existing private key or creating a new one")
klog.V(2).Infof("No valid private key and/or certificate found, reusing existing private key or creating a new one")
// Note: always call LoadOrGenerateKeyFile so that private key is
// reused on next startup if CSR request fails.
keyData, _, err = certutil.LoadOrGenerateKeyFile(privKeyPath)
@ -92,7 +102,11 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string,
}
}
certData, err := csr.RequestNodeCertificate(bootstrapClient.CertificateSigningRequests(), keyData, nodeName)
if err := waitForServer(*bootstrapClientConfig, 1*time.Minute); err != nil {
klog.Warningf("Error waiting for apiserver to come up: %v", err)
}
certData, err := requestNodeCertificate(bootstrapClient.CertificateSigningRequests(), keyData, nodeName)
if err != nil {
return err
}
@ -100,7 +114,7 @@ func LoadClientCert(kubeconfigPath string, bootstrapPath string, certDir string,
return err
}
if err := os.Remove(privKeyPath); err != nil && !os.IsNotExist(err) {
glog.V(2).Infof("failed cleaning up private key file %q: %v", privKeyPath, err)
klog.V(2).Infof("failed cleaning up private key file %q: %v", privKeyPath, err)
}
pemPath := store.CurrentPath()
@ -207,3 +221,97 @@ func verifyKeyData(data []byte) bool {
_, err := certutil.ParsePrivateKeyPEM(data)
return err == nil
}
func waitForServer(cfg restclient.Config, deadline time.Duration) error {
cfg.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
cfg.Timeout = 1 * time.Second
cli, err := restclient.UnversionedRESTClientFor(&cfg)
if err != nil {
return fmt.Errorf("couldn't create client: %v", err)
}
ctx, cancel := context.WithTimeout(context.TODO(), deadline)
defer cancel()
var connected bool
wait.JitterUntil(func() {
if _, err := cli.Get().AbsPath("/healthz").Do().Raw(); err != nil {
klog.Infof("Failed to connect to apiserver: %v", err)
return
}
cancel()
connected = true
}, 2*time.Second, 0.2, true, ctx.Done())
if !connected {
return errors.New("timed out waiting to connect to apiserver")
}
return nil
}
// requestNodeCertificate will create a certificate signing request for a node
// (Organization and CommonName for the CSR will be set as expected for node
// certificates) and send it to API server, then it will watch the object's
// status, once approved by API server, it will return the API server's issued
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
// will return an error. This is intended for use on nodes (kubelet and
// kubeadm).
func requestNodeCertificate(client certificatesv1beta1.CertificateSigningRequestInterface, privateKeyData []byte, nodeName types.NodeName) (certData []byte, err error) {
subject := &pkix.Name{
Organization: []string{"system:nodes"},
CommonName: "system:node:" + string(nodeName),
}
privateKey, err := certutil.ParsePrivateKeyPEM(privateKeyData)
if err != nil {
return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
}
csrData, err := certutil.MakeCSR(privateKey, subject, nil, nil)
if err != nil {
return nil, fmt.Errorf("unable to generate certificate request: %v", err)
}
usages := []certificates.KeyUsage{
certificates.UsageDigitalSignature,
certificates.UsageKeyEncipherment,
certificates.UsageClientAuth,
}
name := digestedName(privateKeyData, subject, usages)
req, err := csr.RequestCertificate(client, csrData, name, usages, privateKey)
if err != nil {
return nil, err
}
return csr.WaitForCertificate(client, req, 3600*time.Second)
}
// This digest should include all the relevant pieces of the CSR we care about.
// We can't direcly hash the serialized CSR because of random padding that we
// regenerate every loop and we include usages which are not contained in the
// CSR. This needs to be kept up to date as we add new fields to the node
// certificates and with ensureCompatible.
func digestedName(privateKeyData []byte, subject *pkix.Name, usages []certificates.KeyUsage) string {
hash := sha512.New512_256()
// Here we make sure two different inputs can't write the same stream
// to the hash. This delimiter is not in the base64.URLEncoding
// alphabet so there is no way to have spill over collisions. Without
// it 'CN:foo,ORG:bar' hashes to the same value as 'CN:foob,ORG:ar'
const delimiter = '|'
encode := base64.RawURLEncoding.EncodeToString
write := func(data []byte) {
hash.Write([]byte(encode(data)))
hash.Write([]byte{delimiter})
}
write(privateKeyData)
write([]byte(subject.CommonName))
for _, v := range subject.Organization {
write([]byte(v))
}
for _, v := range usages {
write([]byte(v))
}
return "node-csr-" + encode(hash.Sum(nil))
}

View File

@ -17,13 +17,19 @@ limitations under the License.
package bootstrap
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
certificates "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/watch"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
restclient "k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
)
func TestLoadRESTClientConfig(t *testing.T) {
@ -36,7 +42,7 @@ clusters:
server: https://cluster-a.com
name: cluster-a
- cluster:
certificate-authority-data: VGVzdA==
certificate-authority-data: VGVzdA==
server: https://cluster-b.com
name: cluster-b
contexts:
@ -83,3 +89,110 @@ users:
t.Errorf("Unexpected config: %s", diff.ObjectDiff(config, expectedConfig))
}
}
func TestRequestNodeCertificateNoKeyData(t *testing.T) {
certData, err := requestNodeCertificate(&fakeClient{}, []byte{}, "fake-node-name")
if err == nil {
t.Errorf("Got no error, wanted error an error because there was an empty private key passed in.")
}
if certData != nil {
t.Errorf("Got cert data, wanted nothing as there should have been an error.")
}
}
func TestRequestNodeCertificateErrorCreatingCSR(t *testing.T) {
client := &fakeClient{
failureType: createError,
}
privateKeyData, err := certutil.MakeEllipticPrivateKeyPEM()
if err != nil {
t.Fatalf("Unable to generate a new private key: %v", err)
}
certData, err := requestNodeCertificate(client, privateKeyData, "fake-node-name")
if err == nil {
t.Errorf("Got no error, wanted error an error because client.Create failed.")
}
if certData != nil {
t.Errorf("Got cert data, wanted nothing as there should have been an error.")
}
}
func TestRequestNodeCertificate(t *testing.T) {
privateKeyData, err := certutil.MakeEllipticPrivateKeyPEM()
if err != nil {
t.Fatalf("Unable to generate a new private key: %v", err)
}
certData, err := requestNodeCertificate(&fakeClient{}, privateKeyData, "fake-node-name")
if err != nil {
t.Errorf("Got %v, wanted no error.", err)
}
if certData == nil {
t.Errorf("Got nothing, expected a CSR.")
}
}
type failureType int
const (
noError failureType = iota
createError
certificateSigningRequestDenied
)
type fakeClient struct {
certificatesclient.CertificateSigningRequestInterface
watch *watch.FakeWatcher
failureType failureType
}
func (c *fakeClient) Create(*certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, error) {
if c.failureType == createError {
return nil, fmt.Errorf("fakeClient failed creating request")
}
csr := certificates.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
UID: "fake-uid",
Name: "fake-certificate-signing-request-name",
},
}
return &csr, nil
}
func (c *fakeClient) List(opts metav1.ListOptions) (*certificates.CertificateSigningRequestList, error) {
return &certificates.CertificateSigningRequestList{}, nil
}
func (c *fakeClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
c.watch = watch.NewFakeWithChanSize(1, false)
c.watch.Add(c.generateCSR())
c.watch.Stop()
return c.watch, nil
}
func (c *fakeClient) generateCSR() *certificates.CertificateSigningRequest {
var condition certificates.CertificateSigningRequestCondition
if c.failureType == certificateSigningRequestDenied {
condition = certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied,
}
} else {
condition = certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
}
}
csr := certificates.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
UID: "fake-uid",
},
Status: certificates.CertificateSigningRequestStatus{
Conditions: []certificates.CertificateSigningRequestCondition{
condition,
},
Certificate: []byte{},
},
}
return &csr
}

View File

@ -21,21 +21,23 @@ import (
"crypto/x509/pkix"
"fmt"
"net"
"sort"
"github.com/prometheus/client_golang/prometheus"
certificates "k8s.io/api/certificates/v1beta1"
"k8s.io/api/core/v1"
"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"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"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) {
func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, getAddresses func() []v1.NodeAddress, certDirectory string) (certificate.Manager, error) {
var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface
if kubeClient != nil && kubeClient.CertificatesV1beta1() != nil {
certSigningRequestClient = kubeClient.CertificatesV1beta1().CertificateSigningRequests()
@ -59,16 +61,25 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
)
prometheus.MustRegister(certificateExpiration)
m, err := certificate.NewManager(&certificate.Config{
CertificateSigningRequestClient: certSigningRequestClient,
Template: &x509.CertificateRequest{
getTemplate := func() *x509.CertificateRequest {
hostnames, ips := addressesToHostnamesAndIPs(getAddresses())
// don't return a template if we have no addresses to request for
if len(hostnames) == 0 && len(ips) == 0 {
return nil
}
return &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: hostnames,
IPAddresses: ips,
},
}
}
m, err := certificate.NewManager(&certificate.Config{
CertificateSigningRequestClient: certSigningRequestClient,
GetTemplate: getTemplate,
Usages: []certificates.KeyUsage{
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
//
@ -92,6 +103,44 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
return m, nil
}
func addressesToHostnamesAndIPs(addresses []v1.NodeAddress) (dnsNames []string, ips []net.IP) {
seenDNSNames := map[string]bool{}
seenIPs := map[string]bool{}
for _, address := range addresses {
if len(address.Address) == 0 {
continue
}
switch address.Type {
case v1.NodeHostName:
if ip := net.ParseIP(address.Address); ip != nil {
seenIPs[address.Address] = true
} else {
seenDNSNames[address.Address] = true
}
case v1.NodeExternalIP, v1.NodeInternalIP:
if ip := net.ParseIP(address.Address); ip != nil {
seenIPs[address.Address] = true
}
case v1.NodeExternalDNS, v1.NodeInternalDNS:
seenDNSNames[address.Address] = true
}
}
for dnsName := range seenDNSNames {
dnsNames = append(dnsNames, dnsName)
}
for ip := range seenIPs {
ips = append(ips, net.ParseIP(ip))
}
// return in stable order
sort.Strings(dnsNames)
sort.Slice(ips, func(i, j int) bool { return ips[i].String() < ips[j].String() })
return dnsNames, ips
}
// 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

View File

@ -0,0 +1,101 @@
/*
Copyright 2018 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 (
"net"
"reflect"
"testing"
"k8s.io/api/core/v1"
)
func TestAddressesToHostnamesAndIPs(t *testing.T) {
tests := []struct {
name string
addresses []v1.NodeAddress
wantDNSNames []string
wantIPs []net.IP
}{
{
name: "empty",
addresses: nil,
wantDNSNames: nil,
wantIPs: nil,
},
{
name: "ignore empty values",
addresses: []v1.NodeAddress{{Type: v1.NodeHostName, Address: ""}},
wantDNSNames: nil,
wantIPs: nil,
},
{
name: "ignore invalid IPs",
addresses: []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: "1.2"},
{Type: v1.NodeExternalIP, Address: "3.4"},
},
wantDNSNames: nil,
wantIPs: nil,
},
{
name: "dedupe values",
addresses: []v1.NodeAddress{
{Type: v1.NodeHostName, Address: "hostname"},
{Type: v1.NodeExternalDNS, Address: "hostname"},
{Type: v1.NodeInternalDNS, Address: "hostname"},
{Type: v1.NodeInternalIP, Address: "1.1.1.1"},
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
},
wantDNSNames: []string{"hostname"},
wantIPs: []net.IP{net.ParseIP("1.1.1.1")},
},
{
name: "order values",
addresses: []v1.NodeAddress{
{Type: v1.NodeHostName, Address: "hostname-2"},
{Type: v1.NodeExternalDNS, Address: "hostname-1"},
{Type: v1.NodeInternalDNS, Address: "hostname-3"},
{Type: v1.NodeInternalIP, Address: "2.2.2.2"},
{Type: v1.NodeExternalIP, Address: "1.1.1.1"},
{Type: v1.NodeInternalIP, Address: "3.3.3.3"},
},
wantDNSNames: []string{"hostname-1", "hostname-2", "hostname-3"},
wantIPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2.2.2.2"), net.ParseIP("3.3.3.3")},
},
{
name: "handle IP and DNS hostnames",
addresses: []v1.NodeAddress{
{Type: v1.NodeHostName, Address: "hostname"},
{Type: v1.NodeHostName, Address: "1.1.1.1"},
},
wantDNSNames: []string{"hostname"},
wantIPs: []net.IP{net.ParseIP("1.1.1.1")},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotDNSNames, gotIPs := addressesToHostnamesAndIPs(tt.addresses)
if !reflect.DeepEqual(gotDNSNames, tt.wantDNSNames) {
t.Errorf("addressesToHostnamesAndIPs() gotDNSNames = %v, want %v", gotDNSNames, tt.wantDNSNames)
}
if !reflect.DeepEqual(gotIPs, tt.wantIPs) {
t.Errorf("addressesToHostnamesAndIPs() gotIPs = %v, want %v", gotIPs, tt.wantIPs)
}
})
}
}

View File

@ -23,7 +23,7 @@ import (
"net/http"
"time"
"github.com/golang/glog"
"k8s.io/klog"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
@ -65,74 +65,84 @@ func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig
d := connrotation.NewDialer((&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext)
if clientCertificateManager != nil {
if err := addCertRotation(stopCh, period, clientConfig, clientCertificateManager, exitAfter, d); err != nil {
return nil, err
}
} else {
clientConfig.Dial = d.DialContext
}
return d.CloseAll, nil
}
func addCertRotation(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration, d *connrotation.Dialer) error {
tlsConfig, err := restclient.TLSConfigFor(clientConfig)
if err != nil {
return nil, fmt.Errorf("unable to configure TLS for the rest client: %v", err)
return fmt.Errorf("unable to configure TLS for the rest client: %v", err)
}
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
if clientCertificateManager != nil {
tlsConfig.Certificates = nil
tlsConfig.GetClientCertificate = func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
cert := clientCertificateManager.Current()
if cert == nil {
return &tls.Certificate{Certificate: nil}, nil
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
}
lastCertAvailable := time.Now()
lastCert := clientCertificateManager.Current()
go wait.Until(func() {
curr := clientCertificateManager.Current()
if exitAfter > 0 {
now := time.Now()
if curr == nil {
// the certificate has been deleted from disk or is otherwise corrupt
if now.After(lastCertAvailable.Add(exitAfter)) {
if clientCertificateManager.ServerHealthy() {
klog.Fatalf("It has been %s since a valid client cert was found and the server is responsive, exiting.", exitAfter)
} else {
klog.Errorf("It has been %s since a valid client cert was found, but the server is not responsive. A restart may be necessary to retrieve new initial credentials.", exitAfter)
}
}
} else {
// the certificate is expired
if now.After(curr.Leaf.NotAfter) {
if clientCertificateManager.ServerHealthy() {
klog.Fatalf("The currently active client certificate has expired and the server is responsive, exiting.")
} else {
klog.Errorf("The currently active client certificate has expired, but the server is not responsive. A restart may be necessary to retrieve new initial credentials.")
}
}
lastCertAvailable = now
}
return cert, nil
}
lastCertAvailable := time.Now()
lastCert := clientCertificateManager.Current()
go wait.Until(func() {
curr := clientCertificateManager.Current()
if curr == nil || lastCert == curr {
// Cert hasn't been rotated.
return
}
lastCert = curr
if exitAfter > 0 {
now := time.Now()
if curr == nil {
// the certificate has been deleted from disk or is otherwise corrupt
if now.After(lastCertAvailable.Add(exitAfter)) {
if clientCertificateManager.ServerHealthy() {
glog.Fatalf("It has been %s since a valid client cert was found and the server is responsive, exiting.", exitAfter)
} else {
glog.Errorf("It has been %s since a valid client cert was found, but the server is not responsive. A restart may be necessary to retrieve new initial credentials.", exitAfter)
}
}
} else {
// the certificate is expired
if 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.")
}
}
lastCertAvailable = now
}
}
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
d.CloseAll()
}, period, stopCh)
}
klog.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
d.CloseAll()
}, period, stopCh)
clientConfig.Transport = utilnet.SetTransportDefaults(&http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
MaxIdleConnsPerHost: 25,
DialContext: d.DialContext, // Use custom dialer.
DialContext: d.DialContext,
})
// Zero out all existing TLS options since our new transport enforces them.
@ -144,5 +154,5 @@ func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig
clientConfig.CAFile = ""
clientConfig.Insecure = false
return d.CloseAll, nil
return nil
}