]> source.dussan.org Git - gitea.git/commitdiff
Improve reverse proxy documents and clarify the AppURL guessing behavior (#31003...
authorGiteabot <teabot@gitea.io>
Sun, 19 May 2024 15:22:54 +0000 (23:22 +0800)
committerGitHub <noreply@github.com>
Sun, 19 May 2024 15:22:54 +0000 (15:22 +0000)
Backport #31003 by wxiaoguang

Fix #31002

1. Mention Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea
2. Clarify the basic requirements and move the "general configuration" to the top
3. Add a comment for the "container registry"
4. Use 1.21 behavior if the reverse proxy is not correctly configured

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
docs/content/administration/reverse-proxies.en-us.md
modules/httplib/url.go
modules/httplib/url_test.go
routers/api/packages/container/container.go
routers/web/admin/admin_test.go

index fe54c67d0279573c252f2fe951ba970712b93b93..5fbd0eb0b73d3d6d34fae55cd36e92df3c8cb582 100644 (file)
@@ -17,15 +17,35 @@ menu:
 
 # Reverse Proxies
 
+## General configuration
+
+1. Set `[server] ROOT_URL = https://git.example.com/` in your `app.ini` file.
+2. Make the reverse-proxy pass `https://git.example.com/foo` to `http://gitea:3000/foo`.
+3. Make sure the reverse-proxy does not decode the URI. The request `https://git.example.com/a%2Fb` should be passed as `http://gitea:3000/a%2Fb`.
+4. Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea to make Gitea see the real URL being visited.
+
+### Use a sub-path
+
+Usually it's **not recommended** to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases.
+
+To make Gitea work with a sub-path (eg: `https://common.example.com/gitea/`),
+there are some extra requirements besides the general configuration above:
+
+1. Use `[server] ROOT_URL = https://common.example.com/gitea/` in your `app.ini` file.
+2. Make the reverse-proxy pass `https://common.example.com/gitea/foo` to `http://gitea:3000/foo`.
+3. The container registry requires a fixed sub-path `/v2` at the root level which must be configured:
+   - Make the reverse-proxy pass `https://common.example.com/v2` to `http://gitea:3000/v2`.
+   - Make sure the URI and headers are also correctly passed (see the general configuration above).
+
 ## Nginx
 
-If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`:
+If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`.
 
-```
-server {
-    listen 80;
-    server_name git.example.com;
+Make sure `client_max_body_size` is large enough, otherwise there would be "413 Request Entity Too Large" error when uploading large files.
 
+```nginx
+server {
+    ...
     location / {
         client_max_body_size 512M;
         proxy_pass http://localhost:3000;
@@ -39,37 +59,35 @@ server {
 }
 ```
 
-### Resolving Error: 413 Request Entity Too Large
-
-This error indicates nginx is configured to restrict the file upload size,
-it affects attachment uploading, form posting, package uploading and LFS pushing, etc.
-You can fine tune the `client_max_body_size` option according to [nginx document](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size).
-
 ## Nginx with a sub-path
 
-In case you already have a site, and you want Gitea to share the domain name, you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section inside the `http` section of `nginx.conf`:
+In case you already have a site, and you want Gitea to share the domain name,
+you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section
+into the `http` section of `nginx.conf`:
 
-```
+```nginx
 server {
-    listen 80;
-    server_name git.example.com;
-
-    # Note: Trailing slash
-    location /gitea/ {
+    ...
+    location ~ ^/(gitea|v2)($|/) {
         client_max_body_size 512M;
 
-        # make nginx use unescaped URI, keep "%2F" as is
+        # make nginx use unescaped URI, keep "%2F" as-is, remove the "/gitea" sub-path prefix, pass "/v2" as-is.
         rewrite ^ $request_uri;
-        rewrite ^/gitea(/.*) $1 break;
+        rewrite ^(/gitea)?(/.*) $2 break;
         proxy_pass http://127.0.0.1:3000$uri;
 
         # other common HTTP headers, see the "Nginx" config section above
-        proxy_set_header ...
+        proxy_set_header Connection $http_connection;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
     }
 }
 ```
 
-Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/git/` correctly in your configuration.
+Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/gitea/` correctly in your configuration.
 
 ## Nginx and serve static resources directly
 
@@ -93,7 +111,7 @@ or use a cdn for the static files.
 
 Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
 
-```apacheconf
+```nginx
 server {
     listen 80;
     server_name git.example.com;
@@ -112,7 +130,7 @@ server {
 
 Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration.
 
-```apacheconf
+```nginx
 # application server running Gitea
 server {
     listen 80;
@@ -124,7 +142,7 @@ server {
 }
 ```
 
-```apacheconf
+```nginx
 # static content delivery server
 server {
     listen 80;
@@ -151,6 +169,8 @@ If you want Apache HTTPD to serve your Gitea instance, you can add the following
     ProxyRequests off
     AllowEncodedSlashes NoDecode
     ProxyPass / http://localhost:3000/ nocanon
+    ProxyPreserveHost On
+    RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
 </VirtualHost>
 ```
 
@@ -172,6 +192,8 @@ In case you already have a site, and you want Gitea to share the domain name, yo
     AllowEncodedSlashes NoDecode
     # Note: no trailing slash after either /git or port
     ProxyPass /git http://localhost:3000 nocanon
+    ProxyPreserveHost On
+    RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
 </VirtualHost>
 ```
 
@@ -183,7 +205,7 @@ Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`.
 
 If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile:
 
-```apacheconf
+```
 git.example.com {
     reverse_proxy localhost:3000
 }
@@ -193,7 +215,7 @@ git.example.com {
 
 In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile:
 
-```apacheconf
+```
 git.example.com {
     route /git/* {
         uri strip_prefix /git
@@ -371,19 +393,3 @@ gitea:
 This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
 
 Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.
-
-## General sub-path configuration
-
-Usually it's not recommended to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases.
-
-If you really need to do so, to make Gitea works with sub-path (eg: `http://example.com/gitea/`), here are the requirements:
-
-1. Set `[server] ROOT_URL = http://example.com/gitea/` in your `app.ini` file.
-2. Make the reverse-proxy pass `http://example.com/gitea/foo` to `http://gitea-server:3000/foo`.
-3. Make sure the reverse-proxy not decode the URI, the request `http://example.com/gitea/a%2Fb` should be passed as `http://gitea-server:3000/a%2Fb`.
-
-## Docker / Container Registry
-
-The container registry uses a fixed sub-path `/v2` which can't be changed.
-Even if you deploy Gitea with a different sub-path, `/v2` will be used by the `docker` client.
-Therefore you may need to add an additional route to your reverse proxy configuration.
index 541c4f325bb6302824aed8f181ead7f89e9d3838..8dc5b71181abd7ac67715fb8d7a1db043a1b6daa 100644 (file)
@@ -32,7 +32,7 @@ func IsRelativeURL(s string) bool {
        return err == nil && urlIsRelative(s, u)
 }
 
-func guessRequestScheme(req *http.Request, def string) string {
+func getRequestScheme(req *http.Request) string {
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
        if s := req.Header.Get("X-Forwarded-Proto"); s != "" {
                return s
@@ -49,10 +49,10 @@ func guessRequestScheme(req *http.Request, def string) string {
        if s := req.Header.Get("X-Forwarded-Ssl"); s != "" {
                return util.Iif(s == "on", "https", "http")
        }
-       return def
+       return ""
 }
 
-func guessForwardedHost(req *http.Request) string {
+func getForwardedHost(req *http.Request) string {
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
        return req.Header.Get("X-Forwarded-Host")
 }
@@ -63,15 +63,24 @@ func GuessCurrentAppURL(ctx context.Context) string {
        if !ok {
                return setting.AppURL
        }
-       if host := guessForwardedHost(req); host != "" {
-               // if it is behind a reverse proxy, use "https" as default scheme in case the site admin forgets to set the correct forwarded-protocol headers
-               return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/"
-       } else if req.Host != "" {
-               // if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible
-               defaultScheme := util.Iif(setting.Protocol == "http", "http", "https")
-               return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/"
+       // If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
+       // At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
+       // There are some cases:
+       // 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
+       // 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx.
+       // 3. There is no reverse proxy.
+       // Without an extra config option, Gitea is impossible to distinguish between case 2 and case 3,
+       // then case 2 would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users.
+       // So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
+       reqScheme := getRequestScheme(req)
+       if reqScheme == "" {
+               return setting.AppURL
+       }
+       reqHost := getForwardedHost(req)
+       if reqHost == "" {
+               reqHost = req.Host
        }
-       return setting.AppURL
+       return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
 }
 
 func MakeAbsoluteURL(ctx context.Context, s string) string {
index e021cd610d3f40859197ab5f3abad62a85fa45ee..9980cb74e8b29b76c5b3d11b41ca647741429df0 100644 (file)
@@ -41,19 +41,19 @@ func TestIsRelativeURL(t *testing.T) {
 
 func TestMakeAbsoluteURL(t *testing.T) {
        defer test.MockVariableValue(&setting.Protocol, "http")()
-       defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")()
+       defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
        defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
 
        ctx := context.Background()
-       assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, ""))
-       assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
-       assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+       assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
+       assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
+       assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
        assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
 
        ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
                Host: "user-host",
        })
-       assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+       assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
 
        ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
                Host: "user-host",
@@ -61,7 +61,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
                        "X-Forwarded-Host": {"forwarded-host"},
                },
        })
-       assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+       assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
 
        ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
                Host: "user-host",
index 1efd166eb3e650a34cfe79545c4f7f10db6f5a9d..2a6d44ba081249e6582b0156e00663b06ff87d5e 100644 (file)
@@ -116,6 +116,8 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
 }
 
 func apiUnauthorizedError(ctx *context.Context) {
+       // TODO: it doesn't seem quite right but it doesn't really cause problem at the moment.
+       // container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed, ideally.
        ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`)
        apiErrorDefined(ctx, errUnauthorized)
 }
index 782126adf5c38ce6c20082ac6f4e539697f692a0..6c38f0b509d594a028c4bb04e532cb860854a255 100644 (file)
@@ -87,6 +87,6 @@ func TestSelfCheckPost(t *testing.T) {
        err := json.Unmarshal(resp.Body.Bytes(), &data)
        assert.NoError(t, err)
        assert.Equal(t, []string{
-               ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://host/sub/"),
+               ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://config/sub/"),
        }, data.Problems)
 }