added IMAP, LetsEncrypt, update layout
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package tlsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCertReloaderReload(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
certFile := filepath.Join(dir, "server.crt")
|
||||
keyFile := filepath.Join(dir, "server.key")
|
||||
|
||||
if err := GenerateSelfSignedCert(certFile, keyFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reloader, err := NewCertReloader(certFile, keyFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certA, err := reloader.GetCertificate(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Overwrite with a fresh cert (delete first, since GenerateSelfSignedCert is
|
||||
// skip-if-exists).
|
||||
if err := os.Remove(certFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Remove(keyFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := GenerateSelfSignedCert(certFile, keyFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := reloader.Reload(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certB, err := reloader.GetCertificate(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(certA.Certificate[0]) == string(certB.Certificate[0]) {
|
||||
t.Fatal("expected Reload to pick up a different certificate, got the same bytes")
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -92,3 +93,53 @@ func CreateSSLContext(certFile, keyFile string) (*tls.Config, error) {
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CertReloader holds the currently-active certificate behind a tls.Config's
|
||||
// GetCertificate hook, so a listener can pick up a newly-obtained/renewed certificate
|
||||
// (see internal/acmecert) without restarting the process.
|
||||
type CertReloader struct {
|
||||
certFile, keyFile string
|
||||
|
||||
mu sync.RWMutex
|
||||
cert *tls.Certificate
|
||||
}
|
||||
|
||||
// NewCertReloader loads certFile/keyFile once and returns a reloader ready to hand to
|
||||
// a tls.Config's GetCertificate field.
|
||||
func NewCertReloader(certFile, keyFile string) (*CertReloader, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CertReloader{certFile: certFile, keyFile: keyFile, cert: &cert}, nil
|
||||
}
|
||||
|
||||
// GetCertificate satisfies tls.Config.GetCertificate.
|
||||
func (r *CertReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.cert, nil
|
||||
}
|
||||
|
||||
// Reload re-reads certFile/keyFile from disk and atomically swaps the active
|
||||
// certificate. Called after a successful Let's Encrypt obtain/renew.
|
||||
func (r *CertReloader) Reload() error {
|
||||
cert, err := tls.LoadX509KeyPair(r.certFile, r.keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.cert = &cert
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewReloadableTLSConfig builds a tls.Config backed by reloader instead of a fixed
|
||||
// certificate — used by both the SMTP and IMAP implicit-TLS listeners so a single
|
||||
// Reload() call (self-signed regeneration or a Let's Encrypt renewal) updates both.
|
||||
func NewReloadableTLSConfig(reloader *CertReloader) *tls.Config {
|
||||
return &tls.Config{
|
||||
GetCertificate: reloader.GetCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user