Skip to content

Commit

Permalink
Merge pull request #14 from chaospuppy/13-certificate-reloading
Browse files Browse the repository at this point in the history
13 certificate reloading
  • Loading branch information
chaospuppy authored Mar 7, 2022
2 parents c4aec1e + 7da5680 commit 42f609b
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 28 deletions.
43 changes: 23 additions & 20 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*Package cmd root.go
Copyright © 2021 Tim Seagren
Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -27,7 +27,6 @@ import (

var (
tlsKey, tlsCert, httpPort string
insecure bool
)

// rootCmd represents the base command when called without any subcommands
Expand All @@ -37,28 +36,33 @@ var rootCmd = &cobra.Command{
Long: `A binary used as part of a webhook to replace the existing hostname of a Pod
image: field with the desired registry hostname.`,
Run: func(cmd *cobra.Command, args []string) {

if len(args) < 1 {
klog.Fatalf("Missing requred hostname positional argument")
}
hostname := args[0]
svr := server.NewHTTPServer(httpPort, hostname)

// TODO run additional validation on provided hostname to ensure it matches a registry regex
registryHostname := args[0]
svr := server.NewHTTPServer(httpPort, registryHostname, tlsCert, tlsKey)

idleConnsClosed := make(chan struct{})
go func() {
if err := server.RunHTTPServer(svr, tlsKey, tlsCert); err != nil {
klog.Errorf("Failed to listen and serve: %v", err)
// Listen for shutdown signal
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan

klog.Infof("Shutdown gracefully...")
if err := svr.Shutdown(context.Background()); err != nil {
klog.Error(err)
}
// Wait for graceful termination
close(idleConnsClosed)
}()
klog.Infof("Server listening on port: %v", httpPort)

// listen shutdown signal
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan

klog.Infof("Shutdown gracefully...")
if err := svr.Shutdown(context.Background()); err != nil {
klog.Error(err)
}

server.RunHTTPServer(svr)
klog.Infof("Server listening on port: %v", httpPort)
<-idleConnsClosed
},
}

Expand All @@ -69,8 +73,7 @@ func Execute() {
}

func init() {
rootCmd.PersistentFlags().BoolVar(&insecure, "--insecure", false, "TODO: Does nothing")
rootCmd.PersistentFlags().StringVar(&httpPort, "httpPort", "8443", "The port the webhook HTTP Server with listen on. Defaults to 8443")
rootCmd.PersistentFlags().StringVar(&tlsKey, "tls-key-file", "/etc/webhook/certs/key.pem", "Path to TLS key")
rootCmd.PersistentFlags().StringVar(&tlsCert, "tls-cert-file", "/etc/webhook/certs/cert.pem", "Path to TLS certificate")
rootCmd.PersistentFlags().StringVar(&tlsKey, "tlsKeyFile", "/etc/webhook/certs/key.pem", "Path to TLS key")
rootCmd.PersistentFlags().StringVar(&tlsCert, "tlsCertFile", "/etc/webhook/certs/cert.pem", "Path to TLS certificate")
}
77 changes: 69 additions & 8 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,92 @@
package server

import (
"crypto/tls"
"errors"
"fmt"
"github.com/chaospuppy/imageswap/pods"
"k8s.io/klog/v2"
"net/http"
"sync"
"time"
)

// RunHTTPServer runs a new instance of an http.Server over TLS using the provided paths to the TLS cert and key files
func RunHTTPServer(server *http.Server, tlsKey string, tlsCert string) error {
if err := server.ListenAndServeTLS(tlsCert, tlsKey); err != nil {
type keypairReloader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}

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
go func() {
// TODO: Consider replacing with syscall.SIGHUP channel - send upon updates to TLS secret
ticker := time.NewTicker(60 * time.Second)
for range ticker.C {
klog.Infof("Reloading TLS certificate and key from %s and %s", certPath, keyPath)
if err := result.maybeReload(); err != nil {
klog.Infof("Keeping old TLS certificate because the new one could not be loaded: %v", err)
}
}
}()
return result, nil
}

func (kpr *keypairReloader) maybeReload() 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
}

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
}
}

// RunHTTPServer runs a new instance of an http.Server over TLS using the provided paths to the TLS cert and key files
func RunHTTPServer(server *http.Server) {
if err := server.ListenAndServeTLS("", ""); !errors.Is(err, http.ErrServerClosed) {
klog.Fatalf("Failed to listen and serve: %v", err)
}
}

// NewHTTPServer returns a new instance of the *http.Server used to serve the imageswap webhook endpoints
func NewHTTPServer(port string, hostname string) *http.Server {
func NewHTTPServer(port, registryHostname, tlsCert, tlsKey string) *http.Server {
kpr, err := newKeypairReloader(tlsCert, tlsKey)
if err != nil {
klog.Fatal(err)
}

// Instances hooks
podsMutation := pods.NewMutationHook(hostname)
tlsConfig := &tls.Config{}
tlsConfig.GetCertificate = kpr.GetCertificateFunc()

// Instances hooks
podsMutation := pods.NewMutationHook(registryHostname)
ah := newAdmissionHandler()
mux := http.NewServeMux()

mux.Handle("/healthz", healthz())
mux.Handle("/mutate", ah.Serve(podsMutation))
return &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: mux,
Addr: fmt.Sprintf(":%s", port),
Handler: mux,
TLSConfig: tlsConfig,
}
}

0 comments on commit 42f609b

Please sign in to comment.