From ccada3e6dfd1b306922ce697b9aec1ff834b1a3a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 5 Dec 2021 15:21:05 +0100 Subject: [PATCH] split cert func to related packages --- cmd/main.go | 5 +- server/{ => certificates}/certificates.go | 8 +- server/dns/const.go | 6 ++ server/dns/dns.go | 56 +++++++++++ server/domains.go | 110 ---------------------- server/handler.go | 9 +- server/upstream/const.go | 3 + server/upstream/domains.go | 53 +++++++++++ 8 files changed, 131 insertions(+), 119 deletions(-) rename server/{ => certificates}/certificates.go (97%) create mode 100644 server/dns/const.go create mode 100644 server/dns/dns.go delete mode 100644 server/domains.go create mode 100644 server/upstream/domains.go diff --git a/cmd/main.go b/cmd/main.go index f4ee205..246d0d7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,6 +15,7 @@ import ( "codeberg.org/codeberg/pages/server" "codeberg.org/codeberg/pages/server/cache" + "codeberg.org/codeberg/pages/server/certificates" "codeberg.org/codeberg/pages/server/database" "codeberg.org/codeberg/pages/server/utils" ) @@ -92,13 +93,13 @@ func Serve(ctx *cli.Context) error { } defer keyDatabase.Sync() //nolint:errcheck // database has no close ... sync behave like it - listener = tls.NewListener(listener, server.TLSConfig(mainDomainSuffix, + listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix, giteaRoot, giteaAPIToken, dnsProvider, acmeUseRateLimits, keyCache, challengeCache, dnsLookupCache, canonicalDomainCache, keyDatabase)) - server.SetupCertificates(mainDomainSuffix, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider, acmeUseRateLimits, acmeAcceptTerms, enableHTTPServer, challengeCache, keyDatabase) + certificates.SetupCertificates(mainDomainSuffix, acmeAPI, acmeMail, acmeEabHmac, acmeEabKID, dnsProvider, acmeUseRateLimits, acmeAcceptTerms, enableHTTPServer, challengeCache, keyDatabase) if enableHTTPServer { go (func() { challengePath := []byte("/.well-known/acme-challenge/") diff --git a/server/certificates.go b/server/certificates/certificates.go similarity index 97% rename from server/certificates.go rename to server/certificates/certificates.go index 1493d03..b63ed0e 100644 --- a/server/certificates.go +++ b/server/certificates/certificates.go @@ -1,4 +1,4 @@ -package server +package certificates import ( "bytes" @@ -35,6 +35,8 @@ import ( "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/database" + dnsutils "codeberg.org/codeberg/pages/server/dns" + "codeberg.org/codeberg/pages/server/upstream" ) // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates. @@ -75,14 +77,14 @@ func TLSConfig(mainDomainSuffix []byte, sni = string(sniBytes) } else { var targetRepo, targetBranch string - targetOwner, targetRepo, targetBranch = getTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache) + targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache) if targetOwner == "" { // DNS not set up, return main certificate to redirect to the docs sniBytes = mainDomainSuffix sni = string(sniBytes) } else { _, _ = targetRepo, targetBranch - _, valid := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) + _, valid := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) if !valid { sniBytes = mainDomainSuffix sni = string(sniBytes) diff --git a/server/dns/const.go b/server/dns/const.go new file mode 100644 index 0000000..fcf669b --- /dev/null +++ b/server/dns/const.go @@ -0,0 +1,6 @@ +package dns + +import "time" + +// DnsLookupCacheTimeout specifies the timeout for the DNS lookup cache. +var DnsLookupCacheTimeout = 15 * time.Minute diff --git a/server/dns/dns.go b/server/dns/dns.go new file mode 100644 index 0000000..c231439 --- /dev/null +++ b/server/dns/dns.go @@ -0,0 +1,56 @@ +package dns + +import ( + "net" + "strings" + + "codeberg.org/codeberg/pages/server/cache" +) + +// GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix. +// If everything is fine, it returns the target data. +func GetTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) { + // Get CNAME or TXT + var cname string + var err error + if cachedName, ok := dnsLookupCache.Get(domain); ok { + cname = cachedName.(string) + } else { + cname, err = net.LookupCNAME(domain) + cname = strings.TrimSuffix(cname, ".") + if err != nil || !strings.HasSuffix(cname, mainDomainSuffix) { + cname = "" + // TODO: check if the A record matches! + names, err := net.LookupTXT(domain) + if err == nil { + for _, name := range names { + name = strings.TrimSuffix(name, ".") + if strings.HasSuffix(name, mainDomainSuffix) { + cname = name + break + } + } + } + } + _ = dnsLookupCache.Set(domain, cname, DnsLookupCacheTimeout) + } + if cname == "" { + return + } + cnameParts := strings.Split(strings.TrimSuffix(cname, mainDomainSuffix), ".") + targetOwner = cnameParts[len(cnameParts)-1] + if len(cnameParts) > 1 { + targetRepo = cnameParts[len(cnameParts)-2] + } + if len(cnameParts) > 2 { + targetBranch = cnameParts[len(cnameParts)-3] + } + if targetRepo == "" { + targetRepo = "pages" + } + if targetBranch == "" && targetRepo != "pages" { + targetBranch = "pages" + } + // if targetBranch is still empty, the caller must find the default branch + return +} diff --git a/server/domains.go b/server/domains.go deleted file mode 100644 index cbbdef9..0000000 --- a/server/domains.go +++ /dev/null @@ -1,110 +0,0 @@ -package server - -import ( - "net" - "strings" - "time" - - "github.com/valyala/fasthttp" - - "codeberg.org/codeberg/pages/server/cache" - "codeberg.org/codeberg/pages/server/upstream" -) - -// DnsLookupCacheTimeout specifies the timeout for the DNS lookup cache. -var DnsLookupCacheTimeout = 15 * time.Minute - -// getTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix. -// If everything is fine, it returns the target data. -func getTargetFromDNS(domain, mainDomainSuffix string, dnsLookupCache cache.SetGetKey) (targetOwner, targetRepo, targetBranch string) { - // Get CNAME or TXT - var cname string - var err error - if cachedName, ok := dnsLookupCache.Get(domain); ok { - cname = cachedName.(string) - } else { - cname, err = net.LookupCNAME(domain) - cname = strings.TrimSuffix(cname, ".") - if err != nil || !strings.HasSuffix(cname, mainDomainSuffix) { - cname = "" - // TODO: check if the A record matches! - names, err := net.LookupTXT(domain) - if err == nil { - for _, name := range names { - name = strings.TrimSuffix(name, ".") - if strings.HasSuffix(name, mainDomainSuffix) { - cname = name - break - } - } - } - } - _ = dnsLookupCache.Set(domain, cname, DnsLookupCacheTimeout) - } - if cname == "" { - return - } - cnameParts := strings.Split(strings.TrimSuffix(cname, mainDomainSuffix), ".") - targetOwner = cnameParts[len(cnameParts)-1] - if len(cnameParts) > 1 { - targetRepo = cnameParts[len(cnameParts)-2] - } - if len(cnameParts) > 2 { - targetBranch = cnameParts[len(cnameParts)-3] - } - if targetRepo == "" { - targetRepo = "pages" - } - if targetBranch == "" && targetRepo != "pages" { - targetBranch = "pages" - } - // if targetBranch is still empty, the caller must find the default branch - return -} - -// CanonicalDomainCacheTimeout specifies the timeout for the canonical domain cache. -var CanonicalDomainCacheTimeout = 15 * time.Minute - -// checkCanonicalDomain returns the canonical domain specified in the repo (using the file `.canonical-domain`). -func checkCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaApiToken string, canonicalDomainCache cache.SetGetKey) (canonicalDomain string, valid bool) { - domains := []string{} - if cachedValue, ok := canonicalDomainCache.Get(targetOwner + "/" + targetRepo + "/" + targetBranch); ok { - domains = cachedValue.([]string) - for _, domain := range domains { - if domain == actualDomain { - valid = true - break - } - } - } else { - req := fasthttp.AcquireRequest() - req.SetRequestURI(giteaRoot + "/api/v1/repos/" + targetOwner + "/" + targetRepo + "/raw/" + targetBranch + "/.domains" + "?access_token=" + giteaApiToken) - res := fasthttp.AcquireResponse() - - err := upstream.Client.Do(req, res) - if err == nil && res.StatusCode() == fasthttp.StatusOK { - for _, domain := range strings.Split(string(res.Body()), "\n") { - domain = strings.ToLower(domain) - domain = strings.TrimSpace(domain) - domain = strings.TrimPrefix(domain, "http://") - domain = strings.TrimPrefix(domain, "https://") - if len(domain) > 0 && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') { - domains = append(domains, domain) - } - if domain == actualDomain { - valid = true - } - } - } - domains = append(domains, targetOwner+mainDomainSuffix) - if domains[len(domains)-1] == actualDomain { - valid = true - } - if targetRepo != "" && targetRepo != "pages" { - domains[len(domains)-1] += "/" + targetRepo - } - _ = canonicalDomainCache.Set(targetOwner+"/"+targetRepo+"/"+targetBranch, domains, CanonicalDomainCacheTimeout) - } - canonicalDomain = domains[0] - return -} diff --git a/server/handler.go b/server/handler.go index ba6cfdd..89f3f4a 100644 --- a/server/handler.go +++ b/server/handler.go @@ -9,6 +9,7 @@ import ( "codeberg.org/codeberg/pages/html" "codeberg.org/codeberg/pages/server/cache" + "codeberg.org/codeberg/pages/server/dns" "codeberg.org/codeberg/pages/server/upstream" "codeberg.org/codeberg/pages/server/utils" ) @@ -113,7 +114,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, var tryUpstream = func() { // check if a canonical domain exists on a request on MainDomain if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { - canonicalDomain, _ := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) + canonicalDomain, _ := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) { canonicalPath := string(ctx.RequestURI()) if targetRepo != "pages" { @@ -247,7 +248,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, trimmedHostStr := string(trimmedHost) // Serve pages from external domains - targetOwner, targetRepo, targetBranch = getTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache) + targetOwner, targetRepo, targetBranch = dns.GetTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache) if targetOwner == "" { html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency) return @@ -264,13 +265,13 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Try to use the given repo on the given branch or the default branch log.Debug().Msg("custom domain preparations, now trying with details from DNS") if tryBranch(targetRepo, targetBranch, pathElements, canonicalLink) { - canonicalDomain, valid := checkCanonicalDomain(targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) + canonicalDomain, valid := upstream.CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), giteaRoot, giteaApiToken, canonicalDomainCache) if !valid { html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest) return } else if canonicalDomain != trimmedHostStr { // only redirect if the target is also a codeberg page! - targetOwner, _, _ = getTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache) + targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix), dnsLookupCache) if targetOwner != "" { ctx.Redirect("https://"+canonicalDomain+string(ctx.RequestURI()), fasthttp.StatusTemporaryRedirect) return diff --git a/server/upstream/const.go b/server/upstream/const.go index f51b152..8c351df 100644 --- a/server/upstream/const.go +++ b/server/upstream/const.go @@ -16,3 +16,6 @@ var FileCacheTimeout = 5 * time.Minute // FileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default. var FileCacheSizeLimit = 1024 * 1024 + +// CanonicalDomainCacheTimeout specifies the timeout for the canonical domain cache. +var CanonicalDomainCacheTimeout = 15 * time.Minute diff --git a/server/upstream/domains.go b/server/upstream/domains.go new file mode 100644 index 0000000..87941dd --- /dev/null +++ b/server/upstream/domains.go @@ -0,0 +1,53 @@ +package upstream + +import ( + "strings" + + "github.com/valyala/fasthttp" + + "codeberg.org/codeberg/pages/server/cache" +) + +// CheckCanonicalDomain returns the canonical domain specified in the repo (using the file `.canonical-domain`). +func CheckCanonicalDomain(targetOwner, targetRepo, targetBranch, actualDomain, mainDomainSuffix, giteaRoot, giteaApiToken string, canonicalDomainCache cache.SetGetKey) (canonicalDomain string, valid bool) { + domains := []string{} + if cachedValue, ok := canonicalDomainCache.Get(targetOwner + "/" + targetRepo + "/" + targetBranch); ok { + domains = cachedValue.([]string) + for _, domain := range domains { + if domain == actualDomain { + valid = true + break + } + } + } else { + req := fasthttp.AcquireRequest() + req.SetRequestURI(giteaRoot + "/api/v1/repos/" + targetOwner + "/" + targetRepo + "/raw/" + targetBranch + "/.domains" + "?access_token=" + giteaApiToken) + res := fasthttp.AcquireResponse() + + err := Client.Do(req, res) + if err == nil && res.StatusCode() == fasthttp.StatusOK { + for _, domain := range strings.Split(string(res.Body()), "\n") { + domain = strings.ToLower(domain) + domain = strings.TrimSpace(domain) + domain = strings.TrimPrefix(domain, "http://") + domain = strings.TrimPrefix(domain, "https://") + if len(domain) > 0 && !strings.HasPrefix(domain, "#") && !strings.ContainsAny(domain, "\t /") && strings.ContainsRune(domain, '.') { + domains = append(domains, domain) + } + if domain == actualDomain { + valid = true + } + } + } + domains = append(domains, targetOwner+mainDomainSuffix) + if domains[len(domains)-1] == actualDomain { + valid = true + } + if targetRepo != "" && targetRepo != "pages" { + domains[len(domains)-1] += "/" + targetRepo + } + _ = canonicalDomainCache.Set(targetOwner+"/"+targetRepo+"/"+targetBranch, domains, CanonicalDomainCacheTimeout) + } + canonicalDomain = domains[0] + return +}