rio/internal/repo/repo.go

213 lines
4.9 KiB
Go
Raw Normal View History

2024-01-01 13:19:19 +00:00
package repo
2023-12-31 12:26:56 +00:00
//go:generate mockgen -destination mock_repo_test.go -package repo code.gitea.io/sdk/gitea Client
2023-12-31 12:26:56 +00:00
import (
"encoding/json"
2023-12-31 12:26:56 +00:00
"errors"
"slices"
2023-12-31 12:26:56 +00:00
"strings"
"git.polynom.me/rio/internal/constants"
"git.polynom.me/rio/internal/context"
"git.polynom.me/rio/internal/gitea"
2024-01-01 13:19:19 +00:00
2023-12-31 12:26:56 +00:00
log "github.com/sirupsen/logrus"
)
var (
ForbiddenHeaders = []string{
"content-length",
"content-type",
"date",
"location",
"strict-transport-security",
"set-cookie",
}
)
2024-02-03 14:39:31 +00:00
func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, ctx *context.GlobalContext) (*gitea.Repository, error) {
log.Debugf("CNAME: %s", cname)
2023-12-31 12:26:56 +00:00
log.Debugf("Looking up repository %s/%s", username, reponame)
2024-02-03 14:39:31 +00:00
repo, err := ctx.Gitea.GetRepository(username, reponame)
2023-12-31 12:26:56 +00:00
if err != nil {
return nil, err
}
2024-02-03 14:39:31 +00:00
if !ctx.Gitea.HasBranch(username, reponame, branchName) {
return nil, errors.New("Specified branch does not exist")
}
2023-12-31 12:26:56 +00:00
// Check if the CNAME file matches
2024-02-03 14:39:31 +00:00
2023-12-31 12:26:56 +00:00
if cname != "" {
log.Debug("Checking CNAME")
2024-02-03 14:39:31 +00:00
repoInfo := GetRepositoryInformation(username, reponame, ctx)
if repoInfo == nil {
log.Warn("Repository does not contain a rio.json file")
return nil, errors.New("No CNAME available in repository")
2023-12-31 12:26:56 +00:00
}
2024-02-03 14:39:31 +00:00
log.Debugf("CNAME Content: \"%s\"", repoInfo.CNAME)
if repoInfo.CNAME != host {
log.Warnf("CNAME mismatch: Repo '%s', Host '%s'", repoInfo.CNAME, host)
2023-12-31 12:26:56 +00:00
return nil, errors.New("CNAME mismatch")
}
}
// Cache data
2024-02-03 14:39:31 +00:00
ctx.Cache.SetRepositoryPath(
domain,
path,
context.RepositoryPathInformation{
Repository: repo,
Path: path,
2023-12-31 12:26:56 +00:00
},
)
return &repo, nil
2023-12-31 12:26:56 +00:00
}
2024-01-06 20:26:09 +00:00
// 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 "".
2024-02-03 14:39:31 +00:00
func RepoFromPath(username, host, cname, path string, ctx *context.GlobalContext) (*gitea.Repository, string, error) {
2023-12-31 12:26:56 +00:00
domain := host
// Guess the repository
2024-02-03 14:39:31 +00:00
entry := ctx.Cache.GetRepositoryPath(domain, path)
if entry != nil {
return &entry.Repository, entry.Path, nil
}
// Allow specifying the repository name in the TXT record
reponame := ""
if cname != "" {
2024-02-03 14:39:31 +00:00
repoLookup, err := ctx.Gitea.LookupRepoTXT(host)
if err == nil && repoLookup != "" {
log.Infof(
"TXT lookup for %s resulted in choosing repository %s",
2024-01-06 20:26:09 +00:00
host,
repoLookup,
)
reponame = repoLookup
}
2023-12-31 12:26:56 +00:00
}
pathParts := strings.Split(path, "/")
2024-01-06 20:30:44 +00:00
log.Debugf("reponame='%s' len(pathParts)='%d'", reponame, len(pathParts))
if reponame == "" && len(pathParts) > 1 {
2023-12-31 12:26:56 +00:00
log.Debugf("Trying repository %s", pathParts[0])
modifiedPath := strings.Join(pathParts[1:], "/")
repo, err := lookupRepositoryAndCache(
username,
pathParts[0],
constants.PagesBranch,
2023-12-31 12:26:56 +00:00
host,
domain,
modifiedPath,
cname,
2024-02-03 14:39:31 +00:00
ctx,
2023-12-31 12:26:56 +00:00
)
if err == nil {
return repo, modifiedPath, nil
}
}
if reponame == "" {
reponame = domain
2023-12-31 21:41:51 +00:00
}
log.Debugf("Trying repository %s/%s", username, reponame)
2023-12-31 12:26:56 +00:00
repo, err := lookupRepositoryAndCache(
username,
reponame,
constants.PagesBranch,
2023-12-31 12:26:56 +00:00
host,
domain,
path,
cname,
2024-02-03 14:39:31 +00:00
ctx,
2023-12-31 12:26:56 +00:00
)
return repo, path, err
}
// 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.
2024-02-03 14:39:31 +00:00
func CanRequestCertificate(username string, ctx *context.GlobalContext) bool {
found := ctx.Cache.GetUser(username)
if found {
return true
}
2024-02-03 14:39:31 +00:00
hasUser := ctx.Gitea.HasUser(username)
if hasUser {
2024-02-03 14:39:31 +00:00
ctx.Cache.SetUser(username)
}
return hasUser
}
2024-01-06 16:42:08 +00:00
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
}
newHeaders[key] = value
2024-01-06 16:42:08 +00:00
}
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,
2024-01-06 16:42:08 +00:00
constants.PagesBranch,
"rio.json",
nil,
2024-01-06 16:42:08 +00:00
)
if err != nil {
log.Errorf("Failed to request rio.json for %s/%s:%v", owner, repoName, err)
return nil
}
2024-01-06 16:42:08 +00:00
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
}
2024-01-06 16:42:08 +00:00
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)
2024-01-06 16:42:08 +00:00
}
2024-02-03 14:51:23 +00:00
cname, found := payload["CNAME"]
if found {
switch cname.(type) {
case string:
// NOOP
default:
log.Warnf("CNAME attribute is not a string for %s/%s", owner, repoName)
cname = ""
}
} else {
cname = ""
}
info := context.RepositoryInformation{
Headers: filterHeaders(headers.(map[string]string)),
2024-02-03 14:51:23 +00:00
CNAME: cname.(string),
}
ctx.Cache.SetRepositoryInformation(owner, repoName, info)
return &info
2024-01-06 16:42:08 +00:00
}