Skip to content

Commit

Permalink
Implement TLS certificate configuration (without certmagic)
Browse files Browse the repository at this point in the history
For systems on which certbot or other LetsEncrypt setups are already deployed,
re-using the existing certificates might be more convenient to set up.

When receiving SIGHUP, the certificates are reloaded. Sending SIGHUP can be done
from the certbot deploy-hook, for example.
  • Loading branch information
stapelberg committed Jul 18, 2021
1 parent bd4316e commit c4a33ec
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ type configAcmeLe struct {
ChallengePort int
}

type configTLS struct {
CertPath string
KeyPath string
}

type configVHost struct {
Domain string
Upstream string
Expand All @@ -64,6 +69,7 @@ type config struct {
Domain string
UseSMTPS bool
LetsEncrypt *configAcmeLe
TLS *configTLS
ReadTimeout time.Duration
WriteTimeout time.Duration
MaxMessageBytes int
Expand Down
60 changes: 60 additions & 0 deletions internal/tlsutil/tlsutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package tlsutil

import (
"crypto/tls"
"os"
"os/signal"
"sync"
"syscall"

log "github.com/sirupsen/logrus"
)

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() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
for range c {
log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", certPath, keyPath)
if err := result.maybeReload(); err != nil {
log.Printf("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
}
}
10 changes: 10 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
package main

import (
"crypto/tls"
"runtime"

"github.com/heroku/rollrus"
"github.com/mback2k/smtp-dkim-signer/internal/tlsutil"
"github.com/rollbar/rollbar-go"
"github.com/rollbar/rollbar-go/errors"

Expand All @@ -47,6 +49,14 @@ func setupServer(cfg *config) (*smtp.Server, bool) {
if err != nil {
panic(err)
}
} else if cfg.TLS != nil && cfg.TLS.KeyPath != "" && cfg.TLS.CertPath != "" {
kpr, err := tlsutil.NewKeypairReloader(cfg.TLS.CertPath, cfg.TLS.KeyPath)
if err != nil {
panic(err)
}
server.TLSConfig = &tls.Config{
GetCertificate: kpr.GetCertificateFunc(),
}
}
return server, cfg.UseSMTPS
}
Expand Down

0 comments on commit c4a33ec

Please sign in to comment.