Skip to content

Commit

Permalink
Watch cert files and reload on change
Browse files Browse the repository at this point in the history
The current webhook does not watch for cert renewal changes.
When the cert is renewed, the pod must be restarted to reload
the new cert.

This commit watches the cert files and reloads the cert when a
change is detected.

See: #135
  • Loading branch information
ycheng-kareo committed Feb 8, 2024
1 parent a6aaef3 commit 91a292d
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 2 deletions.
88 changes: 88 additions & 0 deletions admission-webhook/cert_reloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package main

import (
"crypto/tls"
"sync"

"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)

type CertReloader struct {
sync.Mutex
certPath string
keyPath string
certificate *tls.Certificate
}

func NewCertReloader(certPath, keyPath string) *CertReloader {
return &CertReloader{
certPath: certPath,
keyPath: keyPath,
}
}

// LoadCertificate loads or reloads the certificate from disk.
func (cr *CertReloader) LoadCertificate() (*tls.Certificate, error) {
cr.Lock()
defer cr.Unlock()

cert, err := tls.LoadX509KeyPair(cr.certPath, cr.keyPath)
if err != nil {
return nil, err
}
cr.certificate = &cert
return cr.certificate, nil
}

// GetCertificateFunc returns a function that can be assigned to tls.Config.GetCertificate
func (cr *CertReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
return cr.certificate, nil
}
}

func watchCertFiles(certReloader *CertReloader) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Errorf("error creating watcher: %v", err)
}
defer watcher.Close()

done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Rename == fsnotify.Rename {
logrus.Infof("detected change in certificate file: %v", event.Name)
_, err := certReloader.LoadCertificate()
if err != nil {
logrus.Errorf("error reloading certificate: %v", err)
} else {
logrus.Infof("successfully reloaded certificate")
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.Errorf("watcher error: %v", err)
}
}
}()

err = watcher.Add(certReloader.certPath)
if err != nil {
logrus.Errorf("error watching certificate file: %v", err)
}
err = watcher.Add(certReloader.keyPath)
if err != nil {
logrus.Errorf("error watching key file: %v", err)
}

<-done
}
52 changes: 52 additions & 0 deletions admission-webhook/cert_reloader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"os"
"testing"
)

// TestCertReloader tests the reloading functionality of the certificate.
func TestCertReloader(t *testing.T) {
// Create temporary cert and key files
tmpCertFile, err := os.CreateTemp("", "cert*.pem")
if err != nil {
t.Fatalf("Failed to create temp cert file: %v", err)
}
defer os.Remove(tmpCertFile.Name()) // clean up

tmpKeyFile, err := os.CreateTemp("", "key*.pem")
if err != nil {
t.Fatalf("Failed to create temp key file: %v", err)
}
defer os.Remove(tmpKeyFile.Name()) // clean up

// Write initial cert and key to temp files
initialCertData, _ := os.ReadFile("testdata/cert.pem")
if err := os.WriteFile(tmpCertFile.Name(), initialCertData, 0644); err != nil {
t.Fatalf("Failed to write to temp cert file: %v", err)
}

initialKeyData, _ := os.ReadFile("testdata/key.pem")
if err := os.WriteFile(tmpKeyFile.Name(), initialKeyData, 0644); err != nil {
t.Fatalf("Failed to write to temp key file: %v", err)
}

// Setup CertReloader with temp files
certReloader := NewCertReloader(tmpCertFile.Name(), tmpKeyFile.Name())
_, err = certReloader.LoadCertificate()
if err != nil {
t.Fatalf("Failed to load initial certificate: %v", err)
}

// Mocking a certificate change by writing new data to the files
newCertData, _ := os.ReadFile("testdata/cert.pem")
if err := os.WriteFile(tmpCertFile.Name(), newCertData, 0644); err != nil {
t.Fatalf("Failed to write new data to cert file: %v", err)
}

// Simulate reloading
_, err = certReloader.LoadCertificate()
if err != nil {
t.Fatalf("Failed to reload certificate: %v", err)
}
}
1 change: 1 addition & 0 deletions admission-webhook/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/kubernetes-sigs/windows-gmsa/admission-webhook
go 1.21

require (
github.com/fsnotify/fsnotify v1.7.0
github.com/mitchellh/go-homedir v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
Expand Down
2 changes: 2 additions & 0 deletions admission-webhook/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
Expand Down
29 changes: 29 additions & 0 deletions admission-webhook/testdata/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvGgAwIBAgIUDlTAXZUZ0DGcutGxcspszdn4lAowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDIwODA2MDAyN1oXDTM0MDIw
NTA2MDAyN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAg1kVSkS0tl8+VMBH8nVwfXy3yNgDS5a8uWAFv62O1vI4
KEOClNhPaFvihL7ubi3rhCmHoUfbIhe1MMqwV7mEXI+AVObs7Sf8feX5WkW6zJyF
AByH8O37fGFfEsISJBcMwmS5/gCShPfUuG2vr7Zg28brxU+Ixp1DhA87X+A+CEG0
JTH2LDiKMv86At/olH/IeDOH7j2tD25MThDN+xyKa9u2cpy73GcF822haUtFzmVX
mA8Mw4Qs5B2lMPY3k/C2UaqRDnNFu+0U011hvAGFA4+Jw4Cvpy13/4kQQZ0JHSOD
oy+jtbpqMQJn2oCMQ9DX0WQTS7E4W03y5gKS4v8xkUneAWuWoSwTm8TXRoAXbT3n
ZqDXmdy69ckLiLgn/w5uAeKjeHdk522QiJ2MHqYRLJbzUQ6LsrYdcR3nhh1pgh5K
tdnuz7HQtg77KR9g1X1aAT20SqV9rV85FwWI50dTfg8ehWXOSuXlZMlRUuMOn0a0
iAyw+rCbaLvmuXwPZxuk/PPW+4lWwE1OqHSjs3iHl/fZM5AfmVS9Av3n3JRD2hrA
2aoOnjiFSjI/9qzcjUl8LTvrzGFt3QWcVRDzMqNQW8qPvBFvxrRcZlPTElRM023p
TO8P3k4n08EGgY+dV9s3xC6dnIkyVp7b2UtEDAC6E53mI2e+wG5uYrKu4QSaM9MC
AwEAAaNTMFEwHQYDVR0OBBYEFCEw8jWYUa8ed+R5O9dxhUSVqBJwMB8GA1UdIwQY
MBaAFCEw8jWYUa8ed+R5O9dxhUSVqBJwMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggIBAAE7F7qZ4B5PzqydYRJ0Er39kdfEugs0L3/LYwHfQ4eJKedI
CNXnSW+2kh5kQW9YnXy77VewZ/wtaZyGndNNocRKlN6X2yr97HOtuysdSvHuNmUl
Dyk9brgPcRJK4YizO44DHuQmn0LUhxbeph2VjYxs6B5XEdD6aGFpljWNCHzuWqao
so3uF588lhudSPVkx9VEWHF/N5BKQeJr6gPy1BB8rlSkD8ImxHmq7ledV7ri0mCS
o5WO+17kaLBvyj5H8sN/M+zWPKMHLohe5BXFWwlgl8nGnaXNW0HaKhgyjGTZBZJe
u5kTVQnhTDUo706K4BC8Zz78L6Xcb44FMUIRF5yZ8iKN2M6mPmEQmrE6aKEmiNxc
j0Yfz5MGumog8goYEgOkxp49aI6zojOq7GskDVKo9NxPsfotASriDOhpe106F/yK
cboL1oeL3pAjgICgwdy2pNawjDVNt8cadU4RAxF+m0gpa/xAsGhlz5YKsdOv/7V8
Lb7KguUyYHmyBFwzJluhBrUWrGpPEKxdjrMbn/9G8b7AbXyV2w/9bwqkLvFU6qyJ
vuv4HpHIywsm9tST8p62RDVRbWlYfBwWIsnz4sraOPJXt8SU9QE3XI3MLw3iiEbs
1oToxKEj+6KbAgaC81cIkohZ+6RYnX+huhhe89Mgg0r7wWnt+huHiKLhGnm/
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions admission-webhook/testdata/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
Qq94HQjGtc+tQXYMH1mJfTWqcfyT
-----END PRIVATE KEY-----
16 changes: 14 additions & 2 deletions admission-webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
Expand All @@ -21,7 +22,6 @@ import (

type webhookOperation string

//
type gmsaResourceKind string

const (
Expand Down Expand Up @@ -77,7 +77,19 @@ func (webhook *webhook) start(port int, tlsConfig *tlsConfig, listeningChan chan
if tlsConfig == nil {
err = webhook.server.Serve(keepAliveListener)
} else {
err = webhook.server.ServeTLS(keepAliveListener, tlsConfig.crtPath, tlsConfig.keyPath)
certReloader := NewCertReloader(tlsConfig.crtPath, tlsConfig.keyPath)
_, err = certReloader.LoadCertificate()
if err != nil {
return err
}

go watchCertFiles(certReloader)

webhook.server.TLSConfig = &tls.Config{
GetCertificate: certReloader.GetCertificateFunc(),
}

err = webhook.server.ServeTLS(keepAliveListener, "", "")
}

if err != nil {
Expand Down

0 comments on commit 91a292d

Please sign in to comment.