transport.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package certificate
  14. import (
  15. "crypto/tls"
  16. "fmt"
  17. "net"
  18. "net/http"
  19. "time"
  20. "k8s.io/klog"
  21. utilnet "k8s.io/apimachinery/pkg/util/net"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. restclient "k8s.io/client-go/rest"
  24. "k8s.io/client-go/util/certificate"
  25. "k8s.io/client-go/util/connrotation"
  26. )
  27. // UpdateTransport instruments a restconfig with a transport that dynamically uses
  28. // certificates provided by the manager for TLS client auth.
  29. //
  30. // The config must not already provide an explicit transport.
  31. //
  32. // The returned function allows forcefully closing all active connections.
  33. //
  34. // The returned transport periodically checks the manager to determine if the
  35. // certificate has changed. If it has, the transport shuts down all existing client
  36. // connections, forcing the client to re-handshake with the server and use the
  37. // new certificate.
  38. //
  39. // The exitAfter duration, if set, will terminate the current process if a certificate
  40. // is not available from the store (because it has been deleted on disk or is corrupt)
  41. // or if the certificate has expired and the server is responsive. This allows the
  42. // process parent or the bootstrap credentials an opportunity to retrieve a new initial
  43. // certificate.
  44. //
  45. // stopCh should be used to indicate when the transport is unused and doesn't need
  46. // to continue checking the manager.
  47. func UpdateTransport(stopCh <-chan struct{}, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration) (func(), error) {
  48. return updateTransport(stopCh, 10*time.Second, clientConfig, clientCertificateManager, exitAfter)
  49. }
  50. // updateTransport is an internal method that exposes how often this method checks that the
  51. // client cert has changed.
  52. func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration) (func(), error) {
  53. if clientConfig.Transport != nil || clientConfig.Dial != nil {
  54. return nil, fmt.Errorf("there is already a transport or dialer configured")
  55. }
  56. d := connrotation.NewDialer((&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext)
  57. if clientCertificateManager != nil {
  58. if err := addCertRotation(stopCh, period, clientConfig, clientCertificateManager, exitAfter, d); err != nil {
  59. return nil, err
  60. }
  61. } else {
  62. clientConfig.Dial = d.DialContext
  63. }
  64. return d.CloseAll, nil
  65. }
  66. func addCertRotation(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration, d *connrotation.Dialer) error {
  67. tlsConfig, err := restclient.TLSConfigFor(clientConfig)
  68. if err != nil {
  69. return fmt.Errorf("unable to configure TLS for the rest client: %v", err)
  70. }
  71. if tlsConfig == nil {
  72. tlsConfig = &tls.Config{}
  73. }
  74. tlsConfig.Certificates = nil
  75. tlsConfig.GetClientCertificate = func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
  76. cert := clientCertificateManager.Current()
  77. if cert == nil {
  78. return &tls.Certificate{Certificate: nil}, nil
  79. }
  80. return cert, nil
  81. }
  82. lastCertAvailable := time.Now()
  83. lastCert := clientCertificateManager.Current()
  84. go wait.Until(func() {
  85. curr := clientCertificateManager.Current()
  86. if exitAfter > 0 {
  87. now := time.Now()
  88. if curr == nil {
  89. // the certificate has been deleted from disk or is otherwise corrupt
  90. if now.After(lastCertAvailable.Add(exitAfter)) {
  91. if clientCertificateManager.ServerHealthy() {
  92. klog.Fatalf("It has been %s since a valid client cert was found and the server is responsive, exiting.", exitAfter)
  93. } else {
  94. 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)
  95. }
  96. }
  97. } else {
  98. // the certificate is expired
  99. if now.After(curr.Leaf.NotAfter) {
  100. if clientCertificateManager.ServerHealthy() {
  101. klog.Fatalf("The currently active client certificate has expired and the server is responsive, exiting.")
  102. } else {
  103. 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.")
  104. }
  105. }
  106. lastCertAvailable = now
  107. }
  108. }
  109. if curr == nil || lastCert == curr {
  110. // Cert hasn't been rotated.
  111. return
  112. }
  113. lastCert = curr
  114. klog.Infof("certificate rotation detected, shutting down client connections to start using new credentials")
  115. // The cert has been rotated. Close all existing connections to force the client
  116. // to reperform its TLS handshake with new cert.
  117. //
  118. // See: https://github.com/kubernetes-incubator/bootkube/pull/663#issuecomment-318506493
  119. d.CloseAll()
  120. }, period, stopCh)
  121. clientConfig.Transport = utilnet.SetTransportDefaults(&http.Transport{
  122. Proxy: http.ProxyFromEnvironment,
  123. TLSHandshakeTimeout: 10 * time.Second,
  124. TLSClientConfig: tlsConfig,
  125. MaxIdleConnsPerHost: 25,
  126. DialContext: d.DialContext,
  127. })
  128. // Zero out all existing TLS options since our new transport enforces them.
  129. clientConfig.CertData = nil
  130. clientConfig.KeyData = nil
  131. clientConfig.CertFile = ""
  132. clientConfig.KeyFile = ""
  133. clientConfig.CAData = nil
  134. clientConfig.CAFile = ""
  135. clientConfig.Insecure = false
  136. clientConfig.NextProtos = nil
  137. return nil
  138. }