Skip to content

Commit

Permalink
Catch termination signal
Browse files Browse the repository at this point in the history
In order for termination signal to be captured, two goroutines
are created which handle TLS keypair reloading and HTTP server
instantiation.

Signed-off-by: Kennelly, Martin <[email protected]>
  • Loading branch information
martinkennelly committed Jan 19, 2021
1 parent a03c581 commit 46ed783
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 91 deletions.
121 changes: 32 additions & 89 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
package main

import (
"crypto/tls"
"flag"
"fmt"
"net/http"
"sync"
"time"

"github.com/fsnotify/fsnotify"
Expand Down Expand Up @@ -78,97 +76,42 @@ func main() {
glog.Fatalf("error in setting resource name keys: %s", err.Error())
}

go func() {
/* register handlers */
var httpServer *http.Server

http.HandleFunc("/mutate", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/mutate" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodPost {
http.Error(w, "Invalid HTTP verb requested", 405)
return
}
webhook.MutateHandler(w, r)
})

/* start serving */
httpServer = &http.Server{
Addr: fmt.Sprintf("%s:%d", *address, *port),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ReadHeaderTimeout: 1 * time.Second,
TLSConfig: &tls.Config{
ClientAuth: webhook.GetClientAuth(*insecure),
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384},
ClientCAs: clientCaPool.GetCertPool(),
PreferServerCipherSuites: true,
InsecureSkipVerify: false,
CipherSuites: []uint16{
// tls 1.2
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// tls 1.3 configuration not supported
},
GetCertificate: keyPair.GetCertificateFunc(),
},
}

err := httpServer.ListenAndServeTLS("", "")
if err != nil {
glog.Fatalf("error starting web server: %v", err)
}
}()

/* watch the cert file and restart http sever if the file updated. */
/* Two goroutines required - TLS keypair watcher and HTTP server */
/* Configure and start TLS keypair watcher */
tlsWatcherStop := make(chan struct{}, 1) // channel to signal TLS keypair watcher to stop
tlsWatcherDone := make(chan struct{}, 1) // channel to signal if TLS keypair watcher ended unexpectedly
wg := &sync.WaitGroup{}
watcher, err := fsnotify.NewWatcher()
if err != nil {
glog.Fatalf("error starting fsnotify watcher: %v", err)
}
defer watcher.Close()

certUpdated := false
keyUpdated := false

for {
watcher.Add(*cert)
watcher.Add(*key)

select {
case event, ok := <-watcher.Events:
if !ok {
continue
}
glog.Infof("watcher event: %v", event)
mask := fsnotify.Create | fsnotify.Rename | fsnotify.Remove |
fsnotify.Write | fsnotify.Chmod
if (event.Op & mask) != 0 {
glog.Infof("modified file: %v", event.Name)
if event.Name == *cert {
certUpdated = true
}
if event.Name == *key {
keyUpdated = true
}
if keyUpdated && certUpdated {
if err := keyPair.Reload(); err != nil {
glog.Fatalf("Failed to reload certificate: %v", err)
}
certUpdated = false
keyUpdated = false
}
}
case err, ok := <-watcher.Errors:
if !ok {
continue
}
glog.Infof("watcher error: %v", err)
}
go webhook.StartTLSFileWatcher(wg, tlsWatcherStop, tlsWatcherDone, *cert, *key, keyPair, watcher)

/* Configure and start HTTP server */
readTimeout := 5 * time.Second
writeTimeout := 10 * time.Second
readHeaderTimeout := 1 * time.Second
goroutineTimeout := 2 * time.Second
httpServer := webhook.ConfigureHTTPServer(*address, *port, *insecure, readTimeout, writeTimeout,
readHeaderTimeout, clientCaPool, keyPair)
srvDone := make(chan struct{}, 1) // channel to signal if HTTP server ended unexpectedly

go webhook.StartHTTPServer(wg, srvDone, httpServer)

/* Blocks until termination, TLS keypair or HTTP server signal occurs */
signal, err := webhook.WatchForSig(srvDone, tlsWatcherDone)
if err != nil {
glog.Fatalf("error waiting for signal to occur: '%s'", err.Error())
}

if err := webhook.StopGoroutines(signal, httpServer, tlsWatcherStop, goroutineTimeout); err != nil {
glog.Errorf("error stopping goroutine(s): '%s'", err.Error())
}

/* wait until goroutines end or timeout */
if webhook.IsTimeout(wg, goroutineTimeout) {
glog.Errorf("timeout waiting for goroutine(s) to end")
}
}
8 changes: 8 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ const (
Hugepages1GLimitPath = "hugepages_1G_limit"
Hugepages2MLimitPath = "hugepages_2M_limit"
)

type Signal int

const (
Watcher Signal = iota // TLS keypair watcher
Server // HTTP server
Term // Linux termination signal
)
Loading

0 comments on commit 46ed783

Please sign in to comment.