diff --git a/cmd/rio/main.go b/cmd/rio/main.go index 8dd5b8c..075ecb9 100644 --- a/cmd/rio/main.go +++ b/cmd/rio/main.go @@ -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 diff --git a/internal/context/cache.go b/internal/context/cache.go new file mode 100644 index 0000000..d83bbeb --- /dev/null +++ b/internal/context/cache.go @@ -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) +} diff --git a/internal/context/context.go b/internal/context/context.go index 1ed1237..2cc6c7c 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -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 { diff --git a/internal/repo/client.go b/internal/gitea/client.go similarity index 86% rename from internal/repo/client.go rename to internal/gitea/client.go index f0eb123..89444b7 100644 --- a/internal/repo/client.go +++ b/internal/gitea/client.go @@ -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) }, } diff --git a/internal/pages/pages.go b/internal/pages/pages.go index 5dae5a4..0ba28ee 100644 --- a/internal/pages/pages.go +++ b/internal/pages/pages.go @@ -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) diff --git a/internal/repo/repo.go b/internal/repo/repo.go index e0b6792..8e3e529 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -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) - fetchedCsp, changed, err := giteaClient.GetFile( - username, - repositoryName, - constants.PagesBranch, - "CSP", - &since, - ) - csp := "" - if err != nil { - if found { - return cachedCsp.(CSPCacheEntry).CSP + for key, value := range headers { + if slices.Contains[[]string, string](ForbiddenHeaders, strings.ToLower(key)) { + continue } - csp = defaultCsp - } else { - csp = string(fetchedCsp) - - if !found || changed { - cspCache.Set(key, CSPCacheEntry{ - CSP: csp, - LastRequested: time.Now(), - }, cache.DefaultExpiration) - } + newHeaders[key] = value } - 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 } diff --git a/internal/repo/repo_test.go b/internal/repo/repo_test.go index ebdb382..8a236f2 100644 --- a/internal/repo/repo_test.go +++ b/internal/repo/repo_test.go @@ -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 . 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 := "" - csp := GetCSPForRepository( - "papatutuwawa", - "rio", - defaultValue, - &client, - ) - - if csp != defaultValue { - t.Fatalf("Unexpected CSP returned: %s", csp) - } -} diff --git a/internal/server/tls.go b/internal/server/tls.go index ae500f7..3d477e5 100644 --- a/internal/server/tls.go +++ b/internal/server/tls.go @@ -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