mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-22 23:21:45 +00:00
[breaking-change] Allow multiple wildcard domains to be configured.
This commit is contained in:
@@ -86,16 +86,16 @@ The authorization flow for content updates (`PUT`, `DELETE`, `POST` requests) pr
|
||||
- **`Pages` scheme:** Request includes an `Authorization: Pages <token>` header.
|
||||
- **`Basic` scheme:** Request includes an `Authorization: Basic <basic>` header, where `<basic>` is equal to `Base64("Pages:<token>")`. (Useful for non-Forgejo forges.)
|
||||
3. **DNS Allowlist:** If the method is `PUT` or `POST`, and a TXT record lookup at `_git-pages-repository.<host>` returns a set of well-formed absolute URLs, and (for `PUT` requests) the body contains a repository URL, and the requested clone URLs is contained in this set of URLs, the request is authorized.
|
||||
4. **Wildcard Match (Site):** If the method is `POST`, and a `[wildcard]` configuration section is present, and the suffix of a hostname (compared label-wise) is equal to `[wildcard].domain`, and (for `PUT` requests) the body contains a repository URL, and the requested clone URL is a *matching* clone URL, the request is authorized.
|
||||
- **Index repository:** If the request URL is `scheme://<user>.<host>/`, a *matching* clone URL is computed by templating `[wildcard.clone-url]` with `<user>` and `<project>`, where `<project>` is computed by templating each element of `[wildcard].index-repos` with `<user>`.
|
||||
- **Project repository:** If the request URL is `scheme://<user>.<host>/<project>/`, a *matching* clone URL is computed by templating `[wildcard.clone-url]` with `<user>` and `<project>`.
|
||||
4. **Wildcard Match (Site):** If the method is `POST`, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and (for `PUT` requests) the body contains a repository URL, and the requested clone URL is a *matching* clone URL, the request is authorized.
|
||||
- **Index repository:** If the request URL is `scheme://<user>.<host>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, where `<project>` is computed by templating each element of `[[wildcard]].index-repos` with `<user>`, and `[[wildcard]]` is the section where the match occurred.
|
||||
- **Project repository:** If the request URL is `scheme://<user>.<host>/<project>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, and `[[wildcard]]` is the section where the match occurred.
|
||||
5. **Default Deny:** Otherwise, the request is not authorized.
|
||||
|
||||
The authorization flow for metadata retrieval (`GET` requests with site paths starting with `.git-pages/`) in the following order, with the first of multiple applicable rule taking precedence:
|
||||
|
||||
1. **Development Mode:** Same as for content updates.
|
||||
2. **DNS Challenge:** Same as for content updates.
|
||||
3. **Wildcard Match (Domain):** If a `[wildcard]` configuration section is present, and the suffix of a hostname (compared label-wise) is equal to `[wildcard].domain`, the request is authorized.
|
||||
3. **Wildcard Match (Domain):** If a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, the request is authorized.
|
||||
4. **Default Deny:** Otherwise, the request is not authorized.
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ pages = "tcp/:3000"
|
||||
caddy = "tcp/:3001"
|
||||
health = "tcp/:3002"
|
||||
|
||||
# [wildcard]
|
||||
# [[wildcard]]
|
||||
# domain = "codeberg.page"
|
||||
# clone-url = "https://codeberg.org/<user>/<project>.git"
|
||||
# index-repos = ["<user>.codeberg.page", "pages"]
|
||||
|
||||
54
src/auth.go
54
src/auth.go
@@ -165,24 +165,24 @@ func authorizeDNSAllowlist(r *http.Request) (*Authorization, error) {
|
||||
return &Authorization{repoURLs}, err
|
||||
}
|
||||
|
||||
func authorizeWildcardMatchHost(r *http.Request) (*Authorization, error) {
|
||||
func authorizeWildcardMatchHost(r *http.Request, pattern *WildcardPattern) (*Authorization, error) {
|
||||
host, err := GetHost(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostParts := strings.Split(host, ".")
|
||||
if slices.Equal(hostParts[1:], wildcardPattern.Domain) {
|
||||
if slices.Equal(hostParts[1:], pattern.Domain) {
|
||||
return &Authorization{}, nil
|
||||
} else {
|
||||
return nil, AuthError{
|
||||
http.StatusUnauthorized,
|
||||
fmt.Sprintf("domain %s does not match wildcard *.%s", host, config.Wildcard.Domain),
|
||||
fmt.Sprintf("domain %s does not match wildcard %s", host, pattern.GetHost()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func authorizeWildcardMatchSite(r *http.Request) (*Authorization, error) {
|
||||
func authorizeWildcardMatchSite(r *http.Request, pattern *WildcardPattern) (*Authorization, error) {
|
||||
host, err := GetHost(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -194,12 +194,12 @@ func authorizeWildcardMatchSite(r *http.Request) (*Authorization, error) {
|
||||
}
|
||||
|
||||
hostParts := strings.Split(host, ".")
|
||||
if slices.Equal(hostParts[1:], wildcardPattern.Domain) {
|
||||
if slices.Equal(hostParts[1:], pattern.Domain) {
|
||||
userName := hostParts[0]
|
||||
var repoURLs []string
|
||||
repoURLTemplate := wildcardPattern.CloneURL
|
||||
repoURLTemplate := pattern.CloneURL
|
||||
if projectName == ".index" {
|
||||
for _, indexRepoTemplate := range wildcardPattern.IndexRepos {
|
||||
for _, indexRepoTemplate := range pattern.IndexRepos {
|
||||
indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName})
|
||||
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]interface{}{
|
||||
"user": userName,
|
||||
@@ -216,7 +216,7 @@ func authorizeWildcardMatchSite(r *http.Request) (*Authorization, error) {
|
||||
} else {
|
||||
return nil, AuthError{
|
||||
http.StatusUnauthorized,
|
||||
fmt.Sprintf("domain %s does not match wildcard *.%s", host, config.Wildcard.Domain),
|
||||
fmt.Sprintf("domain %s does not match wildcard %s", host, pattern.GetHost()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,15 +239,16 @@ func AuthorizeMetadataRetrieval(r *http.Request) (*Authorization, error) {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
auth, err = authorizeWildcardMatchHost(r)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
log.Printf("auth: wildcard *.%s\n",
|
||||
config.Wildcard.Domain)
|
||||
return auth, nil
|
||||
for _, pattern := range wildcardPatterns {
|
||||
auth, err = authorizeWildcardMatchHost(r, pattern)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
log.Printf("auth: wildcard %s\n", pattern.GetHost())
|
||||
return auth, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Join(causes...)
|
||||
@@ -290,15 +291,16 @@ func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) {
|
||||
|
||||
// Wildcard match is only available for webhooks, not the REST API.
|
||||
if r.Method == http.MethodPost {
|
||||
auth, err = authorizeWildcardMatchSite(r)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
log.Printf("auth: wildcard *.%s: allow %v\n",
|
||||
config.Wildcard.Domain, auth.repoURLs)
|
||||
return auth, nil
|
||||
for _, pattern := range wildcardPatterns {
|
||||
auth, err = authorizeWildcardMatchSite(r, pattern)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
log.Printf("auth: wildcard %s: allow %v\n", pattern.GetHost(), auth.repoURLs)
|
||||
return auth, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ type Config struct {
|
||||
Caddy string `toml:"caddy"`
|
||||
Health string `toml:"health"`
|
||||
} `toml:"listen"`
|
||||
Wildcard struct {
|
||||
Wildcard []struct {
|
||||
Domain string `toml:"domain"`
|
||||
CloneURL string `toml:"clone-url"`
|
||||
IndexRepos []string `toml:"index-repos"`
|
||||
@@ -107,26 +107,36 @@ type WildcardPattern struct {
|
||||
IndexRepos []*fasttemplate.Template
|
||||
}
|
||||
|
||||
var wildcardPattern WildcardPattern
|
||||
func (pattern *WildcardPattern) GetHost() string {
|
||||
parts := []string{"*"}
|
||||
parts = append(parts, pattern.Domain...)
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
var wildcardPatterns []*WildcardPattern
|
||||
|
||||
func CompileWildcardPattern() {
|
||||
wildcardPattern = WildcardPattern{
|
||||
Domain: strings.Split(config.Wildcard.Domain, "."),
|
||||
}
|
||||
for _, configWildcard := range config.Wildcard {
|
||||
wildcardPattern := WildcardPattern{
|
||||
Domain: strings.Split(configWildcard.Domain, "."),
|
||||
}
|
||||
|
||||
template, err := fasttemplate.NewTemplate(config.Wildcard.CloneURL, "<", ">")
|
||||
if err != nil {
|
||||
log.Fatalf("wildcard pattern: clone URL: %s", err)
|
||||
} else {
|
||||
wildcardPattern.CloneURL = template
|
||||
}
|
||||
|
||||
for _, indexRepo := range config.Wildcard.IndexRepos {
|
||||
template, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
|
||||
template, err := fasttemplate.NewTemplate(configWildcard.CloneURL, "<", ">")
|
||||
if err != nil {
|
||||
log.Fatalf("wildcard pattern: clone URL: %s", err)
|
||||
} else {
|
||||
wildcardPattern.IndexRepos = append(wildcardPattern.IndexRepos, template)
|
||||
wildcardPattern.CloneURL = template
|
||||
}
|
||||
|
||||
for _, indexRepo := range configWildcard.IndexRepos {
|
||||
template, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
|
||||
if err != nil {
|
||||
log.Fatalf("wildcard pattern: clone URL: %s", err)
|
||||
} else {
|
||||
wildcardPattern.IndexRepos = append(wildcardPattern.IndexRepos, template)
|
||||
}
|
||||
}
|
||||
|
||||
wildcardPatterns = append(wildcardPatterns, &wildcardPattern)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user