config: Reload TLS server certificates once in a minute

Use of inotify and possibly other mechanisms poses portability risks.
Notably, "cross-platform" abstractions such as fsnotify library remove
access to certain features that are important to use it correctly in
some cases e.g. it is preferable to listen only for IN_CLOSE_WRITE on
Linux instead of IN_MODIFY to prevent races and unexpected failures.

Pooling approach avoids such problems by either running reload code at a
different time than actual renewal or retrying later if parse fails.
With certificates being renewed before expiry (e.g. 1 week before) delay
is not a signficiant problem.

Closes #160.
This commit is contained in:
fox.cpp 2020-01-02 18:17:43 +03:00
parent a88a1a96b5
commit 14505f4de1
No known key found for this signature in database
GPG key ID: E76D97CCEDE90B6C
2 changed files with 21 additions and 16 deletions

View file

@ -71,17 +71,9 @@ You still need to make keys readable for maddy, though:
$ sudo setfacl -R -m u:maddy:rX /etc/letsencrypt/{live,archive} $ sudo setfacl -R -m u:maddy:rX /etc/letsencrypt/{live,archive}
``` ```
Additionally, it is a good idea to automatically restart maddy reloads TLS certificates from disk once in a minute so it will notice
maddy on certificate renewal. renewal. It is possible to force reload via `systemctl reload maddy` (or just
Put that into /etc/letsencrypt/renewal-hooks/post/restart: `killall -USR2 maddy`).
```shell
#!/bin/bash
systemctl restart maddy
```
And make it executable:
```
$ sudo chmod +x /etc/letsencrypt/renewal-hooks/post/restart
```
## First run ## First run

View file

@ -33,7 +33,7 @@ func (cfg *TLSConfig) Get() *tls.Config {
return cfg.cfg.Clone() return cfg.cfg.Clone()
} }
func (cfg *TLSConfig) read(m *Map, node *Node) error { func (cfg *TLSConfig) read(m *Map, node *Node, generateSelfSig bool) error {
cfg.l.Lock() cfg.l.Lock()
defer cfg.l.Unlock() defer cfg.l.Unlock()
@ -44,6 +44,10 @@ func (cfg *TLSConfig) read(m *Map, node *Node) error {
cfg.cfg = nil cfg.cfg = nil
return nil return nil
case "self_signed": case "self_signed":
if !generateSelfSig {
return nil
}
tlsCfg := &tls.Config{ tlsCfg := &tls.Config{
MinVersion: tls.VersionTLS10, MinVersion: tls.VersionTLS10,
MaxVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13,
@ -79,16 +83,25 @@ func TLSDirective(m *Map, node *Node) (interface{}, error) {
cfg := TLSConfig{ cfg := TLSConfig{
initCfg: node, initCfg: node,
} }
if err := cfg.read(m, node); err != nil { if err := cfg.read(m, node, true); err != nil {
return nil, err return nil, err
} }
hooks.AddHook(hooks.EventReload, func() { hooks.AddHook(hooks.EventReload, func() {
log.Debugln("reloading TLS configuration") log.Debugln("tls: reloading certificates")
if err := cfg.read(NewMap(nil, cfg.initCfg), cfg.initCfg); err != nil { if err := cfg.read(NewMap(nil, cfg.initCfg), cfg.initCfg, false); err != nil {
log.DefaultLogger.Error("failed to reload TLS config", err) log.DefaultLogger.Error("tls: failed to load new certs", err)
} }
}) })
go func() {
t := time.NewTicker(1 * time.Minute)
for range t.C {
log.Debugln("tls: reloading certificates")
if err := cfg.read(NewMap(nil, cfg.initCfg), cfg.initCfg, false); err != nil {
log.DefaultLogger.Error("tls: failed to load new certs", err)
}
}
}()
// Return nil so callers can check whether TLS is enabled easier. // Return nil so callers can check whether TLS is enabled easier.
if cfg.cfg == nil { if cfg.cfg == nil {