mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-14 03:01:48 +00:00
[breaking-change] Use a distinct scope for forge DNS allowlist authz.
Before this commit, a `_git-pages-repository.<host>` TXT record would
allow both forge DNS allowlist authorization, as well as normal DNS
allowlist authorization. This means that a site set up to have its
contents updated by a Forgejo Action could have its contents replaced
by the contents of the repository which contains the Forgejo Action,
which will effectively erase the site in most cases. This is a classic
confused deputy scenario.
To fix this, forge DNS allowlist authorization now uses a distinct
`_git-pages-forge-allowlist.<host>` TXT record, removing ambiguity
that allows this scenario to happen.
The issue was introduced in 27a6de792c
and existed in `main` for about a hour, so it is unlikely anybody
has been impacted by this.
This commit is contained in:
@@ -121,7 +121,7 @@ The authorization flow for content updates (`PUT`, `PATCH`, `DELETE`, `POST` req
|
||||
- **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. **Forge Authorization (wildcard):** If the method is `PUT` or `PATCH` or `DELETE`, and (unless the method is `DELETE`) the body contains an archive, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and `[[wildcard]].authorization` is non-empty, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at the *matching* clone URL (as defined above) as determined by an API call to the forge, the request is authorized.
|
||||
6. **Forge Authorization (DNS allowlist):** If the method is `PUT` or `PATCH` or `DELETE`, and (unless the method is `DELETE`) the body contains an archive, and the request URL is `scheme://<user>.<host>/`, and a TXT record lookup at `_git-pages-repository.<host>` returns a set of well-formed absolute URLs, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at any of the URLs in the TXT records as determined by an API call to the forge, the request is authorized.
|
||||
6. **Forge Authorization (DNS allowlist):** If the method is `PUT` or `PATCH` or `DELETE`, and (unless the method is `DELETE`) the body contains an archive, and the request URL is `scheme://<user>.<host>/`, and a TXT record lookup at `_git-pages-forge-allowlist.<host>` returns a set of well-formed absolute URLs, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at any of the URLs in the TXT records as determined by an API call to the forge, the request is authorized.
|
||||
7. **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:
|
||||
|
||||
10
src/auth.go
10
src/auth.go
@@ -179,7 +179,7 @@ func authorizeDNSChallenge(r *http.Request) (*Authorization, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authorizeDNSAllowlist(r *http.Request) (*Authorization, error) {
|
||||
func authorizeDNSAllowlist(r *http.Request, scope string) (*Authorization, error) {
|
||||
host, err := GetHost(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -190,7 +190,7 @@ func authorizeDNSAllowlist(r *http.Request) (*Authorization, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowlistHostname := fmt.Sprintf("_git-pages-repository.%s", host)
|
||||
allowlistHostname := fmt.Sprintf("_%s.%s", scope, host)
|
||||
records, err := net.LookupTXT(allowlistHostname)
|
||||
if err != nil {
|
||||
return nil, AuthError{http.StatusUnauthorized,
|
||||
@@ -421,7 +421,7 @@ func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) {
|
||||
|
||||
// DNS allowlist gives authority to update but not delete.
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodPost {
|
||||
auth, err = authorizeDNSAllowlist(r)
|
||||
auth, err = authorizeDNSAllowlist(r, "git-pages-repository")
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
@@ -741,7 +741,7 @@ func authorizeForgeWildcard(r *http.Request) (*Authorization, error) {
|
||||
}
|
||||
|
||||
// Validates a provided forge token against a repository URL extracted from the DNS allowlist
|
||||
// records of the target domain (`_git-pages-repository.*`).
|
||||
// records of the target domain specified in `_git-pages-forge-authorization.*`.
|
||||
func authorizeForgeDNSAllowlist(r *http.Request) (*Authorization, error) {
|
||||
forgeToken := r.Header.Get("Forge-Authorization")
|
||||
if forgeToken == "" {
|
||||
@@ -749,7 +749,7 @@ func authorizeForgeDNSAllowlist(r *http.Request) (*Authorization, error) {
|
||||
}
|
||||
|
||||
var errs []error
|
||||
if dnsAuth, err := authorizeDNSAllowlist(r); err != nil {
|
||||
if dnsAuth, err := authorizeDNSAllowlist(r, "git-pages-forge-allowlist"); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else if dnsAuth != nil {
|
||||
// DNS allows uploads from some repositories, but we don't know yet if the forge token
|
||||
|
||||
Reference in New Issue
Block a user