feaT: Allow specifying custom headers in the rio.json
This commit is contained in:
parent
8630855374
commit
cf85380ddb
@ -16,6 +16,7 @@ import (
|
||||
"git.polynom.me/rio/internal/certificates"
|
||||
"git.polynom.me/rio/internal/context"
|
||||
"git.polynom.me/rio/internal/dns"
|
||||
riogitea "git.polynom.me/rio/internal/gitea"
|
||||
"git.polynom.me/rio/internal/metrics"
|
||||
"git.polynom.me/rio/internal/pages"
|
||||
"git.polynom.me/rio/internal/repo"
|
||||
@ -187,7 +188,7 @@ func runServer(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
giteaClient := repo.NewGiteaClient(giteaUrl, giteaApiClient)
|
||||
giteaClient := riogitea.NewGiteaClient(giteaUrl, giteaApiClient)
|
||||
|
||||
// Listen on the port
|
||||
addr := ctx.String("listen-host") + ":" + ctx.String("listen-port")
|
||||
@ -266,11 +267,16 @@ func runServer(ctx *cli.Context) error {
|
||||
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
|
||||
|
38
internal/context/cache.go
Normal file
38
internal/context/cache.go
Normal file
@ -0,0 +1,38 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type RepositoryInformation struct {
|
||||
// Headers to include in every response
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func repoInfoKey(owner, name string) string {
|
||||
return owner + ":" + name
|
||||
}
|
||||
|
||||
func (c *CacheContext) GetRepositoryInformation(owner, repoName string) *RepositoryInformation {
|
||||
data, found := c.RepositoryInformationCache.Get(repoInfoKey(owner, repoName))
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
typedData := data.(RepositoryInformation)
|
||||
return &typedData
|
||||
}
|
||||
|
||||
func (c *CacheContext) SetRepositoryInformation(owner, repoName string, info RepositoryInformation) {
|
||||
c.RepositoryInformationCache.Set(
|
||||
repoInfoKey(owner, repoName),
|
||||
info,
|
||||
cache.DefaultExpiration,
|
||||
)
|
||||
}
|
||||
|
||||
func MakeRepoInfoCache() cache.Cache {
|
||||
return *cache.New(24*time.Hour, 12*time.Hour)
|
||||
}
|
@ -3,15 +3,22 @@ package context
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.polynom.me/rio/internal/gitea"
|
||||
"git.polynom.me/rio/internal/metrics"
|
||||
"git.polynom.me/rio/internal/repo"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type CacheContext struct {
|
||||
// Cache for general repository information
|
||||
RepositoryInformationCache cache.Cache
|
||||
}
|
||||
|
||||
type GlobalContext struct {
|
||||
DefaultCSP string
|
||||
PagesDomain string
|
||||
Gitea *repo.GiteaClient
|
||||
Gitea *gitea.GiteaClient
|
||||
MetricConfig *metrics.LokiMetricConfig
|
||||
Cache *CacheContext
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package repo
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -38,17 +38,17 @@ type Repository struct {
|
||||
}
|
||||
|
||||
type GiteaClient struct {
|
||||
getRepository GetRepositoryMethod
|
||||
hasBranch HasBranchMethod
|
||||
hasUser HasUserMethod
|
||||
GetRepository GetRepositoryMethod
|
||||
HasBranch HasBranchMethod
|
||||
HasUser HasUserMethod
|
||||
GetFile GetFileMethod
|
||||
lookupCNAME LookupCNAMEMethod
|
||||
lookupRepoTXT LookupRepoTXTMethod
|
||||
LookupCNAME LookupCNAMEMethod
|
||||
LookupRepoTXT LookupRepoTXTMethod
|
||||
}
|
||||
|
||||
func NewGiteaClient(giteaUrl string, giteaClient *gitea.Client) GiteaClient {
|
||||
return GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
GetRepository: func(username, repositoryName string) (Repository, error) {
|
||||
repo, _, err := giteaClient.GetRepo(username, repositoryName)
|
||||
if err != nil {
|
||||
return Repository{}, err
|
||||
@ -58,7 +58,7 @@ func NewGiteaClient(giteaUrl string, giteaClient *gitea.Client) GiteaClient {
|
||||
Name: repo.Name,
|
||||
}, nil
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
res, _, err := giteaClient.ListRepoBranches(username, repositoryName, gitea.ListRepoBranchesOptions{})
|
||||
if err != nil {
|
||||
return false
|
||||
@ -71,7 +71,7 @@ func NewGiteaClient(giteaUrl string, giteaClient *gitea.Client) GiteaClient {
|
||||
}
|
||||
return false
|
||||
},
|
||||
hasUser: func(username string) bool {
|
||||
HasUser: func(username string) bool {
|
||||
_, _, err := giteaClient.GetUserInfo(username)
|
||||
return err == nil
|
||||
},
|
||||
@ -109,10 +109,10 @@ func NewGiteaClient(giteaUrl string, giteaClient *gitea.Client) GiteaClient {
|
||||
return content, true, err
|
||||
}
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
return dns.LookupCNAME(domain)
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
return dns.LookupRepoTXT(domain)
|
||||
},
|
||||
}
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"git.polynom.me/rio/internal/constants"
|
||||
"git.polynom.me/rio/internal/context"
|
||||
"git.polynom.me/rio/internal/repo"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -29,7 +28,7 @@ func makePageContentCacheEntry(username, path string) string {
|
||||
return username + ":" + path
|
||||
}
|
||||
|
||||
func addHeaders(csp, contentType string, contentLength int, w http.ResponseWriter) {
|
||||
func addHeaders(repoInfo *context.RepositoryInformation, contentType string, contentLength int, w http.ResponseWriter) {
|
||||
// Always set a content type
|
||||
if strings.Trim(contentType, " ") == "" {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
@ -41,8 +40,10 @@ func addHeaders(csp, contentType string, contentLength int, w http.ResponseWrite
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(contentLength))
|
||||
|
||||
if csp != "" {
|
||||
w.Header().Set("Content-Security-Policy", csp)
|
||||
if repoInfo != nil {
|
||||
for key, value := range repoInfo.Headers {
|
||||
w.Header().Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,16 +75,19 @@ func ServeFile(context *context.Context) {
|
||||
path,
|
||||
since,
|
||||
)
|
||||
csp := repo.GetCSPForRepository(context.Username, context.Reponame, "", context.Global.Gitea)
|
||||
repoInfo := context.Global.Cache.GetRepositoryInformation(
|
||||
context.Username,
|
||||
context.Reponame,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if !found {
|
||||
log.Errorf("Failed to get file %s/%s/%s (%s)", context.Username, context.Reponame, path, err)
|
||||
addHeaders(csp, "text/html", 0, context.Writer)
|
||||
addHeaders(repoInfo, "text/html", 0, context.Writer)
|
||||
context.Writer.WriteHeader(404)
|
||||
} else {
|
||||
log.Debugf("Request failed but page %s is cached in memory", path)
|
||||
addHeaders(csp, mimeType, len(content), context.Writer)
|
||||
addHeaders(repoInfo, mimeType, len(content), context.Writer)
|
||||
context.Writer.WriteHeader(200)
|
||||
context.Writer.Write(content)
|
||||
}
|
||||
@ -93,7 +97,7 @@ func ServeFile(context *context.Context) {
|
||||
|
||||
if found && !changed {
|
||||
log.Debugf("Page %s is unchanged and cached in memory", path)
|
||||
addHeaders(csp, mimeType, len(content), context.Writer)
|
||||
addHeaders(repoInfo, mimeType, len(content), context.Writer)
|
||||
context.Writer.WriteHeader(200)
|
||||
context.Writer.Write(content)
|
||||
return
|
||||
@ -115,7 +119,7 @@ func ServeFile(context *context.Context) {
|
||||
)
|
||||
|
||||
log.Debugf("Page %s requested from Gitea and cached in memory at %v", path, now)
|
||||
addHeaders(csp, mimeType, len(content), context.Writer)
|
||||
addHeaders(repoInfo, mimeType, len(content), context.Writer)
|
||||
context.Writer.WriteHeader(200)
|
||||
context.Writer.Write(content)
|
||||
|
||||
|
@ -3,54 +3,56 @@ package repo
|
||||
//go:generate mockgen -destination mock_repo_test.go -package repo code.gitea.io/sdk/gitea Client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.polynom.me/rio/internal/constants"
|
||||
"git.polynom.me/rio/internal/context"
|
||||
"git.polynom.me/rio/internal/gitea"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
ForbiddenHeaders = []string{
|
||||
"content-length",
|
||||
"content-type",
|
||||
"date",
|
||||
"location",
|
||||
"strict-transport-security",
|
||||
"set-cookie",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
pathCache = cache.New(1*time.Hour, 1*time.Hour)
|
||||
|
||||
// Caching the existence of an user
|
||||
userCache = cache.New(24*time.Hour, 12*time.Hour)
|
||||
|
||||
// Caches the existence of a Content-Security-Policy
|
||||
// Mapping: Repository key -> CSPCacheEntry
|
||||
cspCache = cache.New(24*time.Hour, 12*time.Hour)
|
||||
)
|
||||
|
||||
type PageCacheEntry struct {
|
||||
Repository Repository
|
||||
Repository gitea.Repository
|
||||
Path string
|
||||
}
|
||||
|
||||
type CSPCacheEntry struct {
|
||||
CSP string
|
||||
LastRequested time.Time
|
||||
}
|
||||
|
||||
func makePageCacheKey(domain, path string) string {
|
||||
return domain + "/" + path
|
||||
}
|
||||
|
||||
func makeCSPCacheKey(username, repositoryName string) string {
|
||||
return username + ":" + repositoryName
|
||||
}
|
||||
|
||||
func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, giteaClient *GiteaClient) (*Repository, error) {
|
||||
func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, giteaClient *gitea.GiteaClient) (*gitea.Repository, error) {
|
||||
log.Debugf("CNAME: %s", cname)
|
||||
log.Debugf("Looking up repository %s/%s", username, reponame)
|
||||
repo, err := giteaClient.getRepository(username, reponame)
|
||||
repo, err := giteaClient.GetRepository(username, reponame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !giteaClient.hasBranch(username, reponame, branchName) {
|
||||
if !giteaClient.HasBranch(username, reponame, branchName) {
|
||||
return nil, errors.New("Specified branch does not exist")
|
||||
}
|
||||
|
||||
@ -101,7 +103,7 @@ func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path
|
||||
|
||||
// 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 "".
|
||||
func RepoFromPath(username, host, cname, path string, giteaClient *GiteaClient) (*Repository, string, error) {
|
||||
func RepoFromPath(username, host, cname, path string, giteaClient *gitea.GiteaClient) (*gitea.Repository, string, error) {
|
||||
domain := host
|
||||
|
||||
// Guess the repository
|
||||
@ -115,7 +117,7 @@ func RepoFromPath(username, host, cname, path string, giteaClient *GiteaClient)
|
||||
// Allow specifying the repository name in the TXT record
|
||||
reponame := ""
|
||||
if cname != "" {
|
||||
repoLookup, err := giteaClient.lookupRepoTXT(host)
|
||||
repoLookup, err := giteaClient.LookupRepoTXT(host)
|
||||
if err == nil && repoLookup != "" {
|
||||
log.Infof(
|
||||
"TXT lookup for %s resulted in choosing repository %s",
|
||||
@ -166,52 +168,66 @@ func RepoFromPath(username, host, cname, path string, giteaClient *GiteaClient)
|
||||
// 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
|
||||
// usernames.
|
||||
func CanRequestCertificate(username string, giteaClient *GiteaClient) bool {
|
||||
func CanRequestCertificate(username string, giteaClient *gitea.GiteaClient) bool {
|
||||
if _, found := userCache.Get(username); found {
|
||||
return true
|
||||
}
|
||||
|
||||
hasUser := giteaClient.hasUser(username)
|
||||
hasUser := giteaClient.HasUser(username)
|
||||
if hasUser {
|
||||
userCache.Set(username, true, cache.DefaultExpiration)
|
||||
}
|
||||
return hasUser
|
||||
}
|
||||
|
||||
// Checks the repository username/repository@PagesBranch for a file named CSP. If it exists,
|
||||
// read it and return the value. If it does not exist, return defaultCsp.
|
||||
func GetCSPForRepository(username, repositoryName, defaultCsp string, giteaClient *GiteaClient) string {
|
||||
key := makeCSPCacheKey(username, repositoryName)
|
||||
cachedCsp, found := cspCache.Get(key)
|
||||
var since time.Time
|
||||
if found {
|
||||
since = cachedCsp.(CSPCacheEntry).LastRequested
|
||||
func filterHeaders(headers map[string]string) map[string]string {
|
||||
newHeaders := make(map[string]string)
|
||||
|
||||
for key, value := range headers {
|
||||
if slices.Contains[[]string, string](ForbiddenHeaders, strings.ToLower(key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
fetchedCsp, changed, err := giteaClient.GetFile(
|
||||
username,
|
||||
repositoryName,
|
||||
constants.PagesBranch,
|
||||
"CSP",
|
||||
&since,
|
||||
)
|
||||
csp := ""
|
||||
if err != nil {
|
||||
if found {
|
||||
return cachedCsp.(CSPCacheEntry).CSP
|
||||
newHeaders[key] = value
|
||||
}
|
||||
|
||||
csp = defaultCsp
|
||||
} else {
|
||||
csp = string(fetchedCsp)
|
||||
|
||||
if !found || changed {
|
||||
cspCache.Set(key, CSPCacheEntry{
|
||||
CSP: csp,
|
||||
LastRequested: time.Now(),
|
||||
}, cache.DefaultExpiration)
|
||||
}
|
||||
}
|
||||
|
||||
return csp
|
||||
return newHeaders
|
||||
}
|
||||
|
||||
func GetRepositoryInformation(owner, repoName string, ctx *context.GlobalContext) *context.RepositoryInformation {
|
||||
res := ctx.Cache.GetRepositoryInformation(owner, repoName)
|
||||
if res != nil {
|
||||
return res
|
||||
}
|
||||
|
||||
fetchedConfig, _, err := ctx.Gitea.GetFile(
|
||||
owner,
|
||||
repoName,
|
||||
constants.PagesBranch,
|
||||
"rio.json",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to request rio.json for %s/%s:%v", owner, repoName, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
err = json.Unmarshal(fetchedConfig, &payload)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to unmarshal rio.json for %s/%s:%v", owner, repoName, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
headers, found := payload["headers"]
|
||||
if !found {
|
||||
log.Warnf("Did not find headers key in rio.json for %s/%s", owner, repoName)
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
|
||||
info := context.RepositoryInformation{
|
||||
Headers: filterHeaders(headers.(map[string]string)),
|
||||
}
|
||||
ctx.Cache.SetRepositoryInformation(owner, repoName, info)
|
||||
return &info
|
||||
}
|
||||
|
@ -2,28 +2,42 @@ package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"git.polynom.me/rio/internal/gitea"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func clearCache() {
|
||||
pathCache.Flush()
|
||||
userCache.Flush()
|
||||
cspCache.Flush()
|
||||
func TestHeaderFilter(t *testing.T) {
|
||||
map1 := filterHeaders(
|
||||
map[string]string{
|
||||
"Content-Type": "hallo",
|
||||
"content-Type": "welt",
|
||||
"content-type": "uwu",
|
||||
"CONTENT-TYPE": "lol",
|
||||
"Content-Security-Policy": "none",
|
||||
},
|
||||
)
|
||||
|
||||
if len(map1) != 1 {
|
||||
t.Fatalf("filterHeaders allowed %d != 1 headers", len(map1))
|
||||
}
|
||||
|
||||
for key := range map1 {
|
||||
if strings.ToLower(key) == "content-type" {
|
||||
t.Fatalf("filterHeaders allowed Content-Type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickingCorrectRepositoryDefault(t *testing.T) {
|
||||
// Test that we default to the <username>.<pages domain> repository, if we have only
|
||||
// one path component.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username != "example-user" {
|
||||
t.Fatalf("Called with unknown user %s", username)
|
||||
}
|
||||
@ -31,9 +45,9 @@ func TestPickingCorrectRepositoryDefault(t *testing.T) {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
}
|
||||
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "example-user.pages.example.org" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -44,12 +58,12 @@ func TestPickingCorrectRepositoryDefault(t *testing.T) {
|
||||
t.Fatal("getFile called")
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("lookupCNAME called")
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("LookupCNAME called")
|
||||
return "", nil
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("lookupRepoTXT called")
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("LookupRepoTXT called")
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
@ -69,24 +83,22 @@ func TestPickingCorrectRepositoryDefault(t *testing.T) {
|
||||
func TestPickingCorrectRepositoryDefaultSubdirectory(t *testing.T) {
|
||||
// Test that we return the default repository when the first path component does
|
||||
// not correspong to an existing repository.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username != "example-user" {
|
||||
t.Fatalf("Called with unknown user %s", username)
|
||||
}
|
||||
if repositoryName == "assets" {
|
||||
return Repository{}, errors.New("Repository does not exist")
|
||||
return gitea.Repository{}, errors.New("gitea.Repository does not exist")
|
||||
} else if repositoryName == "example-user.pages.example.org" {
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
} else {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
}
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "example-user.pages.example.org" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -97,12 +109,12 @@ func TestPickingCorrectRepositoryDefaultSubdirectory(t *testing.T) {
|
||||
t.Fatal("getFile called")
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("lookupCNAME called")
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("LookupCNAME called")
|
||||
return "", nil
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("lookupRepoTXT called")
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("LookupRepoTXT called")
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
@ -122,28 +134,26 @@ func TestPickingCorrectRepositoryDefaultSubdirectory(t *testing.T) {
|
||||
func TestPickingCorrectRepositorySubdirectoryNoPagesBranch(t *testing.T) {
|
||||
// Test that we're picking the correct repository when the first path component
|
||||
// returns a repository without a pages branch.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username != "example-user" {
|
||||
t.Fatalf("Called with unknown user %s", username)
|
||||
}
|
||||
if repositoryName == "blog" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "blog",
|
||||
}, nil
|
||||
} else if repositoryName == "example-user.pages.example.org" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "example-user.pages.example.org",
|
||||
}, nil
|
||||
} else {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
}
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "example-user.pages.example.org" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -154,12 +164,12 @@ func TestPickingCorrectRepositorySubdirectoryNoPagesBranch(t *testing.T) {
|
||||
t.Fatal("getFile called")
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("lookupCNAME called")
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
t.Fatal("LookupCNAME called")
|
||||
return "", nil
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("lookupRepoTXT called")
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
t.Fatal("LookupRepoTXT called")
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
@ -181,21 +191,19 @@ func TestPickingCorrectRepositorySubdirectoryNoPagesBranch(t *testing.T) {
|
||||
|
||||
func TestPickingNoRepositoryInvalidCNAME(t *testing.T) {
|
||||
// Test that we're not picking a repository if the CNAME validation fails.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username == "example-user" && repositoryName == "example-user.pages.example.org" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "example-user.pages.example.org",
|
||||
}, nil
|
||||
} else {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
}
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "example-user.pages.example.org" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -210,37 +218,35 @@ func TestPickingNoRepositoryInvalidCNAME(t *testing.T) {
|
||||
t.Fatalf("Invalid file requested: %s/%s@%s:%s", username, repositoryName, branch, path)
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
return "", errors.New("No CNAME")
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
|
||||
_, _, err := RepoFromPath("example-user", "example-user.pages.example.org", "example-user.local", "index.html", &client)
|
||||
if err == nil {
|
||||
t.Fatal("Repository returned even though CNAME validation should fail")
|
||||
t.Fatal("gitea.Repository returned even though CNAME validation should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickingRepositoryValidCNAME(t *testing.T) {
|
||||
// Test that we're picking a repository, given a CNAME, if the CNAME validation succeeds.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username == "example-user" && repositoryName == "example-user.local" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "example-user.local",
|
||||
}, nil
|
||||
} else {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
}
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "example-user.local" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -255,10 +261,10 @@ func TestPickingRepositoryValidCNAME(t *testing.T) {
|
||||
t.Fatalf("Invalid file requested: %s/%s@%s:%s", username, repositoryName, branch, path)
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
return "", errors.New("No CNAME")
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
@ -275,21 +281,19 @@ func TestPickingRepositoryValidCNAME(t *testing.T) {
|
||||
func TestPickingRepositoryValidCNAMEWithTXTLookup(t *testing.T) {
|
||||
// Test that we're picking a repository, given a CNAME, if the CNAME validation succeeds
|
||||
// and the TXT lookup returns something different.
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username == "example-user" && repositoryName == "some-different-repository" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "some-different-repository",
|
||||
}, nil
|
||||
} else {
|
||||
t.Fatalf("Called with unknown repository %s", repositoryName)
|
||||
return Repository{}, nil
|
||||
return gitea.Repository{}, nil
|
||||
}
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "some-different-repository" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -304,10 +308,10 @@ func TestPickingRepositoryValidCNAMEWithTXTLookup(t *testing.T) {
|
||||
t.Fatalf("Invalid file requested: %s/%s@%s:%s", username, repositoryName, branch, path)
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
return "", errors.New("No CNAME")
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
if domain == "example-user.local" {
|
||||
return "some-different-repository", nil
|
||||
}
|
||||
@ -327,20 +331,18 @@ func TestPickingRepositoryValidCNAMEWithTXTLookup(t *testing.T) {
|
||||
func TestPickingRepositoryValidCNAMEWithTXTLookupAndSubdirectory(t *testing.T) {
|
||||
// Test that we're picking a repository, given a CNAME, if the CNAME validation succeeds
|
||||
// and the TXT lookup returns something different. Additionally, we now have a subdirectory
|
||||
defer clearCache()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
client := GiteaClient{
|
||||
getRepository: func(username, repositoryName string) (Repository, error) {
|
||||
client := gitea.GiteaClient{
|
||||
GetRepository: func(username, repositoryName string) (gitea.Repository, error) {
|
||||
if username == "example-user" && repositoryName == "some-different-repository" {
|
||||
return Repository{
|
||||
return gitea.Repository{
|
||||
Name: "some-different-repository",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return Repository{}, errors.New("Unknown repository")
|
||||
return gitea.Repository{}, errors.New("Unknown repository")
|
||||
},
|
||||
hasBranch: func(username, repositoryName, branchName string) bool {
|
||||
HasBranch: func(username, repositoryName, branchName string) bool {
|
||||
if username == "example-user" && repositoryName == "some-different-repository" && branchName == "pages" {
|
||||
return true
|
||||
}
|
||||
@ -355,10 +357,10 @@ func TestPickingRepositoryValidCNAMEWithTXTLookupAndSubdirectory(t *testing.T) {
|
||||
t.Fatalf("Invalid file requested: %s/%s@%s:%s", username, repositoryName, branch, path)
|
||||
return []byte{}, true, nil
|
||||
},
|
||||
lookupCNAME: func(domain string) (string, error) {
|
||||
LookupCNAME: func(domain string) (string, error) {
|
||||
return "", errors.New("No CNAME")
|
||||
},
|
||||
lookupRepoTXT: func(domain string) (string, error) {
|
||||
LookupRepoTXT: func(domain string) (string, error) {
|
||||
if domain == "example-user.local" {
|
||||
return "some-different-repository", nil
|
||||
}
|
||||
@ -374,32 +376,3 @@ func TestPickingRepositoryValidCNAMEWithTXTLookupAndSubdirectory(t *testing.T) {
|
||||
t.Fatalf("Invalid repository name returned: %s", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCSPForRepositoryNegativeIntegration(t *testing.T) {
|
||||
defer clearCache()
|
||||
|
||||
httpClient := http.Client{Timeout: 10 * time.Second}
|
||||
giteaClient, err := gitea.NewClient(
|
||||
"https://git.polynom.me",
|
||||
gitea.SetHTTPClient(&httpClient),
|
||||
gitea.SetToken(""),
|
||||
gitea.SetUserAgent("rio-testing"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Gitea client: %v", err)
|
||||
}
|
||||
client := NewGiteaClient("https://git.polynom.me", giteaClient)
|
||||
|
||||
// The repository has no CSP file, so it should return the invalid value
|
||||
defaultValue := "<INVALID>"
|
||||
csp := GetCSPForRepository(
|
||||
"papatutuwawa",
|
||||
"rio",
|
||||
defaultValue,
|
||||
&client,
|
||||
)
|
||||
|
||||
if csp != defaultValue {
|
||||
t.Fatalf("Unexpected CSP returned: %s", csp)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"git.polynom.me/rio/internal/certificates"
|
||||
"git.polynom.me/rio/internal/dns"
|
||||
"git.polynom.me/rio/internal/gitea"
|
||||
"git.polynom.me/rio/internal/repo"
|
||||
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
@ -81,7 +82,7 @@ func getUsername(sni, pagesDomain string) (string, error) {
|
||||
return dns.ExtractUsername(pagesDomain, sni), nil
|
||||
}
|
||||
|
||||
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, giteaClient *repo.GiteaClient) *tls.Config {
|
||||
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, giteaClient *gitea.GiteaClient) *tls.Config {
|
||||
return &tls.Config{
|
||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// Validate that we should even care about this domain
|
||||
|
Loading…
Reference in New Issue
Block a user