Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the raw url path when proxying upstream requests #997

Merged
merged 7 commits into from
Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [#1317](https://github.com/oauth2-proxy/oauth2-proxy/pull/1317) Fix incorrect `</form>` tag on the sing_in page when *not* using a custom template (@jord1e)
- [#1330](https://github.com/oauth2-proxy/oauth2-proxy/pull/1330) Allow specifying URL as input for custom sign in logo (@MaikuMori)
- [#1357](https://github.com/oauth2-proxy/oauth2-proxy/pull/1357) Fix unsafe access to session variable (@harzallah)
- [#997](https://github.com/oauth2-proxy/oauth2-proxy/pull/997) Allow passing the raw url path when proxying upstream requests - e.g. /%2F/ (@FStelzer)

# V7.1.3

Expand Down
14 changes: 8 additions & 6 deletions docs/docs/configuration/alpha_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ They may change between releases without notice.

| Field | Type | Description |
| ----- | ---- | ----------- |
| `upstreams` | _[Upstreams](#upstreams)_ | Upstreams is used to configure upstream servers.<br/>Once a user is authenticated, requests to the server will be proxied to<br/>these upstream servers based on the path mappings defined in this list. |
| `upstreamConfig` | _[UpstreamConfig](#upstreamconfig)_ | UpstreamConfig is used to configure upstream servers.<br/>Once a user is authenticated, requests to the server will be proxied to<br/>these upstream servers based on the path mappings defined in this list. |
| `injectRequestHeaders` | _[[]Header](#header)_ | InjectRequestHeaders is used to configure headers that should be added<br/>to requests to upstream servers.<br/>Headers may source values from either the authenticated user's session<br/>or from a static secret value. |
| `injectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added<br/>to responses from the proxy.<br/>This is typically used when using the proxy as an external authentication<br/>provider in conjunction with another proxy such as NGINX and its<br/>auth_request module.<br/>Headers may source values from either the authenticated user's session<br/>or from a static secret value. |
| `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.<br/>You may choose to run both HTTP and HTTPS servers simultaneously.<br/>This can be done by setting the BindAddress and the SecureBindAddress simultaneously.<br/>To use the secure server you must configure a TLS certificate and key. |
Expand Down Expand Up @@ -364,7 +364,7 @@ TLS contains the information for loading a TLS certifcate and key.

### Upstream

(**Appears on:** [Upstreams](#upstreams))
(**Appears on:** [UpstreamConfig](#upstreamconfig))

Upstream represents the configuration for an upstream server.
Requests will be proxied to this upstream if the path matches the request path.
Expand All @@ -382,11 +382,13 @@ Requests will be proxied to this upstream if the path matches the request path.
| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |

### Upstreams

#### ([[]Upstream](#upstream) alias)
### UpstreamConfig

(**Appears on:** [AlphaOptions](#alphaoptions))

Upstreams is a collection of definitions for upstream servers.
UpstreamConfig is a collection of definitions for upstream servers.

| Field | Type | Description |
| ----- | ---- | ----------- |
| `proxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for url's<br/>like: "/%2F/" which would otherwise be redirected to "/" |
| `upstreams` | _[[]Upstream](#upstream)_ | Upstreams represents the configuration for the upstream servers.<br/>Requests will be proxied to this upstream if the path matches the request path. |
28 changes: 17 additions & 11 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package main

import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"time"

"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
Expand All @@ -24,7 +26,9 @@ client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
`

const testAlphaConfig = `
upstreams:
upstreamConfig:
proxyrawpath: false
upstreams:
- id: /
path: /
uri: http://httpbin
Expand Down Expand Up @@ -100,14 +104,16 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts.Cookie.Secure = false
opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"

opts.UpstreamServers = options.Upstreams{
{
ID: "/",
Path: "/",
URI: "http://httpbin",
FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
PassHostHeader: boolPtr(true),
ProxyWebSockets: boolPtr(true),
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: "/",
Path: "/",
URI: "http://httpbin",
FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
PassHostHeader: boolPtr(true),
ProxyWebSockets: boolPtr(true),
},
},
}

Expand All @@ -130,7 +136,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)

opts.Providers = options.Providers{
{
options.Provider{
ID: "google=oauth2-proxy",
Type: "google",
ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
Expand Down Expand Up @@ -230,7 +236,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
configContent: testCoreConfig,
alphaConfigContent: testAlphaConfig + ":",
expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 49: did not find expected key"),
expectedErr: fmt.Errorf("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line %d: did not find expected key", strings.Count(testAlphaConfig, "\n")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix, thanks! :D

}),
Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
configContent: testCoreConfig + "unknown_field=\"something\"",
Expand Down
4 changes: 3 additions & 1 deletion oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
}

func (p *OAuthProxy) buildServeMux(proxyPrefix string) {
r := mux.NewRouter()
// Use the encoded path here so we can have the option to pass it on in the upstream mux.
// Otherwise something like /%2F/ would be redirected to / here already.
r := mux.NewRouter().UseEncodedPath()
FStelzer marked this conversation as resolved.
Show resolved Hide resolved
// Everything served by the router must go through the preAuthChain first.
r.Use(p.preAuthChain.Then)

Expand Down
107 changes: 66 additions & 41 deletions oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,13 @@ func TestBasicAuthPassword(t *testing.T) {

basicAuthPassword := "This is a secure password"
opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
ID: providerServer.URL,
Path: "/",
URI: providerServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: providerServer.URL,
Path: "/",
URI: providerServer.URL,
},
},
}

Expand Down Expand Up @@ -346,15 +348,17 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
}))

patt.opts = baseTestOptions()
patt.opts.UpstreamServers = options.Upstreams{
{
ID: patt.providerServer.URL,
Path: "/",
URI: patt.providerServer.URL,
patt.opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: patt.providerServer.URL,
Path: "/",
URI: patt.providerServer.URL,
},
},
}
if opts.ProxyUpstream.ID != "" {
patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
patt.opts.UpstreamServers.Upstreams = append(patt.opts.UpstreamServers.Upstreams, opts.ProxyUpstream)
}

patt.opts.Cookie.Secure = false
Expand Down Expand Up @@ -915,6 +919,15 @@ func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
}

func TestEncodedUrlsStayEncoded(t *testing.T) {
encodeTest, err := NewSignInPageTest(false)
if err != nil {
t.Fatal(err)
}
code, _ := encodeTest.GetEndpoint("/%2F/test1/%2F/test2")
assert.Equal(t, 403, code)
}

func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
if err != nil {
Expand Down Expand Up @@ -1260,11 +1273,13 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
t.Cleanup(upstreamServer.Close)

opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
},
},
}
opts.SkipAuthPreflight = true
Expand Down Expand Up @@ -1335,11 +1350,13 @@ func NewSignatureTest() (*SignatureTest, error) {
if err != nil {
return nil, err
}
opts.UpstreamServers = options.Upstreams{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
},
},
}

Expand Down Expand Up @@ -1771,11 +1788,13 @@ func Test_noCacheHeaders(t *testing.T) {
t.Cleanup(upstreamServer.Close)

opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
},
},
}
opts.SkipAuthRegex = []string{".*"}
Expand Down Expand Up @@ -2041,11 +2060,13 @@ func TestTrustedIPs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
ID: "static",
Path: "/",
Static: true,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: "static",
Path: "/",
Static: true,
},
},
}
opts.TrustedIPs = tt.trustedIPs
Expand Down Expand Up @@ -2234,11 +2255,13 @@ func TestAllowedRequest(t *testing.T) {
t.Cleanup(upstreamServer.Close)

opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
},
},
}
opts.SkipAuthRegex = []string{
Expand Down Expand Up @@ -2349,11 +2372,13 @@ func TestProxyAllowedGroups(t *testing.T) {

test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
opts.Providers[0].AllowedGroups = tt.allowedGroups
opts.UpstreamServers = options.Upstreams{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
opts.UpstreamServers = options.UpstreamConfig{
Upstreams: []options.Upstream{
{
ID: upstreamServer.URL,
Path: "/",
URI: upstreamServer.URL,
},
},
}
})
Expand Down
9 changes: 4 additions & 5 deletions pkg/apis/options/alpha_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ package options
// They may change between releases without notice.
// :::
type AlphaOptions struct {
// Upstreams is used to configure upstream servers.
// UpstreamConfig is used to configure upstream servers.
// Once a user is authenticated, requests to the server will be proxied to
// these upstream servers based on the path mappings defined in this list.
Upstreams Upstreams `json:"upstreams,omitempty"`
UpstreamConfig UpstreamConfig `json:"upstreamConfig,omitempty"`

// InjectRequestHeaders is used to configure headers that should be added
// to requests to upstream servers.
Expand Down Expand Up @@ -48,19 +48,18 @@ type AlphaOptions struct {
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.Upstreams
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers

}

// ExtractFrom populates the fields in the AlphaOptions with the values from
// the Options
func (a *AlphaOptions) ExtractFrom(opts *Options) {
a.Upstreams = opts.UpstreamServers
a.UpstreamConfig = opts.UpstreamServers
a.InjectRequestHeaders = opts.InjectRequestHeaders
a.InjectResponseHeaders = opts.InjectResponseHeaders
a.Server = opts.Server
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/options/legacy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ func legacyUpstreamsFlagSet() *pflag.FlagSet {
return flagSet
}

func (l *LegacyUpstreams) convert() (Upstreams, error) {
upstreams := Upstreams{}
func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
upstreams := UpstreamConfig{}

for _, upstreamString := range l.Upstreams {
u, err := url.Parse(upstreamString)
if err != nil {
return nil, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
return UpstreamConfig{}, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
}

if u.Path == "" {
Expand Down Expand Up @@ -169,7 +169,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
upstream.FlushInterval = nil
}

upstreams = append(upstreams, upstream)
upstreams.Upstreams = append(upstreams.Upstreams, upstream)
}

return upstreams, nil
Expand Down
Loading