[breaking-change] Only allow a single [[wildcard]].index-repo.

The git-pages webhook security model depends on there being
a 1:1 mapping between site URLs and repositories; being able to
specify multiple of them breaks this model, as anyone could switch
the published site from one to the other if both repositories exist.
This commit is contained in:
Catherine
2026-01-19 02:25:01 +00:00
parent 9b25ccdc35
commit 0d33c64372
4 changed files with 35 additions and 47 deletions

View File

@@ -12,7 +12,7 @@ metrics = "tcp/localhost:3002"
[[wildcard]] # non-default section
domain = "codeberg.page"
clone-url = "https://codeberg.org/<user>/<project>.git"
index-repos = ["<user>.codeberg.page", "pages"]
index-repo = "pages"
index-repo-branch = "main"
authorization = "forgejo"

View File

@@ -265,8 +265,8 @@ func authorizeWildcardMatchSite(r *http.Request, pattern *WildcardPattern) (*Aut
}
if userName, found := pattern.Matches(host); found {
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
return &Authorization{repoURLs, branch}, nil
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
return &Authorization{[]string{repoURL}, branch}, nil
} else {
return nil, AuthError{
http.StatusUnauthorized,
@@ -632,25 +632,20 @@ func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
}
if userName, found := pattern.Matches(host); found {
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
for _, repoURL := range repoURLs {
parsedRepoURL, err := url.Parse(repoURL)
if err != nil {
panic(err) // misconfiguration
}
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
errs = append(errs, err)
continue
}
// This will actually be ignored by the caller of AuthorizeUpdateFromArchive,
// but we return this information as it makes sense to do contextually here.
return &Authorization{
[]string{repoURL},
branch,
}, nil
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
parsedRepoURL, err := url.Parse(repoURL)
if err != nil {
panic(err) // misconfiguration
}
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
errs = append(errs, err)
continue
}
// This will actually be ignored by the caller of AuthorizeUpdateFromArchive,
// but we return this information as it makes sense to do contextually here.
return &Authorization{[]string{repoURL}, branch}, nil
}
}

View File

@@ -79,11 +79,11 @@ type ServerConfig struct {
}
type WildcardConfig struct {
Domain string `toml:"domain"`
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
IndexRepos []string `toml:"index-repos" default:"[]"`
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
Authorization string `toml:"authorization"`
Domain string `toml:"domain"`
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
IndexRepo string `toml:"index-repo" default:"pages"`
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
Authorization string `toml:"authorization"`
}
type FallbackConfig struct {

View File

@@ -11,7 +11,7 @@ import (
type WildcardPattern struct {
Domain []string
CloneURL *fasttemplate.Template
IndexRepos []*fasttemplate.Template
IndexRepo *fasttemplate.Template
IndexBranch string
Authorization bool
}
@@ -49,27 +49,24 @@ func (pattern *WildcardPattern) Matches(host string) (string, bool) {
return subdomain, true
}
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) ([]string, string) {
var repoURLs []string
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) (string, string) {
var repoURL string
var branch string
repoURLTemplate := pattern.CloneURL
if projectName == ".index" {
for _, indexRepoTemplate := range pattern.IndexRepos {
indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName})
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": indexRepo,
}))
}
repoURL = repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": pattern.IndexRepo.ExecuteString(map[string]any{"user": userName}),
})
branch = pattern.IndexBranch
} else {
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
repoURL = repoURLTemplate.ExecuteString(map[string]any{
"user": userName,
"project": projectName,
}))
})
branch = "pages"
}
return repoURLs, branch
return repoURL, branch
}
func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
@@ -80,14 +77,10 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
return nil, fmt.Errorf("wildcard pattern: clone URL: %w", err)
}
var indexRepoTemplates []*fasttemplate.Template
var indexRepoBranch string = config.IndexRepoBranch
for _, indexRepo := range config.IndexRepos {
indexRepoTemplate, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
if err != nil {
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
}
indexRepoTemplates = append(indexRepoTemplates, indexRepoTemplate)
indexRepoTemplate, err := fasttemplate.NewTemplate(config.IndexRepo, "<", ">")
if err != nil {
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
}
authorization := false
@@ -107,7 +100,7 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
Domain: strings.Split(config.Domain, "."),
CloneURL: cloneURLTemplate,
IndexRepos: indexRepoTemplates,
IndexRepo: indexRepoTemplate,
IndexBranch: indexRepoBranch,
Authorization: authorization,
})