mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
also configure acme validation with http-01, and fix a bug that caused tls cert refresh at startup to not always run
we already do acme tls-alpn-01 validation, and still require it (we could relax this at some point). http-01 is easy to add. the bug was that the list of acme managers and hosts to refresh was overwritten by another listener. the listeners are a map, and we range over it, so the order we handle them is random. if the public listener was handled first, and an internal handler later, the list was reset again.
This commit is contained in:
parent
f60ad1452f
commit
bddc8e4062
3 changed files with 28 additions and 15 deletions
|
@ -2,11 +2,9 @@
|
||||||
// requesting certificates with ACME, typically from Let's Encrypt.
|
// requesting certificates with ACME, typically from Let's Encrypt.
|
||||||
package autotls
|
package autotls
|
||||||
|
|
||||||
// We only do tls-alpn-01. For http-01, we would have to start another
|
// We do tls-alpn-01, and also http-01. For DNS we would need a third party tool
|
||||||
// listener. For DNS we would need a third party tool with an API that can make
|
// with an API that can make the DNS changes, as we don't want to link in dozens of
|
||||||
// the DNS changes, as we don't want to link in dozens of bespoke API's for DNS
|
// bespoke API's for DNS record manipulation into mox.
|
||||||
// record manipulation into mox. We can do http-01 relatively easily. It could
|
|
||||||
// be useful to not depend on a single mechanism.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -272,6 +270,12 @@ func (m *Manager) HostPolicy(ctx context.Context, host string) (rerr error) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xhost, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil {
|
||||||
|
// For http-01, host may include a port number.
|
||||||
|
host = xhost
|
||||||
|
}
|
||||||
|
|
||||||
d, err := dns.ParseDomain(host)
|
d, err := dns.ParseDomain(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid host: %v", err)
|
return fmt.Errorf("invalid host: %v", err)
|
||||||
|
|
|
@ -36,6 +36,9 @@ func TestAutotls(t *testing.T) {
|
||||||
if err := m.HostPolicy(context.Background(), "mox.example"); err != nil {
|
if err := m.HostPolicy(context.Background(), "mox.example"); err != nil {
|
||||||
t.Fatalf("hostpolicy, got err %v, expected no error", err)
|
t.Fatalf("hostpolicy, got err %v, expected no error", err)
|
||||||
}
|
}
|
||||||
|
if err := m.HostPolicy(context.Background(), "mox.example:80"); err != nil {
|
||||||
|
t.Fatalf("hostpolicy, got err %v, expected no error", err)
|
||||||
|
}
|
||||||
if err := m.HostPolicy(context.Background(), "other.mox.example"); err == nil || !errors.Is(err, errHostNotAllowed) {
|
if err := m.HostPolicy(context.Background(), "other.mox.example"); err == nil || !errors.Is(err, errHostNotAllowed) {
|
||||||
t.Fatalf("hostpolicy, got err %v, expected errHostNotAllowed", err)
|
t.Fatalf("hostpolicy, got err %v, expected errHostNotAllowed", err)
|
||||||
}
|
}
|
||||||
|
|
26
http/web.go
26
http/web.go
|
@ -189,7 +189,7 @@ type pathHandler struct {
|
||||||
Handle http.HandlerFunc
|
Handle http.HandlerFunc
|
||||||
}
|
}
|
||||||
type serve struct {
|
type serve struct {
|
||||||
Kinds []string // Type of handler and protocol (http/https).
|
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
PathHandlers []pathHandler // Sorted, longest first.
|
PathHandlers []pathHandler // Sorted, longest first.
|
||||||
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
|
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
|
||||||
|
@ -319,7 +319,7 @@ func Listen() {
|
||||||
|
|
||||||
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
||||||
port := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
port := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
||||||
ensureServe(true, port, "acme-tls-alpn01")
|
ensureServe(true, port, "acme-tls-alpn-01")
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.AccountHTTP.Enabled {
|
if l.AccountHTTP.Enabled {
|
||||||
|
@ -405,16 +405,16 @@ func Listen() {
|
||||||
srv.Webserver = true
|
srv.Webserver = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll explicitly ensure these TLS certs exist (e.g. are created with ACME)
|
|
||||||
// immediately after startup. We only do so for our explicit listener hostnames,
|
|
||||||
// not for mta-sts DNS records, it can be requested on demand (perhaps never). We
|
|
||||||
// do request autoconfig, otherwise clients may run into their timeouts waiting for
|
|
||||||
// the certificate to be given during the first https connection.
|
|
||||||
ensureManagerHosts = map[*autotls.Manager]map[dns.Domain]struct{}{}
|
|
||||||
|
|
||||||
if l.TLS != nil && l.TLS.ACME != "" {
|
if l.TLS != nil && l.TLS.ACME != "" {
|
||||||
m := mox.Conf.Static.ACME[l.TLS.ACME].Manager
|
m := mox.Conf.Static.ACME[l.TLS.ACME].Manager
|
||||||
|
|
||||||
|
// If we are listening on port 80 for plain http, also register acme http-01
|
||||||
|
// validation handler.
|
||||||
|
if srv, ok := portServe[80]; ok && srv.TLSConfig == nil {
|
||||||
|
srv.Kinds = append(srv.Kinds, "acme-http-01")
|
||||||
|
srv.HandleFunc("acme-http-01", nil, "/.well-known/acme-challenge/", m.Manager.HTTPHandler(nil).ServeHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
hosts := map[dns.Domain]struct{}{
|
hosts := map[dns.Domain]struct{}{
|
||||||
mox.Conf.Static.HostnameDomain: {},
|
mox.Conf.Static.HostnameDomain: {},
|
||||||
}
|
}
|
||||||
|
@ -467,7 +467,13 @@ func adminIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// functions to be launched in goroutine that will serve on a listener.
|
// functions to be launched in goroutine that will serve on a listener.
|
||||||
var servers []func()
|
var servers []func()
|
||||||
var ensureManagerHosts map[*autotls.Manager]map[dns.Domain]struct{}
|
|
||||||
|
// We'll explicitly ensure these TLS certs exist (e.g. are created with ACME)
|
||||||
|
// immediately after startup. We only do so for our explicit listener hostnames,
|
||||||
|
// not for mta-sts DNS records, it can be requested on demand (perhaps never). We
|
||||||
|
// do request autoconfig, otherwise clients may run into their timeouts waiting for
|
||||||
|
// the certificate to be given during the first https connection.
|
||||||
|
var ensureManagerHosts = map[*autotls.Manager]map[dns.Domain]struct{}{}
|
||||||
|
|
||||||
// listen prepares a listener, and adds it to "servers", to be launched (if not running as root) through Serve.
|
// listen prepares a listener, and adds it to "servers", to be launched (if not running as root) through Serve.
|
||||||
func listen1(ip string, port int, tlsConfig *tls.Config, name string, kinds []string, handler http.Handler) {
|
func listen1(ip string, port int, tlsConfig *tls.Config, name string, kinds []string, handler http.Handler) {
|
||||||
|
|
Loading…
Reference in a new issue