1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

chasquid: De-couple TLS certificates from domains

Having the certificates inside the domain directory may cause some confusion,
as it's possible they're not for the same name (they should be for the MX we
serve as, not the domain itself).

So it's not a problem if we have domains with no certificates (we could be
their MX with another name), and we could have more than one certificate per
"domain" (if we act as MXs with different names).

So this patch moves the certificates out of the domains into a new certs/
directory, where we do a one-level deep lookup for the files.

While at it, change the names of the files to "fullchain.pem" and
"privkey.pem", which match the names generated by the letsencrypt client, to
make it easier to set up.  There's no general convention for these names
anyway.
This commit is contained in:
Alberto Bertogli
2016-10-01 13:54:09 +01:00
parent 04dd8b9534
commit e138f0dc05
7 changed files with 58 additions and 52 deletions

View File

@@ -77,18 +77,31 @@ func main() {
s.aliasesR.SuffixSep = conf.SuffixSeparators s.aliasesR.SuffixSep = conf.SuffixSeparators
s.aliasesR.DropChars = conf.DropCharacters s.aliasesR.DropChars = conf.DropCharacters
// Load domains. // Load certificates from "certs/<directory>/{fullchain,privkey}.pem".
// They live inside the config directory, so the relative path works. // The structure matches letsencrypt's, to make it easier for that case.
domainDirs, err := ioutil.ReadDir("domains/") glog.Infof("Loading certificates")
if err != nil { for _, info := range mustReadDir("certs/") {
glog.Fatalf("Error reading domains/ directory: %v", err) name := info.Name()
} glog.Infof(" %s", name)
if len(domainDirs) == 0 {
glog.Fatalf("No domains found in config") certPath := filepath.Join("certs/", name, "fullchain.pem")
if _, err := os.Stat(certPath); os.IsNotExist(err) {
continue
}
keyPath := filepath.Join("certs/", name, "privkey.pem")
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
continue
}
err := s.AddCerts(certPath, keyPath)
if err != nil {
glog.Fatalf(" %v", err)
}
} }
// Load domains from "domains/".
glog.Infof("Domain config paths:") glog.Infof("Domain config paths:")
for _, info := range domainDirs { for _, info := range mustReadDir("domains/") {
name := info.Name() name := info.Name()
dir := filepath.Join("domains", name) dir := filepath.Join("domains", name)
loadDomain(name, dir, s) loadDomain(name, dir, s)
@@ -147,7 +160,6 @@ func loadDomain(name, dir string, s *Server) {
glog.Infof(" %s", name) glog.Infof(" %s", name)
s.AddDomain(name) s.AddDomain(name)
s.aliasesR.AddDomain(name) s.aliasesR.AddDomain(name)
s.AddCerts(dir+"/cert.pem", dir+"/key.pem")
if _, err := os.Stat(dir + "/users"); err == nil { if _, err := os.Stat(dir + "/users"); err == nil {
glog.Infof(" adding users") glog.Infof(" adding users")
@@ -185,6 +197,19 @@ func setupSignalHandling() {
}() }()
} }
// Read a directory, which must have at least some entries.
func mustReadDir(path string) []os.FileInfo {
dirs, err := ioutil.ReadDir(path)
if err != nil {
glog.Fatalf("Error reading %q directory: %v", path, err)
}
if len(dirs) == 0 {
glog.Fatalf("No entries found in %q", path)
}
return dirs
}
// Mode for a socket (listening or connection). // Mode for a socket (listening or connection).
// We keep them distinct, as policies can differ between them. // We keep them distinct, as policies can differ between them.
type SocketMode string type SocketMode string
@@ -201,16 +226,13 @@ type Server struct {
// Maximum data size. // Maximum data size.
MaxDataSize int64 MaxDataSize int64
// Certificate and key pairs.
certs, keys []string
// Addresses. // Addresses.
addrs map[SocketMode][]string addrs map[SocketMode][]string
// Listeners (that came via systemd). // Listeners (that came via systemd).
listeners map[SocketMode][]net.Listener listeners map[SocketMode][]net.Listener
// TLS config. // TLS config (including loaded certificates).
tlsConfig *tls.Config tlsConfig *tls.Config
// Local domains. // Local domains.
@@ -236,6 +258,7 @@ func NewServer() *Server {
return &Server{ return &Server{
addrs: map[SocketMode][]string{}, addrs: map[SocketMode][]string{},
listeners: map[SocketMode][]net.Listener{}, listeners: map[SocketMode][]net.Listener{},
tlsConfig: &tls.Config{},
connTimeout: 20 * time.Minute, connTimeout: 20 * time.Minute,
commandTimeout: 1 * time.Minute, commandTimeout: 1 * time.Minute,
localDomains: &set.String{}, localDomains: &set.String{},
@@ -244,9 +267,13 @@ func NewServer() *Server {
} }
} }
func (s *Server) AddCerts(cert, key string) { func (s *Server) AddCerts(certPath, keyPath string) error {
s.certs = append(s.certs, cert) cert, err := tls.LoadX509KeyPair(certPath, keyPath)
s.keys = append(s.keys, key) if err != nil {
return err
}
s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert)
return nil
} }
func (s *Server) AddAddr(a string, m SocketMode) { func (s *Server) AddAddr(a string, m SocketMode) {
@@ -292,31 +319,10 @@ func (s *Server) periodicallyReload() {
} }
} }
func (s *Server) getTLSConfig() (*tls.Config, error) {
var err error
conf := &tls.Config{}
conf.Certificates = make([]tls.Certificate, len(s.certs))
for i := 0; i < len(s.certs); i++ {
conf.Certificates[i], err = tls.LoadX509KeyPair(s.certs[i], s.keys[i])
if err != nil {
return nil, err
}
}
conf.BuildNameToCertificate()
return conf, nil
}
func (s *Server) ListenAndServe() { func (s *Server) ListenAndServe() {
var err error // At this point the TLS config should be done, build the
// name->certificate map (used by the TLS library for SNI).
// Configure TLS. s.tlsConfig.BuildNameToCertificate()
s.tlsConfig, err = s.getTLSConfig()
if err != nil {
glog.Fatalf("Error loading TLS config: %v", err)
}
for m, addrs := range s.addrs { for m, addrs := range s.addrs {
for _, addr := range addrs { for _, addr := range addrs {

View File

@@ -4,7 +4,7 @@ host testserver
port 1587 port 1587
tls on tls on
tls_trust_file config/domains/testserver/cert.pem tls_trust_file config/certs/testserver/fullchain.pem
from user@testserver from user@testserver

View File

@@ -4,7 +4,7 @@ host srv-chasquid
port 1587 port 1587
tls on tls on
tls_trust_file config/domains/srv-chasquid/cert.pem tls_trust_file config/certs/srv-chasquid/fullchain.pem
from user@srv-chasquid from user@srv-chasquid

View File

@@ -4,7 +4,7 @@ host testserver
port 1587 port 1587
tls on tls on
tls_trust_file config/domains/testserver/cert.pem tls_trust_file config/certs/testserver/fullchain.pem
from user@testserver from user@testserver

View File

@@ -4,7 +4,7 @@ host testserver
port 1587 port 1587
tls on tls on
tls_trust_file config/domains/testserver/cert.pem tls_trust_file config/certs/testserver/fullchain.pem
from user@testserver from user@testserver

View File

@@ -142,16 +142,16 @@ func main() {
log.Fatalf("Failed to create certificate: %s", err) log.Fatalf("Failed to create certificate: %s", err)
} }
certOut, err := os.Create("cert.pem") certOut, err := os.Create("fullchain.pem")
if err != nil { if err != nil {
log.Fatalf("failed to open cert.pem for writing: %s", err) log.Fatalf("failed to open fullchain.pem for writing: %s", err)
} }
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close() certOut.Close()
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) keyOut, err := os.OpenFile("privkey.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.Fatalf("failed to open key.pem for writing:", err) log.Fatalf("failed to open privkey.pem for writing:", err)
return return
} }
pem.Encode(keyOut, pemBlockForKey(priv)) pem.Encode(keyOut, pemBlockForKey(priv))

View File

@@ -80,11 +80,11 @@ function wait_for_file() {
done done
} }
# Generate certs for the given domain. # Generate certs for the given hostname.
function generate_certs_for() { function generate_certs_for() {
mkdir -p config/domains/${1} mkdir -p config/certs/${1}/
( (
cd config/domains/${1} cd config/certs/${1}
generate_cert -ca -duration=1h -host=${1} generate_cert -ca -duration=1h -host=${1}
) )
} }