mirror of
https://github.com/gogs/gogs.git
synced 2026-05-28 21:30:36 +00:00
auth: trust reverse proxy auth header only from configured proxies (#8264)
This commit is contained in:
@@ -7,6 +7,7 @@ All notable changes to Gogs are documented in this file.
|
||||
### Fixed
|
||||
|
||||
- _Security:_ Denial of service in repository and wiki file listing pages via crafted file names. [#8116](https://github.com/gogs/gogs/pull/8116) - [GHSA-3qq3-668m-v9mj](https://github.com/gogs/gogs/security/advisories/GHSA-3qq3-668m-v9mj)
|
||||
- _Security:_ Reverse proxy authentication header was honored from any remote address, allowing user impersonation when Gogs was reachable directly. The header is now only trusted from addresses listed in `[auth] TRUSTED_PROXY_IPS`. [#8264](https://github.com/gogs/gogs/pull/8264) - [GHSA-w6j9-vw59-27wv](https://github.com/gogs/gogs/security/advisories/GHSA-w6j9-vw59-27wv)
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -233,6 +233,9 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||
; The HTTP header used as username for reverse proxy authentication.
|
||||
REVERSE_PROXY_AUTHENTICATION_HEADER = X-WEBAUTH-USER
|
||||
; Lists the IPs or CIDR ranges whose requests are allowed to set the reverse
|
||||
; proxy authentication header.
|
||||
TRUSTED_PROXY_IPS = 127.0.0.0/8,::1/128
|
||||
|
||||
[user]
|
||||
; Whether to enable email notifications for users.
|
||||
|
||||
@@ -1284,6 +1284,7 @@ config.auth.enable_registration_captcha = Enable registration captcha
|
||||
config.auth.enable_reverse_proxy_authentication = Enable reverse proxy authentication
|
||||
config.auth.enable_reverse_proxy_auto_registration = Enable reverse proxy auto registration
|
||||
config.auth.reverse_proxy_authentication_header = Reverse proxy authentication header
|
||||
config.auth.trusted_proxy_ips = Trusted proxy IPs
|
||||
|
||||
config.user_config = User configuration
|
||||
config.user.enable_email_notify = Enable email notification
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -221,6 +222,26 @@ func Init(customConf string) error {
|
||||
if err = File.Section("auth").MapTo(&Auth); err != nil {
|
||||
return errors.Wrap(err, "mapping [auth] section")
|
||||
}
|
||||
// Reset before re-parsing so repeated Init calls (e.g. via the web installer)
|
||||
// do not carry over CIDRs from a previous configuration.
|
||||
Auth.TrustedProxyCIDRs = nil
|
||||
for _, raw := range Auth.TrustedProxyIPs {
|
||||
// Allow bare IPs as a convenience by promoting them to single-host CIDRs.
|
||||
if !strings.Contains(raw, "/") {
|
||||
if ip := net.ParseIP(raw); ip != nil {
|
||||
if ip.To4() != nil {
|
||||
raw += "/32"
|
||||
} else {
|
||||
raw += "/128"
|
||||
}
|
||||
}
|
||||
}
|
||||
_, cidr, err := net.ParseCIDR(raw)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parse trusted proxy CIDR %q", raw)
|
||||
}
|
||||
Auth.TrustedProxyCIDRs = append(Auth.TrustedProxyCIDRs, cidr)
|
||||
}
|
||||
|
||||
// *************************
|
||||
// ----- User settings -----
|
||||
|
||||
@@ -2,6 +2,7 @@ package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
@@ -252,7 +253,11 @@ type AuthOpts struct {
|
||||
EnableReverseProxyAuthentication bool
|
||||
EnableReverseProxyAutoRegistration bool
|
||||
ReverseProxyAuthenticationHeader string
|
||||
CustomLogoutURL string `ini:"CUSTOM_LOGOUT_URL"`
|
||||
TrustedProxyIPs []string `ini:"TRUSTED_PROXY_IPS"`
|
||||
CustomLogoutURL string `ini:"CUSTOM_LOGOUT_URL"`
|
||||
|
||||
// Derived from other static values
|
||||
TrustedProxyCIDRs []*net.IPNet `ini:"-"` // Parsed CIDR form of TrustedProxyIPs.
|
||||
}
|
||||
|
||||
// Authentication settings
|
||||
|
||||
+1
@@ -106,6 +106,7 @@ ENABLE_REGISTRATION_CAPTCHA=true
|
||||
ENABLE_REVERSE_PROXY_AUTHENTICATION=false
|
||||
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=false
|
||||
REVERSE_PROXY_AUTHENTICATION_HEADER=X-FORWARDED-FOR
|
||||
TRUSTED_PROXY_IPS=127.0.0.0/8,::1/128
|
||||
CUSTOM_LOGOUT_URL=
|
||||
|
||||
[user]
|
||||
|
||||
@@ -2,6 +2,7 @@ package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -198,7 +199,7 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
|
||||
uid, isTokenAuth := authenticatedUserID(store, ctx, sess)
|
||||
|
||||
if uid <= 0 {
|
||||
if conf.Auth.EnableReverseProxyAuthentication {
|
||||
if conf.Auth.EnableReverseProxyAuthentication && isRequestFromTrustedProxy(ctx.Req.Request) {
|
||||
webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
|
||||
if len(webAuthUser) > 0 {
|
||||
user, err := store.GetUserByUsername(ctx.Req.Context(), webAuthUser)
|
||||
@@ -257,6 +258,32 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
|
||||
return u, false, isTokenAuth
|
||||
}
|
||||
|
||||
// isRequestFromTrustedProxy reports whether the request's immediate remote
|
||||
// address falls within one of the configured trusted proxy CIDR ranges. The
|
||||
// reverse proxy authentication header is only honored for such requests so an
|
||||
// attacker reaching Gogs directly cannot forge it.
|
||||
func isRequestFromTrustedProxy(req *http.Request) bool {
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
// Normalize IPv4-mapped IPv6 (e.g. "::ffff:127.0.0.1" on dual-stack listeners)
|
||||
// to its IPv4 form so it matches IPv4 CIDRs like 127.0.0.0/8.
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
ip = v4
|
||||
}
|
||||
for _, cidr := range conf.Auth.TrustedProxyCIDRs {
|
||||
if cidr.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthenticateByToken attempts to authenticate a user by the given access
|
||||
// token. It returns database.ErrAccessTokenNotExist when the access token does not
|
||||
// exist.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
)
|
||||
|
||||
func TestIsRequestFromTrustedProxy(t *testing.T) {
|
||||
mustCIDR := func(s string) *net.IPNet {
|
||||
_, n, err := net.ParseCIDR(s)
|
||||
require.NoError(t, err)
|
||||
return n
|
||||
}
|
||||
|
||||
original := conf.Auth.TrustedProxyCIDRs
|
||||
t.Cleanup(func() { conf.Auth.TrustedProxyCIDRs = original })
|
||||
conf.Auth.TrustedProxyCIDRs = []*net.IPNet{
|
||||
mustCIDR("127.0.0.0/8"),
|
||||
mustCIDR("::1/128"),
|
||||
mustCIDR("10.1.0.0/16"),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
remoteAddr string
|
||||
want bool
|
||||
}{
|
||||
{name: "loopback IPv4 with port", remoteAddr: "127.0.0.1:54321", want: true},
|
||||
{name: "loopback IPv6 with port", remoteAddr: "[::1]:54321", want: true},
|
||||
{name: "within configured CIDR", remoteAddr: "10.1.2.3:8080", want: true},
|
||||
{name: "outside configured CIDR", remoteAddr: "203.0.113.5:443", want: false},
|
||||
{name: "IPv4-mapped IPv6 matches IPv4 CIDR", remoteAddr: "[::ffff:127.0.0.1]:54321", want: true},
|
||||
{name: "remote without port", remoteAddr: "127.0.0.1", want: false},
|
||||
{name: "unparseable remote", remoteAddr: "not-an-ip", want: false},
|
||||
{name: "empty remote", remoteAddr: "", want: false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := &http.Request{RemoteAddr: tc.remoteAddr}
|
||||
require.Equal(t, tc.want, isRequestFromTrustedProxy(req))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -306,6 +306,8 @@
|
||||
<dd><i class="fa fa{{if .Auth.EnableReverseProxyAutoRegistration}}-check{{end}}-square-o"></i></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.auth.reverse_proxy_authentication_header"}}</dt>
|
||||
<dd><code>{{.Auth.ReverseProxyAuthenticationHeader}}</code></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.auth.trusted_proxy_ips"}}</dt>
|
||||
<dd><code>{{Join .Auth.TrustedProxyIPs ", "}}</code></dd>
|
||||
<dt>{{.i18n.Tr "admin.config.auth_custom_logout_url"}}</dt>
|
||||
<dd>
|
||||
{{if .Auth.CustomLogoutURL}}
|
||||
|
||||
Reference in New Issue
Block a user