Skip to content

Commit

Permalink
Merge pull request #26 from uswitch/cert-reload
Browse files Browse the repository at this point in the history
Add functionality to dynamically reloading of certs
  • Loading branch information
surajnarwade authored Jul 20, 2020
2 parents e2d5e22 + 3ddab0b commit fd8c349
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 5 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
k8s.io/api v0.15.12
k8s.io/apimachinery v0.15.12
Expand Down
14 changes: 9 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func main() {
kingpin.Parse()
log.SetOutput(os.Stderr)

pair, err := tls.LoadX509KeyPair("/etc/webhook/certs/cert.pem", "/etc/webhook/certs/key.pem")
// load certs
kpr, err := NewKeypairReloader("/etc/webhook/certs/cert.pem", "/etc/webhook/certs/key.pem")
if err != nil {
log.Errorf("Failed to load key pair: %v", err)
}
Expand All @@ -56,11 +57,14 @@ func main() {

watcher := NewListWatch(webhookClient)

srv := http.Server{Addr: fmt.Sprintf(":443")}

// this will check if there are new certs before every tls handshake
t := &tls.Config{GetCertificate: kpr.GetCertificateFunc()}
srv.TLSConfig = t

whsvr := webHookServer{
server: &http.Server{
Addr: fmt.Sprintf(":443"),
TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}},
},
server: &srv,
client: client,
bindings: watcher,
}
Expand Down
89 changes: 89 additions & 0 deletions tlsutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"crypto/tls"
"log"
"sync"

"gopkg.in/fsnotify.v1"
)

// KeypairReloader structs holds cert path and certs
type KeypairReloader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}

// NewKeypairReloader will load certs on first run and trigger a goroutine for fsnotify watcher
func NewKeypairReloader(certPath, keyPath string) (*KeypairReloader, error) {
result := &KeypairReloader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
result.cert = &cert

// creates a new file watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}

defer func() {
if err != nil {
watcher.Close()
}
}()

if err := watcher.Add("/etc/webhook/certs"); err != nil {
return nil, err
}

go func() {
for {
select {
// watch for events
case event := <-watcher.Events:
// fsnotify.create events will tell us if there are new certs
if event.Op&fsnotify.Create == fsnotify.Create {
log.Printf("Reloading certs")
if err := result.reload(); err != nil {
log.Printf("Could not load new certs: %v", err)
}
}

// watch for errors
case err := <-watcher.Errors:
log.Print("error", err)
}
}
}()

return result, nil
}

// reload loads updated cert and key whenever they are updated
func (kpr *KeypairReloader) reload() error {
newCert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
defer kpr.certMu.Unlock()
kpr.cert = &newCert
return nil
}

// GetCertificateFunc will return function which will be used as tls.Config.GetCertificate
func (kpr *KeypairReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}

0 comments on commit fd8c349

Please sign in to comment.