rio/internal/server/tls.go

130 lines
3.4 KiB
Go

package server
import (
"crypto/tls"
"strings"
"sync"
"git.polynom.me/rio/internal/certificates"
"git.polynom.me/rio/internal/dns"
"github.com/go-acme/lego/v4/lego"
log "github.com/sirupsen/logrus"
)
var (
// To access requestingDomains, first acquire the lock.
requestingLock = sync.Mutex{}
// Domain -> _. Check if domain is a key here to see if we're already requeting
// a certificate for it.
requestingDomains = make(map[string]bool)
)
func lockIfUnlockedDomain(domain string) bool {
requestingLock.Lock()
defer requestingLock.Unlock()
_, found := requestingDomains[domain]
if !found {
requestingDomains[domain] = true
}
return found
}
func unlockDomain(domain string) {
requestingLock.Lock()
defer requestingLock.Unlock()
delete(requestingDomains, domain)
}
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client) *tls.Config {
return &tls.Config{
InsecureSkipVerify: true,
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
// Validate that we should even care about this domain
if !strings.HasSuffix(info.ServerName, pagesDomain) {
// Note: We do not check err here because err != nil
// always implies that cname == "", which does not have
// pagesDomain as a suffix.
cname, _ := dns.LookupCNAME(info.ServerName)
if !strings.HasSuffix(cname, pagesDomain) {
log.Warnf("Got ServerName for Domain %s that we're not responsible for", info.ServerName)
return cache.FallbackCertificate.TlsCertificate, nil
}
}
// If we want to access <user>.<pages domain>, then we can just
// use a wildcard certificate.
domain := info.ServerName
/*if strings.HasSuffix(info.ServerName, pagesDomain) {
domain = "*." + pagesDomain
}*/
cert, found := cache.Certificates[info.ServerName]
if found {
if cert.IsValid() {
return cert.TlsCertificate, nil
} else {
// If we're already working on the domain,
// return the old certificate
if lockIfUnlockedDomain(domain) {
return cert.TlsCertificate, nil
}
defer unlockDomain(domain)
// TODO: Renew
log.Debugf("Certificate for %s expired, renewing", domain)
}
} else {
// Don't request if we're already requesting.
if lockIfUnlockedDomain(domain) {
return cache.FallbackCertificate.TlsCertificate, nil
}
defer unlockDomain(domain)
// Request new certificate
log.Debugf("Obtaining new certificate for %s...", domain)
cert, err := certificates.ObtainNewCertificate(
[]string{domain},
acmeClient,
)
if err != nil {
log.Errorf(
"Failed to get certificate for %s: %v",
domain,
err,
)
return cache.FallbackCertificate.TlsCertificate, nil
}
// Add to cache and flush
cache.AddCert(cert, cachePath)
return cert.TlsCertificate, nil
}
log.Debugf("TLS ServerName: %s", info.ServerName)
return cache.FallbackCertificate.TlsCertificate, nil
},
NextProtos: []string{
"http/0.9",
"http/1.0",
"http/1.1",
"h2",
"h2c",
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
}
}