mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
use configured tls ca config for all tls connections, so https as well
and add documentation for developers for setting up certificates with manual local CA (with cfssl) or local ACME CA (with pebble).
This commit is contained in:
parent
47b88550be
commit
f60ad1452f
5 changed files with 175 additions and 4 deletions
|
@ -44,7 +44,7 @@ type Static struct {
|
||||||
CertFiles []string `sconf:"optional"`
|
CertFiles []string `sconf:"optional"`
|
||||||
} `sconf:"optional"`
|
} `sconf:"optional"`
|
||||||
CertPool *x509.CertPool `sconf:"-" json:"-"`
|
CertPool *x509.CertPool `sconf:"-" json:"-"`
|
||||||
} `sconf:"optional" sconf-doc:"Global TLS configuration, e.g. for additional Certificate Authorities."`
|
} `sconf:"optional" sconf-doc:"Global TLS configuration, e.g. for additional Certificate Authorities. Used for outgoing SMTP connections, HTTPS requests."`
|
||||||
ACME map[string]ACME `sconf:"optional" sconf-doc:"Automatic TLS configuration with ACME, e.g. through Let's Encrypt. The key is a name referenced in TLS configs, e.g. letsencrypt."`
|
ACME map[string]ACME `sconf:"optional" sconf-doc:"Automatic TLS configuration with ACME, e.g. through Let's Encrypt. The key is a name referenced in TLS configs, e.g. letsencrypt."`
|
||||||
AdminPasswordFile string `sconf:"optional" sconf-doc:"File containing hash of admin password, for authentication in the web admin pages (if enabled)."`
|
AdminPasswordFile string `sconf:"optional" sconf-doc:"File containing hash of admin password, for authentication in the web admin pages (if enabled)."`
|
||||||
Listeners map[string]Listener `sconf-doc:"Listeners are groups of IP addresses and services enabled on those IP addresses, such as SMTP/IMAP or internal endpoints for administration or Prometheus metrics. All listeners with SMTP/IMAP services enabled will serve all configured domains. If the listener is named 'public', it will get a few helpful additional configuration checks, for acme automatic tls certificates and monitoring of ips in dnsbls if those are configured."`
|
Listeners map[string]Listener `sconf-doc:"Listeners are groups of IP addresses and services enabled on those IP addresses, such as SMTP/IMAP or internal endpoints for administration or Prometheus metrics. All listeners with SMTP/IMAP services enabled will serve all configured domains. If the listener is named 'public', it will get a few helpful additional configuration checks, for acme automatic tls certificates and monitoring of ips in dnsbls if those are configured."`
|
||||||
|
|
|
@ -49,8 +49,8 @@ describe-static" and "mox config describe-domains":
|
||||||
# (optional)
|
# (optional)
|
||||||
CheckUpdates: false
|
CheckUpdates: false
|
||||||
|
|
||||||
# Global TLS configuration, e.g. for additional Certificate Authorities.
|
# Global TLS configuration, e.g. for additional Certificate Authorities. Used for
|
||||||
# (optional)
|
# outgoing SMTP connections, HTTPS requests. (optional)
|
||||||
TLS:
|
TLS:
|
||||||
|
|
||||||
# (optional)
|
# (optional)
|
||||||
|
|
158
develop.txt
Normal file
158
develop.txt
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
This file has notes useful for mox developers.
|
||||||
|
|
||||||
|
# TLS certificates
|
||||||
|
|
||||||
|
https://github.com/cloudflare/cfssl is useful for testing with TLS
|
||||||
|
certificates. Create a CA and configure it in mox.conf TLS.CA.CertFiles, and
|
||||||
|
sign host certificates and configure them in the listeners TLS.KeyCerts.
|
||||||
|
|
||||||
|
Setup a local CA with cfssl, run once:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go install github.com/cloudflare/cfssl/cmd/cfssl@latest
|
||||||
|
go install github.com/cloudflare/cfssl/cmd/cfssljson@latest
|
||||||
|
|
||||||
|
mkdir -p local/cfssl
|
||||||
|
cd local/cfssl
|
||||||
|
|
||||||
|
cfssl print-defaults config > ca-config.json # defaults are fine
|
||||||
|
|
||||||
|
# Based on: cfssl print-defaults csr > ca-csr.json
|
||||||
|
cat <<EOF >ca-csr.json
|
||||||
|
{
|
||||||
|
"CN": "mox ca",
|
||||||
|
"key": {
|
||||||
|
"algo": "ecdsa",
|
||||||
|
"size": 256
|
||||||
|
},
|
||||||
|
"names": [
|
||||||
|
{
|
||||||
|
"C": "NL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cfssl gencert -initca ca-csr.json | cfssljson -bare ca - # Generate ca key and cert.
|
||||||
|
|
||||||
|
# Generate wildcard certificates for one or more domains, add localhost for use with pebble, see below.
|
||||||
|
domains="moxtest.example localhost"
|
||||||
|
for domain in $domains; do
|
||||||
|
cat <<EOF >wildcard.$domain.csr.json
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"algo": "ecdsa",
|
||||||
|
"size": 256
|
||||||
|
},
|
||||||
|
"names": [
|
||||||
|
{
|
||||||
|
"O": "mox"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hosts": [
|
||||||
|
"$domain",
|
||||||
|
"*.$domain"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cfssl gencert -ca ca.pem -ca-key ca-key.pem -profile=www wildcard.$domain.csr.json | cfssljson -bare wildcard.$domain
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Now configure mox.conf to add the cfssl CA root certificate:
|
||||||
|
|
||||||
|
```
|
||||||
|
TLS:
|
||||||
|
CA:
|
||||||
|
AdditionalToSystem: true
|
||||||
|
CertFiles:
|
||||||
|
# Assuming local/<env>/config/mox.conf and local/cfssl/.
|
||||||
|
- ../../cfssl/ca.pem
|
||||||
|
|
||||||
|
[...]
|
||||||
|
|
||||||
|
Listeners:
|
||||||
|
public:
|
||||||
|
TLS:
|
||||||
|
KeyCerts:
|
||||||
|
# Assuming local/<env>/config/mox.conf and local/cfssl/.
|
||||||
|
CertFile: ../../cfssl/wildcard.$domain.pem
|
||||||
|
KeyFile: ../../cfssl/wildcard.$domain-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
# ACME
|
||||||
|
|
||||||
|
https://github.com/letsencrypt/pebble is useful for testing with ACME. Start a
|
||||||
|
pebble instance that uses the localhost TLS cert/key created by cfssl for its
|
||||||
|
TLS serving. Pebble generates a new CA certificate for its own use each time it
|
||||||
|
is started. Fetch it from https://localhost:14000/root, write it to a file, and
|
||||||
|
add it to mox.conf TLS.CA.CertFiles. See below.
|
||||||
|
|
||||||
|
Setup pebble, run once:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go install github.com/letsencrypt/pebble/cmd/pebble@latest
|
||||||
|
|
||||||
|
mkdir -p local/pebble
|
||||||
|
cat <<EOF >local/pebble/config.json
|
||||||
|
{
|
||||||
|
"pebble": {
|
||||||
|
"listenAddress": "localhost:14000",
|
||||||
|
"managementListenAddress": "localhost:15000",
|
||||||
|
"certificate": "local/cfssl/localhost.pem",
|
||||||
|
"privateKey": "local/cfssl/localhost-key.pem",
|
||||||
|
"httpPort": 80,
|
||||||
|
"tlsPort": 443,
|
||||||
|
"ocspResponderURL": "",
|
||||||
|
"externalAccountBindingRequired": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Start pebble, this generates a new temporary pebble CA certificate:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pebble -config local/pebble/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Write new CA bundle that includes pebble's temporary CA cert:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export CURL_CA_BUNDLE=local/ca-bundle.pem # for curl
|
||||||
|
export SSL_CERT_FILE=local/ca-bundle.pem # for go apps
|
||||||
|
cat /etc/ssl/certs/ca-certificates.crt local/cfssl/ca.pem >local/ca-bundle.pem
|
||||||
|
curl https://localhost:14000/root >local/pebble/ca.pem # fetch temp pebble ca, DO THIS EVERY TIME PEBBLE IS RESTARTED!
|
||||||
|
cat /etc/ssl/certs/ca-certificates.crt local/cfssl/ca.pem local/pebble/ca.pem >local/ca-bundle.pem # create new list that includes cfssl ca and temp pebble ca.
|
||||||
|
rm -r local/*/data/acme/keycerts/pebble # remove existing pebble-signed certs in acme cert/key cache, they are invalid due to newly generated temp pebble ca.
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit mox.conf, adding pebble ACME and its ca.pem:
|
||||||
|
|
||||||
|
```
|
||||||
|
ACME:
|
||||||
|
pebble:
|
||||||
|
DirectoryURL: https://localhost:14000/dir
|
||||||
|
ContactEmail: root@mox.example
|
||||||
|
TLS:
|
||||||
|
CA:
|
||||||
|
AdditionalToSystem: true
|
||||||
|
CertFiles:
|
||||||
|
# Assuming local/<env>/config/mox.conf and local/pebble/ca.pem and local/cfssl/ca.pem.
|
||||||
|
- ../../pebble/ca.pem
|
||||||
|
- ../../cfssl/ca.pem
|
||||||
|
|
||||||
|
[...]
|
||||||
|
|
||||||
|
Listeners:
|
||||||
|
public:
|
||||||
|
TLS:
|
||||||
|
ACME: pebble
|
||||||
|
```
|
||||||
|
|
||||||
|
For mail clients and browsers to accept pebble-signed certificates, you must add
|
||||||
|
the temporary pebble CA cert to their trusted root CA store each time pebble is
|
||||||
|
started (e.g. to your thunderbird/firefox testing profile). Pebble has no option
|
||||||
|
to not regenerate its CA certificate, presumably for fear of people using it for
|
||||||
|
non-testing purposes. Unfortunately, this also makes it inconvenient to use for
|
||||||
|
testing purposes.
|
|
@ -404,6 +404,7 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer,
|
||||||
Config: &tls.Config{
|
Config: &tls.Config{
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66
|
MinVersion: tls.VersionTLS12, // ../rfc/8996:31 ../rfc/8997:66
|
||||||
|
RootCAs: mox.Conf.Static.TLS.CertPool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
|
@ -600,7 +601,11 @@ func checkDomain(ctx context.Context, resolver dns.Resolver, dialer *net.Dialer,
|
||||||
if !strings.HasPrefix(line, "220 ") {
|
if !strings.HasPrefix(line, "220 ") {
|
||||||
return fmt.Errorf("SMTP STARTTLS response from remote not 220 OK: %q", strings.TrimSuffix(line, "\r\n"))
|
return fmt.Errorf("SMTP STARTTLS response from remote not 220 OK: %q", strings.TrimSuffix(line, "\r\n"))
|
||||||
}
|
}
|
||||||
tlsconn := tls.Client(conn, &tls.Config{ServerName: host})
|
config := &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
RootCAs: mox.Conf.Static.TLS.CertPool,
|
||||||
|
}
|
||||||
|
tlsconn := tls.Client(conn, config)
|
||||||
if err := tlsconn.HandshakeContext(cctx); err != nil {
|
if err := tlsconn.HandshakeContext(cctx); err != nil {
|
||||||
return fmt.Errorf("TLS handshake after SMTP STARTTLS: %s", err)
|
return fmt.Errorf("TLS handshake after SMTP STARTTLS: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
@ -344,6 +345,13 @@ func LoadConfig(ctx context.Context, checkACMEHosts bool) []error {
|
||||||
func SetConfig(c *Config) {
|
func SetConfig(c *Config) {
|
||||||
// Cannot just assign *c to Conf, it would copy the mutex.
|
// Cannot just assign *c to Conf, it would copy the mutex.
|
||||||
Conf = Config{c.Static, sync.Mutex{}, c.Log, sync.Mutex{}, c.Dynamic, c.dynamicMtime, c.DynamicLastCheck, c.accountDestinations}
|
Conf = Config{c.Static, sync.Mutex{}, c.Log, sync.Mutex{}, c.Dynamic, c.dynamicMtime, c.DynamicLastCheck, c.accountDestinations}
|
||||||
|
|
||||||
|
// If we have non-standard CA roots, use them for all HTTPS requests.
|
||||||
|
if Conf.Static.TLS.CertPool != nil {
|
||||||
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
|
RootCAs: Conf.Static.TLS.CertPool,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfig parses the static config at path p. If checkOnly is true, no changes
|
// ParseConfig parses the static config at path p. If checkOnly is true, no changes
|
||||||
|
|
Loading…
Reference in a new issue