From 6c16b4dc43a10390e15926880d52017a4eb57686 Mon Sep 17 00:00:00 2001 From: Christian Hoffmeister Date: Tue, 15 Aug 2023 11:57:14 +0200 Subject: [PATCH] Add reloading of changed TLS key/cert --- deploy/kubernetes/deployment.yaml | 2 +- internal/certloader.go | 35 +++++++++++++++++++++++++++++++ internal/service.go | 21 +++++++++++++++++-- test/examples/deployment.yaml | 1 + 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 internal/certloader.go diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index d6fbd67..a0d99d7 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -15,6 +15,7 @@ spec: labels: app: kube-resourceless spec: + terminationGracePeriodSeconds: 3 containers: - name: kube-resourceless image: ghcr.io/airfocusio/kube-resourceless:latest @@ -22,7 +23,6 @@ spec: - name: tls mountPath: "/etc/certs" readOnly: true - terminationGracePeriodSeconds: 3 volumes: - name: tls secret: diff --git a/internal/certloader.go b/internal/certloader.go new file mode 100644 index 0000000..fe827bc --- /dev/null +++ b/internal/certloader.go @@ -0,0 +1,35 @@ +// see https://stackoverflow.com/questions/37473201/is-there-a-way-to-update-the-tls-certificates-in-a-net-http-server-without-any-d +package internal + +import ( + "crypto/tls" + "fmt" + "os" + "time" +) + +type CertLoader struct { + CertFile string + KeyFile string + cachedCert *tls.Certificate + cachedCertModTime time.Time +} + +func (cr *CertLoader) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { + stat, err := os.Stat(cr.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed checking key file modification time: %w", err) + } + + if cr.cachedCert == nil || stat.ModTime().After(cr.cachedCertModTime) { + pair, err := tls.LoadX509KeyPair(cr.CertFile, cr.KeyFile) + if err != nil { + return nil, fmt.Errorf("failed loading tls key pair: %w", err) + } + + cr.cachedCert = &pair + cr.cachedCertModTime = stat.ModTime() + } + + return cr.cachedCert, nil +} diff --git a/internal/service.go b/internal/service.go index 4b5336c..a093674 100644 --- a/internal/service.go +++ b/internal/service.go @@ -2,6 +2,7 @@ package internal import ( "context" + "crypto/tls" "encoding/json" "fmt" "io/ioutil" @@ -76,14 +77,30 @@ func NewService(opts ServiceOpts) (*Service, error) { } func (s *Service) Run(stop <-chan os.Signal) error { + Info.Printf("starting server...\n") + + certLoader := CertLoader{ + CertFile: s.Opts.TLSCertFile, + KeyFile: s.Opts.TLSKeyFile, + } + tlsConfig := &tls.Config{ + GetCertificate: certLoader.GetCertificate, + } + tlsListener, err := tls.Listen("tcp", ":8443", tlsConfig) + if err != nil { + Error.Printf("listening failed: %v\n", err) + return err + } + http.HandleFunc("/mutate", func(w http.ResponseWriter, r *http.Request) { s.ServeAdmitHandler(w, r, AdmitHandler(s.Mutate)) }) - Info.Printf("starting server...\n") - if err := http.ListenAndServeTLS(":8443", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, nil); err != nil { + + if err := http.Serve(tlsListener, nil); err != nil { Error.Printf("starting server failed: %v\n", err) return err } + return nil } diff --git a/test/examples/deployment.yaml b/test/examples/deployment.yaml index 92967fb..50ec3d0 100644 --- a/test/examples/deployment.yaml +++ b/test/examples/deployment.yaml @@ -14,6 +14,7 @@ spec: labels: app: deplpoyment spec: + terminationGracePeriodSeconds: 1 containers: - name: container-1 image: nginx:alpine