Skip to content

Commit

Permalink
enable kubelet server to dynamically load tls certificate files
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangweikop committed Jun 6, 2024
1 parent 171882b commit 1b303fe
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 9 deletions.
67 changes: 67 additions & 0 deletions pkg/kubelet/certificate/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,26 @@ limitations under the License.
package certificate

import (
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math"
"net"
"sort"
"sync/atomic"
"time"

certificates "k8s.io/api/certificates/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/certificate"
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
"k8s.io/klog/v2"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/metrics"
netutils "k8s.io/utils/net"
Expand Down Expand Up @@ -234,3 +238,66 @@ func NewKubeletClientCertificateManager(

return m, nil
}

// NewKubeletServerCertificateDynamicFileManager creates a certificate manager based on reading and watching certificate and key files.
// The returned struct implements certificate.Manager interface, enabling using it like other CertificateManager in this package.
// But the struct doesn't communicate with API server to perform certificate request at all.
func NewKubeletServerCertificateDynamicFileManager(certFile, keyFile string) (certificate.Manager, error) {
c, err := dynamiccertificates.NewDynamicServingContentFromFiles("kubelet-server-cert-files", certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("unable to set up dynamic certificate manager for kubelet server cert files: %w", err)
}
m := &kubeletServerCertificateDynamicFileManager{
dynamicCertificateContent: c,
certFile: certFile,
keyFile: keyFile,
}
m.Enqueue()
c.AddListener(m)
return m, nil
}

// kubeletServerCertificateDynamicFileManager uses a dynamic CertKeyContentProvider based on cert and key files.
type kubeletServerCertificateDynamicFileManager struct {
cancelFn context.CancelFunc
certFile string
keyFile string
dynamicCertificateContent *dynamiccertificates.DynamicCertKeyPairContent
currentTLSCertificate atomic.Pointer[tls.Certificate]
}

// Enqueue implements the functions to be notified when the serving cert content changes.
func (m *kubeletServerCertificateDynamicFileManager) Enqueue() {
certContent, keyContent := m.dynamicCertificateContent.CurrentCertKeyContent()
cert, err := tls.X509KeyPair(certContent, keyContent)
if err != nil {
klog.ErrorS(err, "invalid certificate and key pair from file", "certFile", m.certFile, "keyFile", m.keyFile)
return
}
m.currentTLSCertificate.Store(&cert)
klog.V(4).InfoS("loaded certificate and key pair in kubelet server certificate manager", "certFile", m.certFile, "keyFile", m.keyFile)
}

// Current returns the last valid certificate key pair loaded from files.
func (m *kubeletServerCertificateDynamicFileManager) Current() *tls.Certificate {
return m.currentTLSCertificate.Load()
}

// Start starts watching the certificate and key files
func (m *kubeletServerCertificateDynamicFileManager) Start() {
var ctx context.Context
ctx, m.cancelFn = context.WithCancel(context.Background())
go m.dynamicCertificateContent.Run(ctx, 1)
}

// Stop stops watching the certificate and key files
func (m *kubeletServerCertificateDynamicFileManager) Stop() {
if m.cancelFn != nil {
m.cancelFn()
}
}

// ServerHealthy always returns true since the file manager doesn't communicate with any server
func (m *kubeletServerCertificateDynamicFileManager) ServerHealthy() bool {
return true
}
29 changes: 20 additions & 9 deletions pkg/kubelet/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,17 +774,28 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
}
klet.imageManager = imageManager

if kubeCfg.ServerTLSBootstrap && kubeDeps.TLSOptions != nil && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, klet.getLastObservedNodeAddresses, certDirectory)
if err != nil {
return nil, fmt.Errorf("failed to initialize certificate manager: %v", err)
if kubeDeps.TLSOptions != nil {
if kubeCfg.ServerTLSBootstrap && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, klet.getLastObservedNodeAddresses, certDirectory)
if err != nil {
return nil, fmt.Errorf("failed to initialize certificate manager: %w", err)
}

} else if kubeDeps.TLSOptions.CertFile != "" && kubeDeps.TLSOptions.KeyFile != "" && utilfeature.DefaultFeatureGate.Enabled(features.ReloadKubeletServerCertificateFile) {
klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateDynamicFileManager(kubeDeps.TLSOptions.CertFile, kubeDeps.TLSOptions.KeyFile)
if err != nil {
return nil, fmt.Errorf("failed to initialize file based certificate manager: %w", err)
}
}
kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := klet.serverCertificateManager.Current()
if cert == nil {
return nil, fmt.Errorf("no serving certificate available for the kubelet")

if klet.serverCertificateManager != nil {
kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := klet.serverCertificateManager.Current()
if cert == nil {
return nil, fmt.Errorf("no serving certificate available for the kubelet")
}
return cert, nil
}
return cert, nil
}
}

Expand Down

0 comments on commit 1b303fe

Please sign in to comment.