feat: Move the CNAME into the rio.json
This commit is contained in:
parent
cf85380ddb
commit
e3032c8233
@ -56,7 +56,7 @@ func handleSubdomain(ctx *context.GlobalContext, domain, cname, path string, req
|
|||||||
domain,
|
domain,
|
||||||
cname,
|
cname,
|
||||||
path,
|
path,
|
||||||
ctx.Gitea,
|
ctx,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get repo: %s", err)
|
log.Errorf("Failed to get repo: %s", err)
|
||||||
@ -209,6 +209,20 @@ func runServer(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare the context
|
||||||
|
cacheCtx := context.CacheContext{
|
||||||
|
RepositoryInformationCache: context.MakeRepoInfoCache(),
|
||||||
|
RepositoryPathCache: context.MakeRepoPathCache(),
|
||||||
|
UsernameCache: context.MakeUsernameCache(),
|
||||||
|
}
|
||||||
|
globalCtx := &context.GlobalContext{
|
||||||
|
DefaultCSP: defaultCsp,
|
||||||
|
PagesDomain: domain,
|
||||||
|
Gitea: &giteaClient,
|
||||||
|
MetricConfig: &lokiConfig,
|
||||||
|
Cache: &cacheCtx,
|
||||||
|
}
|
||||||
|
|
||||||
if !acmeDisable {
|
if !acmeDisable {
|
||||||
if acmeEmail == "" || acmeFile == "" || certsFile == "" || acmeDnsProvider == "" {
|
if acmeEmail == "" || acmeFile == "" || certsFile == "" || acmeDnsProvider == "" {
|
||||||
return errors.New("The options acme-dns-provider, acme-file, acme-email, and certs-file are required")
|
return errors.New("The options acme-dns-provider, acme-file, acme-email, and certs-file are required")
|
||||||
@ -262,23 +276,11 @@ func runServer(ctx *cli.Context) error {
|
|||||||
certsFile,
|
certsFile,
|
||||||
&cache,
|
&cache,
|
||||||
acmeClient,
|
acmeClient,
|
||||||
&giteaClient,
|
globalCtx,
|
||||||
)
|
)
|
||||||
listener = tls.NewListener(listener, tlsConfig)
|
listener = tls.NewListener(listener, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the context
|
|
||||||
cacheCtx := context.CacheContext{
|
|
||||||
RepositoryInformationCache: context.MakeRepoInfoCache(),
|
|
||||||
}
|
|
||||||
globalCtx := &context.GlobalContext{
|
|
||||||
DefaultCSP: defaultCsp,
|
|
||||||
PagesDomain: domain,
|
|
||||||
Gitea: &giteaClient,
|
|
||||||
MetricConfig: &lokiConfig,
|
|
||||||
Cache: &cacheCtx,
|
|
||||||
}
|
|
||||||
|
|
||||||
var waitGroup sync.WaitGroup
|
var waitGroup sync.WaitGroup
|
||||||
servers := 2
|
servers := 2
|
||||||
if acmeDisable {
|
if acmeDisable {
|
||||||
|
@ -11,6 +11,12 @@ import (
|
|||||||
type CacheContext struct {
|
type CacheContext struct {
|
||||||
// Cache for general repository information
|
// Cache for general repository information
|
||||||
RepositoryInformationCache cache.Cache
|
RepositoryInformationCache cache.Cache
|
||||||
|
|
||||||
|
// Cache for path resolutions
|
||||||
|
RepositoryPathCache cache.Cache
|
||||||
|
|
||||||
|
// Cache for username lookups
|
||||||
|
UsernameCache cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
type GlobalContext struct {
|
type GlobalContext struct {
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
type RepositoryInformation struct {
|
type RepositoryInformation struct {
|
||||||
// Headers to include in every response
|
// Headers to include in every response
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
|
|
||||||
|
CNAME string
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoInfoKey(owner, name string) string {
|
func repoInfoKey(owner, name string) string {
|
39
internal/context/path.go
Normal file
39
internal/context/path.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.polynom.me/rio/internal/gitea"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepositoryPathInformation struct {
|
||||||
|
Repository gitea.Repository
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathInfoKey(domain, path string) string {
|
||||||
|
return domain + "/" + path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheContext) GetRepositoryPath(domain, path string) *RepositoryPathInformation {
|
||||||
|
data, found := c.RepositoryPathCache.Get(pathInfoKey(domain, path))
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
typedData := data.(RepositoryPathInformation)
|
||||||
|
return &typedData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheContext) SetRepositoryPath(domain, path string, info RepositoryPathInformation) {
|
||||||
|
c.RepositoryPathCache.Set(
|
||||||
|
pathInfoKey(domain, path),
|
||||||
|
info,
|
||||||
|
cache.DefaultExpiration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeRepoPathCache() cache.Cache {
|
||||||
|
return *cache.New(24*time.Hour, 12*time.Hour)
|
||||||
|
}
|
24
internal/context/user.go
Normal file
24
internal/context/user.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CacheContext) GetUser(username string) bool {
|
||||||
|
_, found := c.UsernameCache.Get(username)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheContext) SetUser(username string) {
|
||||||
|
c.UsernameCache.Set(
|
||||||
|
username,
|
||||||
|
true,
|
||||||
|
cache.DefaultExpiration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeUsernameCache() cache.Cache {
|
||||||
|
return *cache.New(24*time.Hour, 12*time.Hour)
|
||||||
|
}
|
@ -7,13 +7,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.polynom.me/rio/internal/constants"
|
"git.polynom.me/rio/internal/constants"
|
||||||
"git.polynom.me/rio/internal/context"
|
"git.polynom.me/rio/internal/context"
|
||||||
"git.polynom.me/rio/internal/gitea"
|
"git.polynom.me/rio/internal/gitea"
|
||||||
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,96 +26,62 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, ctx *context.GlobalContext) (*gitea.Repository, error) {
|
||||||
pathCache = cache.New(1*time.Hour, 1*time.Hour)
|
|
||||||
|
|
||||||
// Caching the existence of an user
|
|
||||||
userCache = cache.New(24*time.Hour, 12*time.Hour)
|
|
||||||
)
|
|
||||||
|
|
||||||
type PageCacheEntry struct {
|
|
||||||
Repository gitea.Repository
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func makePageCacheKey(domain, path string) string {
|
|
||||||
return domain + "/" + path
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, giteaClient *gitea.GiteaClient) (*gitea.Repository, error) {
|
|
||||||
log.Debugf("CNAME: %s", cname)
|
log.Debugf("CNAME: %s", cname)
|
||||||
log.Debugf("Looking up repository %s/%s", username, reponame)
|
log.Debugf("Looking up repository %s/%s", username, reponame)
|
||||||
repo, err := giteaClient.GetRepository(username, reponame)
|
repo, err := ctx.Gitea.GetRepository(username, reponame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !giteaClient.HasBranch(username, reponame, branchName) {
|
if !ctx.Gitea.HasBranch(username, reponame, branchName) {
|
||||||
return nil, errors.New("Specified branch does not exist")
|
return nil, errors.New("Specified branch does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the CNAME file matches
|
// Check if the CNAME file matches
|
||||||
|
|
||||||
if cname != "" {
|
if cname != "" {
|
||||||
log.Debug("Checking CNAME")
|
log.Debug("Checking CNAME")
|
||||||
file, _, err := giteaClient.GetFile(
|
repoInfo := GetRepositoryInformation(username, reponame, ctx)
|
||||||
username,
|
if repoInfo == nil {
|
||||||
reponame,
|
log.Warn("Repository does not contain a rio.json file")
|
||||||
constants.PagesBranch,
|
return nil, errors.New("No CNAME available in repository")
|
||||||
"CNAME",
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(
|
|
||||||
"Could not verify CNAME of %s/%s@%s: %v\n",
|
|
||||||
username,
|
|
||||||
reponame,
|
|
||||||
constants.PagesBranch,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cnameContent := strings.Trim(
|
log.Debugf("CNAME Content: \"%s\"", repoInfo.CNAME)
|
||||||
string(file[:]),
|
if repoInfo.CNAME != host {
|
||||||
"\n",
|
log.Warnf("CNAME mismatch: Repo '%s', Host '%s'", repoInfo.CNAME, host)
|
||||||
)
|
|
||||||
|
|
||||||
log.Debugf("CNAME Content: %s", cnameContent)
|
|
||||||
if cnameContent != host {
|
|
||||||
log.Warnf("CNAME mismatch: Repo '%s', Host '%s'", cnameContent, host)
|
|
||||||
return nil, errors.New("CNAME mismatch")
|
return nil, errors.New("CNAME mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache data
|
// Cache data
|
||||||
pathCache.Set(
|
ctx.Cache.SetRepositoryPath(
|
||||||
makePageCacheKey(domain, path),
|
domain,
|
||||||
PageCacheEntry{
|
path,
|
||||||
repo,
|
context.RepositoryPathInformation{
|
||||||
path,
|
Repository: repo,
|
||||||
|
Path: path,
|
||||||
},
|
},
|
||||||
cache.DefaultExpiration,
|
|
||||||
)
|
)
|
||||||
return &repo, nil
|
return &repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// host is the domain name we're accessed from. cname is the domain that host is pointing
|
// host is the domain name we're accessed from. cname is the domain that host is pointing
|
||||||
// if, if we're accessed via a CNAME. If not, then cname is "".
|
// if, if we're accessed via a CNAME. If not, then cname is "".
|
||||||
func RepoFromPath(username, host, cname, path string, giteaClient *gitea.GiteaClient) (*gitea.Repository, string, error) {
|
func RepoFromPath(username, host, cname, path string, ctx *context.GlobalContext) (*gitea.Repository, string, error) {
|
||||||
domain := host
|
domain := host
|
||||||
|
|
||||||
// Guess the repository
|
// Guess the repository
|
||||||
key := makePageCacheKey(domain, path)
|
entry := ctx.Cache.GetRepositoryPath(domain, path)
|
||||||
entry, found := pathCache.Get(key)
|
if entry != nil {
|
||||||
if found {
|
return &entry.Repository, entry.Path, nil
|
||||||
pageEntry := entry.(PageCacheEntry)
|
|
||||||
return &pageEntry.Repository, pageEntry.Path, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow specifying the repository name in the TXT record
|
// Allow specifying the repository name in the TXT record
|
||||||
reponame := ""
|
reponame := ""
|
||||||
if cname != "" {
|
if cname != "" {
|
||||||
repoLookup, err := giteaClient.LookupRepoTXT(host)
|
repoLookup, err := ctx.Gitea.LookupRepoTXT(host)
|
||||||
if err == nil && repoLookup != "" {
|
if err == nil && repoLookup != "" {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"TXT lookup for %s resulted in choosing repository %s",
|
"TXT lookup for %s resulted in choosing repository %s",
|
||||||
@ -141,7 +105,7 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.GiteaCl
|
|||||||
domain,
|
domain,
|
||||||
modifiedPath,
|
modifiedPath,
|
||||||
cname,
|
cname,
|
||||||
giteaClient,
|
ctx,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return repo, modifiedPath, nil
|
return repo, modifiedPath, nil
|
||||||
@ -160,7 +124,7 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.GiteaCl
|
|||||||
domain,
|
domain,
|
||||||
path,
|
path,
|
||||||
cname,
|
cname,
|
||||||
giteaClient,
|
ctx,
|
||||||
)
|
)
|
||||||
return repo, path, err
|
return repo, path, err
|
||||||
}
|
}
|
||||||
@ -168,14 +132,15 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.GiteaCl
|
|||||||
// Checks if the username exists as an organisation or an user on the Gitea
|
// Checks if the username exists as an organisation or an user on the Gitea
|
||||||
// instance, so that an attacker can't just request certificates for random
|
// instance, so that an attacker can't just request certificates for random
|
||||||
// usernames.
|
// usernames.
|
||||||
func CanRequestCertificate(username string, giteaClient *gitea.GiteaClient) bool {
|
func CanRequestCertificate(username string, ctx *context.GlobalContext) bool {
|
||||||
if _, found := userCache.Get(username); found {
|
found := ctx.Cache.GetUser(username)
|
||||||
|
if found {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
hasUser := giteaClient.HasUser(username)
|
hasUser := ctx.Gitea.HasUser(username)
|
||||||
if hasUser {
|
if hasUser {
|
||||||
userCache.Set(username, true, cache.DefaultExpiration)
|
ctx.Cache.SetUser(username)
|
||||||
}
|
}
|
||||||
return hasUser
|
return hasUser
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.polynom.me/rio/internal/certificates"
|
"git.polynom.me/rio/internal/certificates"
|
||||||
|
"git.polynom.me/rio/internal/context"
|
||||||
"git.polynom.me/rio/internal/dns"
|
"git.polynom.me/rio/internal/dns"
|
||||||
"git.polynom.me/rio/internal/gitea"
|
|
||||||
"git.polynom.me/rio/internal/repo"
|
"git.polynom.me/rio/internal/repo"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
@ -82,7 +82,7 @@ func getUsername(sni, pagesDomain string) (string, error) {
|
|||||||
return dns.ExtractUsername(pagesDomain, sni), nil
|
return dns.ExtractUsername(pagesDomain, sni), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, giteaClient *gitea.GiteaClient) *tls.Config {
|
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, ctx *context.GlobalContext) *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
// Validate that we should even care about this domain
|
// Validate that we should even care about this domain
|
||||||
@ -100,7 +100,7 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
if cert.IsValid() {
|
if cert.IsValid() {
|
||||||
return cert.TlsCertificate, nil
|
return cert.TlsCertificate, nil
|
||||||
} else {
|
} else {
|
||||||
if !isPagesDomain && !repo.CanRequestCertificate(username, giteaClient) {
|
if !isPagesDomain && !repo.CanRequestCertificate(username, ctx) {
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Cannot renew certificate for %s because CanRequestCertificate(%s) returned false",
|
"Cannot renew certificate for %s because CanRequestCertificate(%s) returned false",
|
||||||
info.ServerName,
|
info.ServerName,
|
||||||
@ -129,7 +129,7 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
return newCert.TlsCertificate, nil
|
return newCert.TlsCertificate, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !isPagesDomain && !repo.CanRequestCertificate(username, giteaClient) {
|
if !isPagesDomain && !repo.CanRequestCertificate(username, ctx) {
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Cannot request certificate for %s because CanRequestCertificate(%s) returned false",
|
"Cannot request certificate for %s because CanRequestCertificate(%s) returned false",
|
||||||
info.ServerName,
|
info.ServerName,
|
||||||
|
Loading…
Reference in New Issue
Block a user