mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-09-30 11:41:19 +02:00
@@ -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)
|
||||
|
||||
+14
-13
@@ -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",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user