Merge commit from fork
Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
parent
43596a7bab
commit
aff369dfa3
|
|
@ -12,6 +12,7 @@
|
|||
- [#3333](https://github.com/oauth2-proxy/oauth2-proxy/pull/3333) fix: invalidate session on fatal OAuth2 refresh errors (@frhack)
|
||||
- [GHSA-f24x-5g9q-753f](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-f24x-5g9q-753f) fix: clear session cookie at beginning of signinpage handler (@fnoehWM / @bella-WI / @tuunit)
|
||||
- [GHSA-5hvv-m4w4-gf6v](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-5hvv-m4w4-gf6v) fix: health check user-agent authentication bypass (@tuunit)
|
||||
- [GHSA-7x63-xv5r-3p2x](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-7x63-xv5r-3p2x) fix: authentication bypass via X-Forwarded-Uri header spoofing (@tuunit)
|
||||
|
||||
# V7.15.1
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ version: "3.0"
|
|||
services:
|
||||
oauth2-proxy:
|
||||
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
|
||||
ports: []
|
||||
ports:
|
||||
- 4180:4180/tcp
|
||||
hostname: oauth2-proxy
|
||||
container_name: oauth2-proxy
|
||||
command: --config /oauth2-proxy.cfg
|
||||
|
|
@ -44,7 +45,7 @@ services:
|
|||
image: nginx:1.29
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80/tcp
|
||||
- 8080:8080/tcp
|
||||
hostname: nginx
|
||||
volumes:
|
||||
- "./nginx.conf:/etc/nginx/conf.d/default.conf"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
# Reverse proxy to oauth2-proxy
|
||||
server {
|
||||
listen 80;
|
||||
server_name oauth2-proxy.oauth2-proxy.localhost;
|
||||
listen 8080;
|
||||
server_name oauth2-proxy.localtest.me;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
|
||||
proxy_pass http://oauth2-proxy:4180/;
|
||||
}
|
||||
|
|
@ -13,8 +14,8 @@ server {
|
|||
|
||||
# Reverse proxy to httpbin
|
||||
server {
|
||||
listen 80;
|
||||
server_name httpbin.oauth2-proxy.localhost;
|
||||
listen 8080;
|
||||
server_name httpbin.localtest.me;
|
||||
|
||||
auth_request /internal-auth/oauth2/auth;
|
||||
|
||||
|
|
@ -29,50 +30,7 @@ server {
|
|||
# Named location for OAuth2 sign-in redirect
|
||||
# Returns a proper 302 that works with --skip-provider-button
|
||||
location @oauth2_signin {
|
||||
return 302 http://oauth2-proxy.oauth2-proxy.localhost/oauth2/sign_in?rd=$scheme://$host$request_uri;
|
||||
}
|
||||
|
||||
# auth_request must be a URI so this allows an internal path to then proxy to
|
||||
# the real auth_request path.
|
||||
# The trailing /'s are required so that nginx strips the prefix before proxying.
|
||||
location /internal-auth/ {
|
||||
internal; # Ensure external users can't access this path
|
||||
|
||||
# Make sure the OAuth2 Proxy knows where the original request came from.
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
|
||||
proxy_pass http://oauth2-proxy:4180/;
|
||||
}
|
||||
}
|
||||
|
||||
# Statically serve the nginx welcome
|
||||
server {
|
||||
listen 80;
|
||||
server_name oauth2-proxy.localhost;
|
||||
|
||||
location / {
|
||||
auth_request /internal-auth/oauth2/auth;
|
||||
|
||||
# On 401, redirect to the sign_in page via a named location
|
||||
# This ensures a proper 302 redirect that browsers will follow
|
||||
error_page 401 = @oauth2_signin;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# Named location for OAuth2 sign-in redirect
|
||||
# Returns a proper 302 that works with --skip-provider-button
|
||||
location @oauth2_signin {
|
||||
return 302 http://oauth2-proxy.oauth2-proxy.localhost/oauth2/sign_in?rd=$scheme://$host$request_uri;
|
||||
}
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
return 302 http://oauth2-proxy.localtest.me:8080/oauth2/sign_in?rd=$scheme://$host$request_uri;
|
||||
}
|
||||
|
||||
# auth_request must be a URI so this allows an internal path to then proxy to
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
http_address="0.0.0.0:4180"
|
||||
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||
provider="oidc"
|
||||
email_domains="example.com"
|
||||
oidc_issuer_url="http://dex.localtest.me:5556/dex"
|
||||
cookie_secure="false"
|
||||
upstreams="static://200"
|
||||
cookie_domains=[".localtest.me"] # Required so cookie can be read on all subdomains.
|
||||
whitelist_domains=[".localtest.me"] # Required to allow redirection back to original requested target.
|
||||
|
||||
# dex provider
|
||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||
client_id="oauth2-proxy"
|
||||
cookie_secure="false"
|
||||
redirect_url="http://oauth2-proxy.localtest.me:4180/oauth2/callback"
|
||||
|
||||
oidc_issuer_url="http://dex.localtest.me:5556/dex"
|
||||
provider="oidc"
|
||||
provider_display_name="Dex"
|
||||
|
||||
redirect_url="http://oauth2-proxy.oauth2-proxy.localhost/oauth2/callback"
|
||||
cookie_domains=".oauth2-proxy.localhost" # Required so cookie can be read on all subdomains.
|
||||
whitelist_domains=".oauth2-proxy.localhost" # Required to allow redirection back to original requested target.
|
||||
# Enables the use of `X-Forwarded-*` headers to determine request correctly
|
||||
reverse_proxy="true"
|
||||
|
|
|
|||
|
|
@ -193,6 +193,10 @@ Provider specific options can be found on their respective subpages.
|
|||
|
||||
### Proxy Options
|
||||
|
||||
:::warning
|
||||
When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or CIDR ranges of the reverse proxies that are allowed to send `X-Forwarded-*` headers. If you leave it unset, OAuth2 Proxy currently trusts all source IPs for backwards compatibility, which means a client that can reach OAuth2 Proxy directly may be able to spoof forwarded headers.
|
||||
:::
|
||||
|
||||
| Flag / Config Field | Type | Description | Default |
|
||||
| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
|
||||
|
|
@ -211,6 +215,7 @@ Provider specific options can be found on their respective subpages.
|
|||
| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
|
||||
| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
|
||||
| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
|
||||
| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
|
||||
| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
|
||||
| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
|
||||
| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
|
||||
|
|
|
|||
|
|
@ -193,6 +193,10 @@ Provider specific options can be found on their respective subpages.
|
|||
|
||||
### Proxy Options
|
||||
|
||||
:::warning
|
||||
When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or CIDR ranges of the reverse proxies that are allowed to send `X-Forwarded-*` headers. If you leave it unset, OAuth2 Proxy currently trusts all source IPs for backwards compatibility, which means a client that can reach OAuth2 Proxy directly may be able to spoof forwarded headers.
|
||||
:::
|
||||
|
||||
| Flag / Config Field | Type | Description | Default |
|
||||
| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
|
||||
|
|
@ -211,6 +215,7 @@ Provider specific options can be found on their respective subpages.
|
|||
| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
|
||||
| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
|
||||
| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
|
||||
| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
|
||||
| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
|
||||
| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
|
||||
| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
defaultTrustedProxyIPs = []string{"0.0.0.0/0", "::/0"}
|
||||
|
||||
// ErrNeedsLogin means the user should be redirected to the login page
|
||||
ErrNeedsLogin = errors.New("redirect to login page")
|
||||
|
||||
|
|
@ -183,13 +185,14 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
|
||||
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Secure, opts.Cookie.HTTPOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh)
|
||||
|
||||
trustedIPs := ip.NewNetSet()
|
||||
for _, ipStr := range opts.TrustedIPs {
|
||||
if ipNet := ip.ParseIPNet(ipStr); ipNet != nil {
|
||||
trustedIPs.AddIPNet(*ipNet)
|
||||
} else {
|
||||
return nil, fmt.Errorf("could not parse IP network (%s)", ipStr)
|
||||
}
|
||||
trustedIPs, err := ip.ParseNetSet(opts.TrustedIPs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trustedProxies, err := buildTrustedProxyNetSet(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowedRoutes, err := buildRoutesAllowlist(opts)
|
||||
|
|
@ -202,7 +205,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
return nil, err
|
||||
}
|
||||
|
||||
preAuthChain, err := buildPreAuthChain(opts, sessionStore)
|
||||
preAuthChain, err := buildPreAuthChain(opts, sessionStore, trustedProxies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
|
||||
}
|
||||
|
|
@ -355,8 +358,8 @@ func (p *OAuthProxy) buildProxySubrouter(s *mux.Router) {
|
|||
// buildPreAuthChain constructs a chain that should process every request before
|
||||
// the OAuth2 Proxy authentication logic kicks in.
|
||||
// For example forcing HTTPS or health checks.
|
||||
func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionStore) (alice.Chain, error) {
|
||||
chain := alice.New(middleware.NewScope(opts.ReverseProxy, opts.Logging.RequestIDHeader))
|
||||
func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionStore, trustedProxies *ip.NetSet) (alice.Chain, error) {
|
||||
chain := alice.New(middleware.NewScope(opts.ReverseProxy, opts.Logging.RequestIDHeader, trustedProxies))
|
||||
|
||||
if opts.ForceHTTPS {
|
||||
_, httpsPort, err := net.SplitHostPort(opts.Server.SecureBindAddress)
|
||||
|
|
@ -395,6 +398,16 @@ func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionSt
|
|||
return chain, nil
|
||||
}
|
||||
|
||||
func buildTrustedProxyNetSet(opts *options.Options) (*ip.NetSet, error) {
|
||||
trustedProxyIPs := opts.TrustedProxyIPs
|
||||
if opts.ReverseProxy && len(trustedProxyIPs) == 0 {
|
||||
logger.Print("WARNING: --reverse-proxy is enabled but no --trusted-proxy-ip CIDRs were configured. All connecting IPs are trusted to supply X-Forwarded-* headers by default (0.0.0.0/0, ::/0). This preserves backwards compatibility but is a potential security risk; configure --trusted-proxy-ip to match your reverse proxy addresses.")
|
||||
trustedProxyIPs = defaultTrustedProxyIPs
|
||||
}
|
||||
|
||||
return ip.ParseNetSet(trustedProxyIPs)
|
||||
}
|
||||
|
||||
func buildSessionChain(opts *options.Options, provider providers.Provider, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
|
|
|
|||
|
|
@ -2843,6 +2843,7 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
req, err := http.NewRequest(tc.method, opts.ProxyPrefix+authOnlyPath, nil)
|
||||
req.Header.Set("X-Forwarded-Uri", tc.url)
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
assert.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
|
@ -2857,6 +2858,43 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllowedRequestWithForwardedUriHeaderRequiresTrustedProxy(t *testing.T) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
opts := baseTestOptions()
|
||||
opts.ReverseProxy = true
|
||||
opts.TrustedProxyIPs = []string{"127.0.0.1/32"}
|
||||
opts.UpstreamServers = options.UpstreamConfig{
|
||||
Upstreams: []options.Upstream{
|
||||
{
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
},
|
||||
}
|
||||
opts.SkipAuthRegex = []string{"^/skip/auth/regex$"}
|
||||
|
||||
err := validation.Validate(opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, opts.ProxyPrefix+authOnlyPath, nil)
|
||||
assert.NoError(t, err)
|
||||
req.RemoteAddr = "192.0.2.10:4180"
|
||||
req.Header.Set("X-Forwarded-Uri", "/skip/auth/regex")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
proxy.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, 401, rw.Code)
|
||||
}
|
||||
|
||||
func TestAllowedRequestNegateWithoutMethod(t *testing.T) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package middleware
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
)
|
||||
|
||||
type scopeKey string
|
||||
|
|
@ -18,9 +21,13 @@ const RequestScopeKey scopeKey = "request-scope"
|
|||
// within the chain.
|
||||
type RequestScope struct {
|
||||
// ReverseProxy tracks whether OAuth2-Proxy is operating in reverse proxy
|
||||
// mode and if request `X-Forwarded-*` headers should be trusted
|
||||
// mode and if request `X-Forwarded-*` headers may be trusted
|
||||
ReverseProxy bool
|
||||
|
||||
// TrustedProxies tracks which direct callers are allowed to supply
|
||||
// forwarded headers when ReverseProxy mode is enabled.
|
||||
TrustedProxies *ip.NetSet
|
||||
|
||||
// RequestID is set to the request's `X-Request-Id` header if set.
|
||||
// Otherwise a random UUID is set.
|
||||
RequestID string
|
||||
|
|
@ -58,3 +65,43 @@ func AddRequestScope(req *http.Request, scope *RequestScope) *http.Request {
|
|||
ctx := context.WithValue(req.Context(), RequestScopeKey, scope)
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
// CanTrustForwardedHeaders returns whether forwarded headers should be
|
||||
// processed for this request.
|
||||
func (s *RequestScope) CanTrustForwardedHeaders(req *http.Request) bool {
|
||||
if s == nil || req == nil || !s.ReverseProxy || s.TrustedProxies == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if isUnixSocketRemoteAddr(req.RemoteAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
remoteIP := parseRemoteAddrIP(req.RemoteAddr)
|
||||
if remoteIP == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.TrustedProxies.Has(remoteIP)
|
||||
}
|
||||
|
||||
func parseRemoteAddrIP(remoteAddr string) net.IP {
|
||||
if remoteAddr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(remoteAddr); ip != nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return net.ParseIP(host)
|
||||
}
|
||||
|
||||
func isUnixSocketRemoteAddr(remoteAddr string) bool {
|
||||
return remoteAddr == "@" || strings.HasPrefix(remoteAddr, "/")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -53,4 +54,37 @@ var _ = Describe("Scope Suite", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("CanTrustForwardedHeaders", func() {
|
||||
var request *http.Request
|
||||
var scope *middleware.RequestScope
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
request, err = http.NewRequest("", "http://127.0.0.1/", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
scope = &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
}
|
||||
})
|
||||
|
||||
It("returns true for a trusted remote address", func() {
|
||||
request.RemoteAddr = "127.0.0.1:4180"
|
||||
Expect(scope.CanTrustForwardedHeaders(request)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("returns false for an untrusted remote address", func() {
|
||||
request.RemoteAddr = "192.0.2.10:4180"
|
||||
Expect(scope.CanTrustForwardedHeaders(request)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("returns true for unix socket callers", func() {
|
||||
request.RemoteAddr = "@"
|
||||
Expect(scope.CanTrustForwardedHeaders(request)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type Options struct {
|
|||
ReadyPath string `flag:"ready-path" cfg:"ready_path"`
|
||||
ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
|
||||
RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
|
||||
TrustedProxyIPs []string `flag:"trusted-proxy-ip" cfg:"trusted_proxy_ips"`
|
||||
TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
|
||||
ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
|
||||
RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
|
||||
|
|
@ -119,6 +120,7 @@ func NewFlagSet() *pflag.FlagSet {
|
|||
|
||||
flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted")
|
||||
flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, X-ProxyUser-IP, X-Envoy-External-Address, or CF-Connecting-IP)")
|
||||
flagSet.StringSlice("trusted-proxy-ip", []string{}, "list of IPs or CIDR ranges that are allowed to set X-Forwarded-* headers when --reverse-proxy is enabled. Defaults to trusting all IPs for backwards compatibility; configure this to your reverse proxy addresses to prevent header spoofing.")
|
||||
flagSet.StringSlice("trusted-ip", []string{}, "list of IPs or CIDR ranges to allow to bypass authentication. WARNING: trusting by IP has inherent security flaws, read the configuration documentation for more information.")
|
||||
flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
|
||||
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -33,9 +34,16 @@ var _ = Describe("Director Suite", func() {
|
|||
req.Header.Add(header, value)
|
||||
}
|
||||
}
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
scope := &middleware.RequestScope{
|
||||
ReverseProxy: in.reverseProxy,
|
||||
})
|
||||
}
|
||||
if in.reverseProxy {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
scope.TrustedProxies = trustedProxies
|
||||
}
|
||||
req = middleware.AddRequestScope(req, scope)
|
||||
|
||||
redirect, err := appDirector.GetRedirect(req)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
|
@ -174,4 +182,27 @@ var _ = Describe("Director Suite", func() {
|
|||
expectedRedirect: "https://a-service.example.com/foo/bar",
|
||||
}),
|
||||
)
|
||||
|
||||
It("ignores forwarded headers from an untrusted remote address", func() {
|
||||
appDirector := NewAppDirector(AppDirectorOpts{
|
||||
ProxyPrefix: testProxyPrefix,
|
||||
Validator: testValidator(true),
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://oauth.example.com/foo?bar", nil)
|
||||
req.RemoteAddr = "192.0.2.10:4180"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
req.Header.Add("X-Forwarded-Host", "a-service.example.com")
|
||||
req.Header.Add("X-Forwarded-Uri", fooBar)
|
||||
trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
|
||||
redirect, err := appDirector.GetRedirect(req)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(redirect).To(Equal("/foo?bar"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -30,8 +31,13 @@ var _ = Describe("Cookie Tests", func() {
|
|||
|
||||
if in.xForwardedHost != "" {
|
||||
req.Header.Add("X-Forwarded-Host", in.xForwardedHost)
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
|
||||
ReverseProxy: true,
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -37,3 +38,18 @@ func ParseIPNet(s string) *net.IPNet {
|
|||
return ipNet
|
||||
}
|
||||
}
|
||||
|
||||
func ParseNetSet(ipStrs []string) (*NetSet, error) {
|
||||
netSet := NewNetSet()
|
||||
|
||||
for _, ipStr := range ipStrs {
|
||||
ipNet := ParseIPNet(ipStr)
|
||||
if ipNet == nil {
|
||||
return nil, fmt.Errorf("could not parse IP network (%s)", ipStr)
|
||||
}
|
||||
|
||||
netSet.AddIPNet(*ipNet)
|
||||
}
|
||||
|
||||
return netSet, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseIPNet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expectedIP net.IP
|
||||
expectedMask net.IPMask
|
||||
}{
|
||||
{
|
||||
name: "ipv4 address",
|
||||
input: "127.0.0.1",
|
||||
expectedIP: net.ParseIP("127.0.0.1"),
|
||||
expectedMask: net.CIDRMask(32, 32),
|
||||
},
|
||||
{
|
||||
name: "ipv6 address",
|
||||
input: "::1",
|
||||
expectedIP: net.ParseIP("::1"),
|
||||
expectedMask: net.CIDRMask(128, 128),
|
||||
},
|
||||
{
|
||||
name: "ipv4 cidr",
|
||||
input: "10.0.0.0/24",
|
||||
expectedIP: net.ParseIP("10.0.0.0"),
|
||||
expectedMask: net.CIDRMask(24, 32),
|
||||
},
|
||||
{
|
||||
name: "ipv6 cidr",
|
||||
input: "2001:db8::/64",
|
||||
expectedIP: net.ParseIP("2001:db8::"),
|
||||
expectedMask: net.CIDRMask(64, 128),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ipNet := ParseIPNet(test.input)
|
||||
|
||||
assert.NotNil(t, ipNet)
|
||||
if ipNet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, test.expectedIP.Equal(ipNet.IP))
|
||||
assert.Equal(t, test.expectedMask, ipNet.Mask)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIPNetRejectsInvalidNetworks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "invalid ip",
|
||||
input: "not-an-ip",
|
||||
},
|
||||
{
|
||||
name: "ipv4 cidr with host bits set",
|
||||
input: "10.0.0.1/24",
|
||||
},
|
||||
{
|
||||
name: "ipv6 cidr with host bits set",
|
||||
input: "2001:db8::1/64",
|
||||
},
|
||||
{
|
||||
name: "invalid cidr mask",
|
||||
input: "10.0.0.0/33",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Nil(t, ParseIPNet(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetSet(t *testing.T) {
|
||||
netSet, err := ParseNetSet([]string{
|
||||
"127.0.0.1",
|
||||
"10.0.0.0/24",
|
||||
"::1",
|
||||
"2001:db8::/64",
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, netSet)
|
||||
if netSet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, netSet.Has(net.ParseIP("127.0.0.1")))
|
||||
assert.True(t, netSet.Has(net.ParseIP("10.0.0.55")))
|
||||
assert.True(t, netSet.Has(net.ParseIP("::1")))
|
||||
assert.True(t, netSet.Has(net.ParseIP("2001:db8::abcd")))
|
||||
|
||||
assert.False(t, netSet.Has(net.ParseIP("127.0.0.2")))
|
||||
assert.False(t, netSet.Has(net.ParseIP("10.0.1.1")))
|
||||
assert.False(t, netSet.Has(net.ParseIP("::2")))
|
||||
assert.False(t, netSet.Has(net.ParseIP("2001:db9::1")))
|
||||
}
|
||||
|
||||
func TestParseNetSetReturnsErrorForInvalidNetwork(t *testing.T) {
|
||||
netSet, err := ParseNetSet([]string{"127.0.0.1", "10.0.0.1/24"})
|
||||
|
||||
assert.Nil(t, netSet)
|
||||
assert.EqualError(t, err, "could not parse IP network (10.0.0.1/24)")
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http/httptest"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -39,6 +40,10 @@ var _ = Describe("RedirectToHTTPS suite", func() {
|
|||
scope := &middlewareapi.RequestScope{
|
||||
ReverseProxy: in.reverseProxy,
|
||||
}
|
||||
if in.reverseProxy {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
scope.TrustedProxies = newRedirectTrustedProxySet("127.0.0.1")
|
||||
}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
|
@ -207,3 +212,13 @@ var _ = Describe("RedirectToHTTPS suite", func() {
|
|||
}),
|
||||
)
|
||||
})
|
||||
|
||||
func newRedirectTrustedProxySet(cidrs ...string) *ip.NetSet {
|
||||
set := ip.NewNetSet()
|
||||
for _, cidr := range cidrs {
|
||||
ipNet := ip.ParseIPNet(cidr)
|
||||
Expect(ipNet).ToNot(BeNil())
|
||||
set.AddIPNet(*ipNet)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,16 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/justinas/alice"
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
)
|
||||
|
||||
func NewScope(reverseProxy bool, idHeader string) alice.Constructor {
|
||||
func NewScope(reverseProxy bool, idHeader string, trustedProxies *ip.NetSet) alice.Constructor {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
scope := &middlewareapi.RequestScope{
|
||||
ReverseProxy: reverseProxy,
|
||||
RequestID: genRequestID(req, idHeader),
|
||||
ReverseProxy: reverseProxy,
|
||||
TrustedProxies: trustedProxies,
|
||||
RequestID: genRequestID(req, idHeader),
|
||||
}
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
next.ServeHTTP(rw, req)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -32,7 +33,7 @@ var _ = Describe("Scope Suite", func() {
|
|||
|
||||
Context("ReverseProxy is false", func() {
|
||||
BeforeEach(func() {
|
||||
handler := NewScope(false, testRequestHeader)(
|
||||
handler := NewScope(false, testRequestHeader, nil)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
|
|
@ -60,8 +61,15 @@ var _ = Describe("Scope Suite", func() {
|
|||
})
|
||||
|
||||
Context("ReverseProxy is true", func() {
|
||||
var trustedProxies *ip.NetSet
|
||||
|
||||
BeforeEach(func() {
|
||||
handler := NewScope(true, testRequestHeader)(
|
||||
var err error
|
||||
|
||||
trustedProxies, err = ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
handler := NewScope(true, testRequestHeader, trustedProxies)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
|
|
@ -74,12 +82,18 @@ var _ = Describe("Scope Suite", func() {
|
|||
Expect(scope).ToNot(BeNil())
|
||||
Expect(scope.ReverseProxy).To(BeTrue())
|
||||
})
|
||||
|
||||
It("stores the trusted proxies on the scope", func() {
|
||||
scope := middlewareapi.GetRequestScope(nextRequest)
|
||||
Expect(scope).ToNot(BeNil())
|
||||
Expect(scope.TrustedProxies).To(BeIdenticalTo(trustedProxies))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Request ID header is present", func() {
|
||||
BeforeEach(func() {
|
||||
request.Header.Add(testRequestHeader, testRequestID)
|
||||
handler := NewScope(false, testRequestHeader)(
|
||||
handler := NewScope(false, testRequestHeader, nil)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
|
|
@ -97,7 +111,7 @@ var _ = Describe("Scope Suite", func() {
|
|||
BeforeEach(func() {
|
||||
uuid.SetRand(mockRand{})
|
||||
|
||||
handler := NewScope(true, testRequestHeader)(
|
||||
handler := NewScope(true, testRequestHeader, nil)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextRequest = r
|
||||
w.WriteHeader(200)
|
||||
|
|
|
|||
|
|
@ -15,30 +15,30 @@ const (
|
|||
)
|
||||
|
||||
// GetRequestProto returns the request scheme or X-Forwarded-Proto if present
|
||||
// and the request is proxied.
|
||||
// and the request came from a trusted reverse proxy.
|
||||
func GetRequestProto(req *http.Request) string {
|
||||
proto := req.Header.Get(XForwardedProto)
|
||||
if !IsProxied(req) || proto == "" {
|
||||
if !CanTrustForwardedHeaders(req) || proto == "" {
|
||||
proto = req.URL.Scheme
|
||||
}
|
||||
return proto
|
||||
}
|
||||
|
||||
// GetRequestHost returns the request host header or X-Forwarded-Host if
|
||||
// present and the request is proxied.
|
||||
// present and the request came from a trusted reverse proxy.
|
||||
func GetRequestHost(req *http.Request) string {
|
||||
host := req.Header.Get(XForwardedHost)
|
||||
if !IsProxied(req) || host == "" {
|
||||
if !CanTrustForwardedHeaders(req) || host == "" {
|
||||
host = req.Host
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// GetRequestURI return the request URI or X-Forwarded-Uri if present and the
|
||||
// request is proxied.
|
||||
// request came from a trusted reverse proxy.
|
||||
func GetRequestURI(req *http.Request) string {
|
||||
uri := req.Header.Get(XForwardedURI)
|
||||
if !IsProxied(req) || uri == "" {
|
||||
if !CanTrustForwardedHeaders(req) || uri == "" {
|
||||
// Use RequestURI to preserve ?query
|
||||
uri = req.URL.RequestURI()
|
||||
}
|
||||
|
|
@ -46,8 +46,8 @@ func GetRequestURI(req *http.Request) string {
|
|||
}
|
||||
|
||||
// GetRequestPath returns the request URI or X-Forwarded-Uri if present and the
|
||||
// request is proxied but always strips the query parameters and only returns
|
||||
// the pure path
|
||||
// request came from a trusted reverse proxy but always strips the query
|
||||
// parameters and only returns the pure path.
|
||||
func GetRequestPath(req *http.Request) string {
|
||||
uri := GetRequestURI(req)
|
||||
|
||||
|
|
@ -64,17 +64,18 @@ func GetRequestPath(req *http.Request) string {
|
|||
return uri
|
||||
}
|
||||
|
||||
// IsProxied determines if a request was from a proxy based on the RequestScope
|
||||
// ReverseProxy tracker.
|
||||
func IsProxied(req *http.Request) bool {
|
||||
// CanTrustForwardedHeaders determines if forwarded headers should be processed
|
||||
// based on the RequestScope and the direct caller's address.
|
||||
func CanTrustForwardedHeaders(req *http.Request) bool {
|
||||
scope := middlewareapi.GetRequestScope(req)
|
||||
if scope == nil {
|
||||
return false
|
||||
}
|
||||
return scope.ReverseProxy
|
||||
|
||||
return scope.CanTrustForwardedHeaders(req)
|
||||
}
|
||||
|
||||
func IsForwardedRequest(req *http.Request) bool {
|
||||
return IsProxied(req) &&
|
||||
return CanTrustForwardedHeaders(req) &&
|
||||
req.Host != GetRequestHost(req)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http/httptest"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
|
@ -19,8 +20,13 @@ var _ = Describe("Util Suite", func() {
|
|||
uriNoQueryParams = "/test/endpoint"
|
||||
)
|
||||
var req *http.Request
|
||||
var trustedProxies *ip.NetSet
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
trustedProxies, err = ip.ParseNetSet([]string{"127.0.0.1"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
req = httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
fmt.Sprintf("%s://%s%s", proto, host, uriWithQueryParams),
|
||||
|
|
@ -29,7 +35,7 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
|
||||
Context("GetRequestHost", func() {
|
||||
Context("IsProxied is false", func() {
|
||||
Context("trusted forwarded headers are disabled", func() {
|
||||
BeforeEach(func() {
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{})
|
||||
})
|
||||
|
|
@ -44,10 +50,12 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("IsProxied is true", func() {
|
||||
Context("trusted forwarded headers are enabled", func() {
|
||||
BeforeEach(func() {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -63,7 +71,7 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
|
||||
Context("GetRequestProto", func() {
|
||||
Context("IsProxied is false", func() {
|
||||
Context("trusted forwarded headers are disabled", func() {
|
||||
BeforeEach(func() {
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{})
|
||||
})
|
||||
|
|
@ -78,10 +86,12 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("IsProxied is true", func() {
|
||||
Context("trusted forwarded headers are enabled", func() {
|
||||
BeforeEach(func() {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -97,7 +107,7 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
|
||||
Context("GetRequestURI", func() {
|
||||
Context("IsProxied is false", func() {
|
||||
Context("trusted forwarded headers are disabled", func() {
|
||||
BeforeEach(func() {
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{})
|
||||
})
|
||||
|
|
@ -112,10 +122,12 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("IsProxied is true", func() {
|
||||
Context("trusted forwarded headers are enabled", func() {
|
||||
BeforeEach(func() {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -131,7 +143,7 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
|
||||
Context("GetRequestPath", func() {
|
||||
Context("IsProxied is false", func() {
|
||||
Context("trusted forwarded headers are disabled", func() {
|
||||
BeforeEach(func() {
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{})
|
||||
})
|
||||
|
|
@ -146,10 +158,12 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("IsProxied is true", func() {
|
||||
Context("trusted forwarded headers are enabled", func() {
|
||||
BeforeEach(func() {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -163,4 +177,30 @@ var _ = Describe("Util Suite", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("CanTrustForwardedHeaders", func() {
|
||||
It("returns false when no scope is present", func() {
|
||||
Expect(util.CanTrustForwardedHeaders(req)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("returns true when the remote address is trusted", func() {
|
||||
req.RemoteAddr = "127.0.0.1:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
|
||||
Expect(util.CanTrustForwardedHeaders(req)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("returns false when the remote address is untrusted", func() {
|
||||
req.RemoteAddr = "192.0.2.10:4180"
|
||||
req = middleware.AddRequestScope(req, &middleware.RequestScope{
|
||||
ReverseProxy: true,
|
||||
TrustedProxies: trustedProxies,
|
||||
})
|
||||
|
||||
Expect(util.CanTrustForwardedHeaders(req)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
|||
|
||||
handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
|
||||
|
||||
proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
|
||||
proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id", nil)(handler))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
|
@ -549,7 +549,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
|
||||
noPassHostServer := httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
|
||||
noPassHostServer := httptest.NewServer(middleware.NewScope(false, "X-Request-Id", nil)(handler))
|
||||
defer noPassHostServer.Close()
|
||||
|
||||
origin := "http://example.localhost"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ func validateAllowlists(o *options.Options) []string {
|
|||
|
||||
msgs = append(msgs, validateAuthRoutes(o)...)
|
||||
msgs = append(msgs, validateAuthRegexes(o)...)
|
||||
msgs = append(msgs, validateTrustedProxyIPs(o)...)
|
||||
msgs = append(msgs, validateTrustedIPs(o)...)
|
||||
|
||||
if len(o.TrustedIPs) > 0 && o.ReverseProxy {
|
||||
|
|
@ -28,6 +29,17 @@ func validateAllowlists(o *options.Options) []string {
|
|||
return msgs
|
||||
}
|
||||
|
||||
// validateTrustedProxyIPs validates IP/CIDRs for trusted reverse proxies.
|
||||
func validateTrustedProxyIPs(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
for i, ipStr := range o.TrustedProxyIPs {
|
||||
if ip.ParseIPNet(ipStr) == nil {
|
||||
msgs = append(msgs, fmt.Sprintf("trusted_proxy_ips[%d] (%s) could not be recognized", i, ipStr))
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// validateAuthRoutes validates method=path routes passed with options.SkipAuthRoutes
|
||||
func validateAuthRoutes(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ var _ = Describe("Allowlist", func() {
|
|||
errStrings []string
|
||||
}
|
||||
|
||||
type validateTrustedProxyIPsTableInput struct {
|
||||
trustedProxyIPs []string
|
||||
errStrings []string
|
||||
}
|
||||
|
||||
DescribeTable("validateRoutes",
|
||||
func(r *validateRoutesTableInput) {
|
||||
opts := &options.Options{
|
||||
|
|
@ -121,4 +126,29 @@ var _ = Describe("Allowlist", func() {
|
|||
},
|
||||
}),
|
||||
)
|
||||
|
||||
DescribeTable("validateTrustedProxyIPs",
|
||||
func(t *validateTrustedProxyIPsTableInput) {
|
||||
opts := &options.Options{
|
||||
TrustedProxyIPs: t.trustedProxyIPs,
|
||||
}
|
||||
Expect(validateTrustedProxyIPs(opts)).To(ConsistOf(t.errStrings))
|
||||
},
|
||||
Entry("Valid trusted proxy IPs", &validateTrustedProxyIPsTableInput{
|
||||
trustedProxyIPs: []string{
|
||||
"127.0.0.1",
|
||||
"10.32.0.1/32",
|
||||
"::1",
|
||||
"2a12:105:ee7:9234:0:0:0:0/64",
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("Invalid trusted proxy IPs", &validateTrustedProxyIPsTableInput{
|
||||
trustedProxyIPs: []string{"[::1]", "alkwlkbn/32"},
|
||||
errStrings: []string{
|
||||
"trusted_proxy_ips[0] ([::1]) could not be recognized",
|
||||
"trusted_proxy_ips[1] (alkwlkbn/32) could not be recognized",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue