mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2024-11-30 21:45:29 +00:00
split cert func to related packages
This commit is contained in:
parent
bb6f28fe57
commit
ccada3e6df
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"codeberg.org/codeberg/pages/server"
|
"codeberg.org/codeberg/pages/server"
|
||||||
"codeberg.org/codeberg/pages/server/cache"
|
"codeberg.org/codeberg/pages/server/cache"
|
||||||
|
"codeberg.org/codeberg/pages/server/certificates"
|
||||||
"codeberg.org/codeberg/pages/server/database"
|
"codeberg.org/codeberg/pages/server/database"
|
||||||
"codeberg.org/codeberg/pages/server/utils"
|
"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
|
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,
|
giteaRoot, giteaAPIToken, dnsProvider,
|
||||||
acmeUseRateLimits,
|
acmeUseRateLimits,
|
||||||
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
|
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
|
||||||
keyDatabase))
|
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 {
|
if enableHTTPServer {
|
||||||
go (func() {
|
go (func() {
|
||||||
challengePath := []byte("/.well-known/acme-challenge/")
|
challengePath := []byte("/.well-known/acme-challenge/")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package server
|
package certificates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -35,6 +35,8 @@ import (
|
|||||||
|
|
||||||
"codeberg.org/codeberg/pages/server/cache"
|
"codeberg.org/codeberg/pages/server/cache"
|
||||||
"codeberg.org/codeberg/pages/server/database"
|
"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.
|
// 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)
|
sni = string(sniBytes)
|
||||||
} else {
|
} else {
|
||||||
var targetRepo, targetBranch string
|
var targetRepo, targetBranch string
|
||||||
targetOwner, targetRepo, targetBranch = getTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache)
|
targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache)
|
||||||
if targetOwner == "" {
|
if targetOwner == "" {
|
||||||
// DNS not set up, return main certificate to redirect to the docs
|
// DNS not set up, return main certificate to redirect to the docs
|
||||||
sniBytes = mainDomainSuffix
|
sniBytes = mainDomainSuffix
|
||||||
sni = string(sniBytes)
|
sni = string(sniBytes)
|
||||||
} else {
|
} else {
|
||||||
_, _ = targetRepo, targetBranch
|
_, _ = 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 {
|
if !valid {
|
||||||
sniBytes = mainDomainSuffix
|
sniBytes = mainDomainSuffix
|
||||||
sni = string(sniBytes)
|
sni = string(sniBytes)
|
6
server/dns/const.go
Normal file
6
server/dns/const.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DnsLookupCacheTimeout specifies the timeout for the DNS lookup cache.
|
||||||
|
var DnsLookupCacheTimeout = 15 * time.Minute
|
56
server/dns/dns.go
Normal file
56
server/dns/dns.go
Normal file
@ -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
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"codeberg.org/codeberg/pages/html"
|
"codeberg.org/codeberg/pages/html"
|
||||||
"codeberg.org/codeberg/pages/server/cache"
|
"codeberg.org/codeberg/pages/server/cache"
|
||||||
|
"codeberg.org/codeberg/pages/server/dns"
|
||||||
"codeberg.org/codeberg/pages/server/upstream"
|
"codeberg.org/codeberg/pages/server/upstream"
|
||||||
"codeberg.org/codeberg/pages/server/utils"
|
"codeberg.org/codeberg/pages/server/utils"
|
||||||
)
|
)
|
||||||
@ -113,7 +114,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
|
|||||||
var tryUpstream = func() {
|
var tryUpstream = func() {
|
||||||
// check if a canonical domain exists on a request on MainDomain
|
// check if a canonical domain exists on a request on MainDomain
|
||||||
if bytes.HasSuffix(trimmedHost, mainDomainSuffix) {
|
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)) {
|
if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) {
|
||||||
canonicalPath := string(ctx.RequestURI())
|
canonicalPath := string(ctx.RequestURI())
|
||||||
if targetRepo != "pages" {
|
if targetRepo != "pages" {
|
||||||
@ -247,7 +248,7 @@ func Handler(mainDomainSuffix, rawDomain []byte,
|
|||||||
trimmedHostStr := string(trimmedHost)
|
trimmedHostStr := string(trimmedHost)
|
||||||
|
|
||||||
// Serve pages from external domains
|
// Serve pages from external domains
|
||||||
targetOwner, targetRepo, targetBranch = getTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache)
|
targetOwner, targetRepo, targetBranch = dns.GetTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache)
|
||||||
if targetOwner == "" {
|
if targetOwner == "" {
|
||||||
html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
|
html.ReturnErrorPage(ctx, fasthttp.StatusFailedDependency)
|
||||||
return
|
return
|
||||||
@ -264,13 +265,13 @@ func Handler(mainDomainSuffix, rawDomain []byte,
|
|||||||
// Try to use the given repo on the given branch or the default branch
|
// 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")
|
log.Debug().Msg("custom domain preparations, now trying with details from DNS")
|
||||||
if tryBranch(targetRepo, targetBranch, pathElements, canonicalLink) {
|
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 {
|
if !valid {
|
||||||
html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest)
|
html.ReturnErrorPage(ctx, fasthttp.StatusMisdirectedRequest)
|
||||||
return
|
return
|
||||||
} else if canonicalDomain != trimmedHostStr {
|
} else if canonicalDomain != trimmedHostStr {
|
||||||
// only redirect if the target is also a codeberg page!
|
// 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 != "" {
|
if targetOwner != "" {
|
||||||
ctx.Redirect("https://"+canonicalDomain+string(ctx.RequestURI()), fasthttp.StatusTemporaryRedirect)
|
ctx.Redirect("https://"+canonicalDomain+string(ctx.RequestURI()), fasthttp.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
|
@ -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.
|
// FileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
|
||||||
var FileCacheSizeLimit = 1024 * 1024
|
var FileCacheSizeLimit = 1024 * 1024
|
||||||
|
|
||||||
|
// CanonicalDomainCacheTimeout specifies the timeout for the canonical domain cache.
|
||||||
|
var CanonicalDomainCacheTimeout = 15 * time.Minute
|
||||||
|
53
server/upstream/domains.go
Normal file
53
server/upstream/domains.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user