feat: First steps towards using wildcard certificates
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -43,10 +43,30 @@ func unlockDomain(domain string) {
|
||||
delete(workingDomains, domain)
|
||||
}
|
||||
|
||||
func buildDomainList(domain, pagesDomain string) []string {
|
||||
if domain == pagesDomain || strings.HasSuffix(domain, pagesDomain) {
|
||||
return []string{
|
||||
pagesDomain,
|
||||
"*." + pagesDomain,
|
||||
}
|
||||
}
|
||||
|
||||
return []string{domain}
|
||||
}
|
||||
|
||||
func getDomainKey(domain, pagesDomain string) string {
|
||||
if domain == pagesDomain || strings.HasSuffix(domain, pagesDomain) {
|
||||
return "*." + pagesDomain
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
|
||||
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, giteaClient *gitea.Client) *tls.Config {
|
||||
return &tls.Config{
|
||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// Validate that we should even care about this domain
|
||||
isPagesDomain := info.ServerName == pagesDomain
|
||||
cname := ""
|
||||
if !strings.HasSuffix(info.ServerName, pagesDomain) {
|
||||
// Note: We do not check err here because err != nil
|
||||
@@ -59,33 +79,27 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}*/
|
||||
|
||||
// Figure out a username for later username checks
|
||||
username := ""
|
||||
if cname == "" {
|
||||
// domain ends on pagesDomain
|
||||
username = strings.Split(domain, ".")[0]
|
||||
username = strings.Split(info.ServerName, ".")[0]
|
||||
} else {
|
||||
// cname ends on pagesDomain
|
||||
username = strings.Split(cname, ".")[0]
|
||||
}
|
||||
|
||||
// Find the correct certificate
|
||||
cert, found := cache.Certificates[info.ServerName]
|
||||
domainKey := getDomainKey(info.ServerName, pagesDomain)
|
||||
cert, found := cache.Certificates[domainKey]
|
||||
if found {
|
||||
if cert.IsValid() {
|
||||
return cert.TlsCertificate, nil
|
||||
} else {
|
||||
if !repo.CanRequestCertificate(username, giteaClient) {
|
||||
if !isPagesDomain && !repo.CanRequestCertificate(username, giteaClient) {
|
||||
log.Warnf(
|
||||
"Cannot renew certificate for %s because CanRequestCertificate(%s) returned false",
|
||||
domain,
|
||||
info.ServerName,
|
||||
username,
|
||||
)
|
||||
return cert.TlsCertificate, nil
|
||||
@@ -93,16 +107,16 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
||||
|
||||
// If we're already working on the domain,
|
||||
// return the old certificate
|
||||
if lockIfUnlockedDomain(domain) {
|
||||
if lockIfUnlockedDomain(domainKey) {
|
||||
return cert.TlsCertificate, nil
|
||||
}
|
||||
defer unlockDomain(domain)
|
||||
defer unlockDomain(domainKey)
|
||||
|
||||
// Renew the certificate
|
||||
log.Infof("Certificate for %s expired, renewing", domain)
|
||||
log.Infof("Certificate for %s expired, renewing", info.ServerName)
|
||||
newCert, err := certificates.RenewCertificate(&cert, acmeClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to renew certificate for %s: %v", domain, err)
|
||||
log.Errorf("Failed to renew certificate for %s: %v", info.ServerName, err)
|
||||
return cert.TlsCertificate, nil
|
||||
}
|
||||
|
||||
@@ -111,31 +125,33 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
||||
return newCert.TlsCertificate, nil
|
||||
}
|
||||
} else {
|
||||
if !repo.CanRequestCertificate(username, giteaClient) {
|
||||
if !isPagesDomain && !repo.CanRequestCertificate(username, giteaClient) {
|
||||
log.Warnf(
|
||||
"Cannot request certificate for %s because CanRequestCertificate(%s) returned false",
|
||||
domain,
|
||||
info.ServerName,
|
||||
username,
|
||||
)
|
||||
return cache.FallbackCertificate.TlsCertificate, nil
|
||||
}
|
||||
|
||||
// Don't request if we're already requesting.
|
||||
if lockIfUnlockedDomain(domain) {
|
||||
key := getDomainKey(info.ServerName, pagesDomain)
|
||||
if lockIfUnlockedDomain(domainKey) {
|
||||
return cache.FallbackCertificate.TlsCertificate, nil
|
||||
}
|
||||
defer unlockDomain(domain)
|
||||
defer unlockDomain(key)
|
||||
|
||||
// Request new certificate
|
||||
log.Infof("Obtaining new certificate for %s...", domain)
|
||||
log.Infof("Obtaining new certificate for %s...", info.ServerName)
|
||||
cert, err := certificates.ObtainNewCertificate(
|
||||
[]string{domain},
|
||||
buildDomainList(info.ServerName, pagesDomain),
|
||||
domainKey,
|
||||
acmeClient,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to get certificate for %s: %v",
|
||||
domain,
|
||||
info.ServerName,
|
||||
err,
|
||||
)
|
||||
return cache.FallbackCertificate.TlsCertificate, nil
|
||||
|
||||
69
internal/server/tls_test.go
Normal file
69
internal/server/tls_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
pagesDomain = "pages.local"
|
||||
pagesDomainWildcard = "*.pages.local"
|
||||
)
|
||||
|
||||
func equals(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, _ := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestDomainListBare(t *testing.T) {
|
||||
expect := []string{pagesDomain, pagesDomainWildcard}
|
||||
res := buildDomainList(pagesDomain, pagesDomain)
|
||||
if !equals(res, expect) {
|
||||
t.Fatalf("%v != %v", res, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainListSubdomain(t *testing.T) {
|
||||
expect := []string{pagesDomain, pagesDomainWildcard}
|
||||
res := buildDomainList("user."+pagesDomain, pagesDomain)
|
||||
if !equals(res, expect) {
|
||||
t.Fatalf("%v != %v", res, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainListCNAME(t *testing.T) {
|
||||
expect := []string{"testdomain.example"}
|
||||
res := buildDomainList("testdomain.example", pagesDomain)
|
||||
if !equals(res, expect) {
|
||||
t.Fatalf("%v != %v", res, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainKeyBare(t *testing.T) {
|
||||
res := getDomainKey(pagesDomain, pagesDomain)
|
||||
if res != pagesDomainWildcard {
|
||||
t.Fatalf("%s != %s", res, pagesDomainWildcard)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainKeySubdomain(t *testing.T) {
|
||||
res := getDomainKey("user."+pagesDomain, pagesDomain)
|
||||
if res != pagesDomainWildcard {
|
||||
t.Fatalf("%s != %s", res, pagesDomainWildcard)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainKeyCNAME(t *testing.T) {
|
||||
res := getDomainKey("testdomain.example", pagesDomain)
|
||||
if res != "testdomain.example" {
|
||||
t.Fatalf("%s != %s", res, "testdomain.example")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user