git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #997 from FStelzer/issue844
ee7c405Joel Speed
CHANGELOG.md+1
@@ -35,6 +35,7 @@
35- [#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)
35- [#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)
36- [#1330](https://github.com/oauth2-proxy/oauth2-proxy/pull/1330) Allow specifying URL as input for custom sign in logo (@MaikuMori)
36- [#1330](https://github.com/oauth2-proxy/oauth2-proxy/pull/1330) Allow specifying URL as input for custom sign in logo (@MaikuMori)
37- [#1357](https://github.com/oauth2-proxy/oauth2-proxy/pull/1357) Fix unsafe access to session variable (@harzallah)
37- [#1357](https://github.com/oauth2-proxy/oauth2-proxy/pull/1357) Fix unsafe access to session variable (@harzallah)
38+- [#997](https://github.com/oauth2-proxy/oauth2-proxy/pull/997) Allow passing the raw url path when proxying upstream requests - e.g. /%2F/ (@FStelzer)
38
39
39# V7.1.3
40# V7.1.3
40
41
docs/docs/configuration/alpha_config.md+8−6
@@ -124,7 +124,7 @@ They may change between releases without notice.
124
124
125| Field | Type | Description |
125| Field | Type | Description |
126| ----- | ---- | ----------- |
126| ----- | ---- | ----------- |
127−| `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. |
127+| `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. |
128| `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. |
128| `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. |
129| `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. |
129| `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. |
130| `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. |
130| `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. |
@@ -364,7 +364,7 @@ TLS contains the information for loading a TLS certifcate and key.
364
364
365### Upstream
365### Upstream
366
366
367−(**Appears on:** [Upstreams](#upstreams))
367+(**Appears on:** [UpstreamConfig](#upstreamconfig))
368
368
369Upstream represents the configuration for an upstream server.
369Upstream represents the configuration for an upstream server.
370Requests will be proxied to this upstream if the path matches the request path.
370Requests will be proxied to this upstream if the path matches the request path.
@@ -382,11 +382,13 @@ Requests will be proxied to this upstream if the path matches the request path.
382| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
382| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
383| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
383| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
384
384
385−### Upstreams
385+### UpstreamConfig
386−
387−#### ([[]Upstream](#upstream) alias)
388
386
389(**Appears on:** [AlphaOptions](#alphaoptions))
387(**Appears on:** [AlphaOptions](#alphaoptions))
390
388
391−Upstreams is a collection of definitions for upstream servers.
389+UpstreamConfig is a collection of definitions for upstream servers.
392
390
391+| Field | Type | Description |
392+| ----- | ---- | ----------- |
393+| `proxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for url's<br/>like: "/%2F/" which would otherwise be redirected to "/" |
394+| `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. |
main_test.go+17−11
@@ -2,8 +2,10 @@ package main
2
2
3import (
3import (
4 "errors"
4 "errors"
5+ "fmt"
5 "io/ioutil"
6 "io/ioutil"
6 "os"
7 "os"
8+ "strings"
7 "time"
9 "time"
8
10
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
@@ -24,7 +26,9 @@ client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
24`
26`
25
27
26 const testAlphaConfig = `
28 const testAlphaConfig = `
29+upstreamConfig:
30+ proxyrawpath: false
27−upstreams:
31+ upstreams:
28 - id: /
32 - id: /
29 path: /
33 path: /
30 uri: http://httpbin
34 uri: http://httpbin
@@ -100,14 +104,16 @@ redirect_url="http://localhost:4180/oauth2/callback"
100 opts.Cookie.Secure = false
104 opts.Cookie.Secure = false
101 opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
105 opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
102
106
103− opts.UpstreamServers = options.Upstreams{
107+ opts.UpstreamServers = options.UpstreamConfig{
108+ Upstreams: []options.Upstream{
104− {
109+ {
105− ID: "/",
110+ ID: "/",
106− Path: "/",
111+ Path: "/",
107− URI: "http://httpbin",
112+ URI: "http://httpbin",
108− FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
113+ FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
109− PassHostHeader: boolPtr(true),
114+ PassHostHeader: boolPtr(true),
110− ProxyWebSockets: boolPtr(true),
115+ ProxyWebSockets: boolPtr(true),
116+ },
111 },
117 },
112 }
118 }
113
119
@@ -130,7 +136,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
130 opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)
136 opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)
131
137
132 opts.Providers = options.Providers{
138 opts.Providers = options.Providers{
133− {
139+ options.Provider{
134 ID: "google=oauth2-proxy",
140 ID: "google=oauth2-proxy",
135 Type: "google",
141 Type: "google",
136 ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
142 ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
@@ -230,7 +236,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
230 configContent: testCoreConfig,
236 configContent: testCoreConfig,
231 alphaConfigContent: testAlphaConfig + ":",
237 alphaConfigContent: testAlphaConfig + ":",
232 expectedOptions: func() *options.Options { return nil },
238 expectedOptions: func() *options.Options { return nil },
233− expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 49: did not find expected key"),
239+ 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")),
234 }),
240 }),
235 Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
241 Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
236 configContent: testCoreConfig + "unknown_field=\"something\"",
242 configContent: testCoreConfig + "unknown_field=\"something\"",
oauthproxy.go+3−1
@@ -265,7 +265,9 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
265}
265}
266
266
267func (p *OAuthProxy) buildServeMux(proxyPrefix string) {
267func (p *OAuthProxy) buildServeMux(proxyPrefix string) {
268+ // Use the encoded path here so we can have the option to pass it on in the upstream mux.
269+ // Otherwise something like /%2F/ would be redirected to / here already.
268− r := mux.NewRouter()
270+ r := mux.NewRouter().UseEncodedPath()
269 // Everything served by the router must go through the preAuthChain first.
271 // Everything served by the router must go through the preAuthChain first.
270 r.Use(p.preAuthChain.Then)
272 r.Use(p.preAuthChain.Then)
271
273
oauthproxy_test.go+66−41
@@ -197,11 +197,13 @@ func TestBasicAuthPassword(t *testing.T) {
197
197
198 basicAuthPassword := "This is a secure password"
198 basicAuthPassword := "This is a secure password"
199 opts := baseTestOptions()
199 opts := baseTestOptions()
200− opts.UpstreamServers = options.Upstreams{
200+ opts.UpstreamServers = options.UpstreamConfig{
201+ Upstreams: []options.Upstream{
201− {
202+ {
202− ID: providerServer.URL,
203+ ID: providerServer.URL,
203− Path: "/",
204+ Path: "/",
204− URI: providerServer.URL,
205+ URI: providerServer.URL,
206+ },
205 },
207 },
206 }
208 }
207
209
@@ -346,15 +348,17 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
346 }))
348 }))
347
349
348 patt.opts = baseTestOptions()
350 patt.opts = baseTestOptions()
349− patt.opts.UpstreamServers = options.Upstreams{
351+ patt.opts.UpstreamServers = options.UpstreamConfig{
352+ Upstreams: []options.Upstream{
350− {
353+ {
351− ID: patt.providerServer.URL,
354+ ID: patt.providerServer.URL,
352− Path: "/",
355+ Path: "/",
353− URI: patt.providerServer.URL,
356+ URI: patt.providerServer.URL,
357+ },
354 },
358 },
355 }
359 }
356 if opts.ProxyUpstream.ID != "" {
360 if opts.ProxyUpstream.ID != "" {
357− patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
361+ patt.opts.UpstreamServers.Upstreams = append(patt.opts.UpstreamServers.Upstreams, opts.ProxyUpstream)
358 }
362 }
359
363
360 patt.opts.Cookie.Secure = false
364 patt.opts.Cookie.Secure = false
@@ -915,6 +919,15 @@ func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
915 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
919 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
916}
920}
917
921
922+func TestEncodedUrlsStayEncoded(t *testing.T) {
923+ encodeTest, err := NewSignInPageTest(false)
924+ if err != nil {
925+ t.Fatal(err)
926+ }
927+ code, _ := encodeTest.GetEndpoint("/%2F/test1/%2F/test2")
928+ assert.Equal(t, 403, code)
929+}
930+
918func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
931func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
919 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
932 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
920 if err != nil {
933 if err != nil {
@@ -1260,11 +1273,13 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
1260 t.Cleanup(upstreamServer.Close)
1273 t.Cleanup(upstreamServer.Close)
1261
1274
1262 opts := baseTestOptions()
1275 opts := baseTestOptions()
1263− opts.UpstreamServers = options.Upstreams{
1276+ opts.UpstreamServers = options.UpstreamConfig{
1277+ Upstreams: []options.Upstream{
1264− {
1278+ {
1265− ID: upstreamServer.URL,
1279+ ID: upstreamServer.URL,
1266− Path: "/",
1280+ Path: "/",
1267− URI: upstreamServer.URL,
1281+ URI: upstreamServer.URL,
1282+ },
1268 },
1283 },
1269 }
1284 }
1270 opts.SkipAuthPreflight = true
1285 opts.SkipAuthPreflight = true
@@ -1335,11 +1350,13 @@ func NewSignatureTest() (*SignatureTest, error) {
1335 if err != nil {
1350 if err != nil {
1336 return nil, err
1351 return nil, err
1337 }
1352 }
1338− opts.UpstreamServers = options.Upstreams{
1353+ opts.UpstreamServers = options.UpstreamConfig{
1354+ Upstreams: []options.Upstream{
1339− {
1355+ {
1340− ID: upstreamServer.URL,
1356+ ID: upstreamServer.URL,
1341− Path: "/",
1357+ Path: "/",
1342− URI: upstreamServer.URL,
1358+ URI: upstreamServer.URL,
1359+ },
1343 },
1360 },
1344 }
1361 }
1345
1362
@@ -1771,11 +1788,13 @@ func Test_noCacheHeaders(t *testing.T) {
1771 t.Cleanup(upstreamServer.Close)
1788 t.Cleanup(upstreamServer.Close)
1772
1789
1773 opts := baseTestOptions()
1790 opts := baseTestOptions()
1774− opts.UpstreamServers = options.Upstreams{
1791+ opts.UpstreamServers = options.UpstreamConfig{
1792+ Upstreams: []options.Upstream{
1775− {
1793+ {
1776− ID: upstreamServer.URL,
1794+ ID: upstreamServer.URL,
1777− Path: "/",
1795+ Path: "/",
1778− URI: upstreamServer.URL,
1796+ URI: upstreamServer.URL,
1797+ },
1779 },
1798 },
1780 }
1799 }
1781 opts.SkipAuthRegex = []string{".*"}
1800 opts.SkipAuthRegex = []string{".*"}
@@ -2041,11 +2060,13 @@ func TestTrustedIPs(t *testing.T) {
2041 for _, tt := range tests {
2060 for _, tt := range tests {
2042 t.Run(tt.name, func(t *testing.T) {
2061 t.Run(tt.name, func(t *testing.T) {
2043 opts := baseTestOptions()
2062 opts := baseTestOptions()
2044− opts.UpstreamServers = options.Upstreams{
2063+ opts.UpstreamServers = options.UpstreamConfig{
2064+ Upstreams: []options.Upstream{
2045− {
2065+ {
2046− ID: "static",
2066+ ID: "static",
2047− Path: "/",
2067+ Path: "/",
2048− Static: true,
2068+ Static: true,
2069+ },
2049 },
2070 },
2050 }
2071 }
2051 opts.TrustedIPs = tt.trustedIPs
2072 opts.TrustedIPs = tt.trustedIPs
@@ -2234,11 +2255,13 @@ func TestAllowedRequest(t *testing.T) {
2234 t.Cleanup(upstreamServer.Close)
2255 t.Cleanup(upstreamServer.Close)
2235
2256
2236 opts := baseTestOptions()
2257 opts := baseTestOptions()
2237− opts.UpstreamServers = options.Upstreams{
2258+ opts.UpstreamServers = options.UpstreamConfig{
2259+ Upstreams: []options.Upstream{
2238− {
2260+ {
2239− ID: upstreamServer.URL,
2261+ ID: upstreamServer.URL,
2240− Path: "/",
2262+ Path: "/",
2241− URI: upstreamServer.URL,
2263+ URI: upstreamServer.URL,
2264+ },
2242 },
2265 },
2243 }
2266 }
2244 opts.SkipAuthRegex = []string{
2267 opts.SkipAuthRegex = []string{
@@ -2349,11 +2372,13 @@ func TestProxyAllowedGroups(t *testing.T) {
2349
2372
2350 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2373 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2351 opts.Providers[0].AllowedGroups = tt.allowedGroups
2374 opts.Providers[0].AllowedGroups = tt.allowedGroups
2352− opts.UpstreamServers = options.Upstreams{
2375+ opts.UpstreamServers = options.UpstreamConfig{
2376+ Upstreams: []options.Upstream{
2353− {
2377+ {
2354− ID: upstreamServer.URL,
2378+ ID: upstreamServer.URL,
2355− Path: "/",
2379+ Path: "/",
2356− URI: upstreamServer.URL,
2380+ URI: upstreamServer.URL,
2381+ },
2357 },
2382 },
2358 }
2383 }
2359 })
2384 })
pkg/apis/options/alpha_options.go+4−5
@@ -9,10 +9,10 @@ package options
9// They may change between releases without notice.
9// They may change between releases without notice.
10// :::
10// :::
11type AlphaOptions struct {
11type AlphaOptions struct {
12− // Upstreams is used to configure upstream servers.
12+ // UpstreamConfig is used to configure upstream servers.
13 // Once a user is authenticated, requests to the server will be proxied to
13 // Once a user is authenticated, requests to the server will be proxied to
14 // these upstream servers based on the path mappings defined in this list.
14 // these upstream servers based on the path mappings defined in this list.
15− Upstreams Upstreams `json:"upstreams,omitempty"`
15+ UpstreamConfig UpstreamConfig `json:"upstreamConfig,omitempty"`
16
16
17 // InjectRequestHeaders is used to configure headers that should be added
17 // InjectRequestHeaders is used to configure headers that should be added
18 // to requests to upstream servers.
18 // to requests to upstream servers.
@@ -48,19 +48,18 @@ type AlphaOptions struct {
48// MergeInto replaces alpha options in the Options struct with the values
48// MergeInto replaces alpha options in the Options struct with the values
49// from the AlphaOptions
49// from the AlphaOptions
50func (a *AlphaOptions) MergeInto(opts *Options) {
50func (a *AlphaOptions) MergeInto(opts *Options) {
51− opts.UpstreamServers = a.Upstreams
51+ opts.UpstreamServers = a.UpstreamConfig
52 opts.InjectRequestHeaders = a.InjectRequestHeaders
52 opts.InjectRequestHeaders = a.InjectRequestHeaders
53 opts.InjectResponseHeaders = a.InjectResponseHeaders
53 opts.InjectResponseHeaders = a.InjectResponseHeaders
54 opts.Server = a.Server
54 opts.Server = a.Server
55 opts.MetricsServer = a.MetricsServer
55 opts.MetricsServer = a.MetricsServer
56 opts.Providers = a.Providers
56 opts.Providers = a.Providers
57−
58}
57}
59
58
60// ExtractFrom populates the fields in the AlphaOptions with the values from
59// ExtractFrom populates the fields in the AlphaOptions with the values from
61// the Options
60// the Options
62func (a *AlphaOptions) ExtractFrom(opts *Options) {
61func (a *AlphaOptions) ExtractFrom(opts *Options) {
63− a.Upstreams = opts.UpstreamServers
62+ a.UpstreamConfig = opts.UpstreamServers
64 a.InjectRequestHeaders = opts.InjectRequestHeaders
63 a.InjectRequestHeaders = opts.InjectRequestHeaders
65 a.InjectResponseHeaders = opts.InjectResponseHeaders
64 a.InjectResponseHeaders = opts.InjectResponseHeaders
66 a.Server = opts.Server
65 a.Server = opts.Server
pkg/apis/options/legacy_options.go+4−4
@@ -114,13 +114,13 @@ func legacyUpstreamsFlagSet() *pflag.FlagSet {
114 return flagSet
114 return flagSet
115}
115}
116
116
117−func (l *LegacyUpstreams) convert() (Upstreams, error) {
117+func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
118− upstreams := Upstreams{}
118+ upstreams := UpstreamConfig{}
119
119
120 for _, upstreamString := range l.Upstreams {
120 for _, upstreamString := range l.Upstreams {
121 u, err := url.Parse(upstreamString)
121 u, err := url.Parse(upstreamString)
122 if err != nil {
122 if err != nil {
123− return nil, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
123+ return UpstreamConfig{}, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
124 }
124 }
125
125
126 if u.Path == "" {
126 if u.Path == "" {
@@ -169,7 +169,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
169 upstream.FlushInterval = nil
169 upstream.FlushInterval = nil
170 }
170 }
171
171
172− upstreams = append(upstreams, upstream)
172+ upstreams.Upstreams = append(upstreams.Upstreams, upstream)
173 }
173 }
174
174
175 return upstreams, nil
175 return upstreams, nil
pkg/apis/options/legacy_options_test.go+42−40
@@ -26,35 +26,37 @@ var _ = Describe("Legacy Options", func() {
26
26
27 truth := true
27 truth := true
28 staticCode := 204
28 staticCode := 204
29− opts.UpstreamServers = Upstreams{
29+ opts.UpstreamServers = UpstreamConfig{
30+ Upstreams: []Upstream{
30− {
31+ {
31− ID: "/baz",
32+ ID: "/baz",
32− Path: "/baz",
33+ Path: "/baz",
33− URI: "http://foo.bar/baz",
34+ URI: "http://foo.bar/baz",
34− FlushInterval: &flushInterval,
35+ FlushInterval: &flushInterval,
35− InsecureSkipTLSVerify: true,
36+ InsecureSkipTLSVerify: true,
36− PassHostHeader: &truth,
37+ PassHostHeader: &truth,
37− ProxyWebSockets: &truth,
38+ ProxyWebSockets: &truth,
38− },
39+ },
39− {
40+ {
40− ID: "/bar",
41+ ID: "/bar",
41− Path: "/bar",
42+ Path: "/bar",
42− URI: "file:///var/lib/website",
43+ URI: "file:///var/lib/website",
43− FlushInterval: &flushInterval,
44+ FlushInterval: &flushInterval,
44− InsecureSkipTLSVerify: true,
45+ InsecureSkipTLSVerify: true,
45− PassHostHeader: &truth,
46+ PassHostHeader: &truth,
46− ProxyWebSockets: &truth,
47+ ProxyWebSockets: &truth,
47− },
48+ },
48− {
49+ {
49− ID: "static://204",
50+ ID: "static://204",
50− Path: "/",
51+ Path: "/",
51− URI: "",
52+ URI: "",
52− Static: true,
53+ Static: true,
53− StaticCode: &staticCode,
54+ StaticCode: &staticCode,
54− FlushInterval: nil,
55+ FlushInterval: nil,
55− InsecureSkipTLSVerify: false,
56+ InsecureSkipTLSVerify: false,
56− PassHostHeader: nil,
57+ PassHostHeader: nil,
57− ProxyWebSockets: nil,
58+ ProxyWebSockets: nil,
59+ },
58 },
60 },
59 }
61 }
60
62
@@ -124,7 +126,7 @@ var _ = Describe("Legacy Options", func() {
124 Context("Legacy Upstreams", func() {
126 Context("Legacy Upstreams", func() {
125 type convertUpstreamsTableInput struct {
127 type convertUpstreamsTableInput struct {
126 upstreamStrings []string
128 upstreamStrings []string
127− expectedUpstreams Upstreams
129+ expectedUpstreams []Upstream
128 errMsg string
130 errMsg string
129 }
131 }
130
132
@@ -219,51 +221,51 @@ var _ = Describe("Legacy Options", func() {
219 Expect(err).ToNot(HaveOccurred())
221 Expect(err).ToNot(HaveOccurred())
220 }
222 }
221
223
222− Expect(upstreams).To(ConsistOf(in.expectedUpstreams))
224+ Expect(upstreams.Upstreams).To(ConsistOf(in.expectedUpstreams))
223 },
225 },
224 Entry("with no upstreams", &convertUpstreamsTableInput{
226 Entry("with no upstreams", &convertUpstreamsTableInput{
225 upstreamStrings: []string{},
227 upstreamStrings: []string{},
226− expectedUpstreams: Upstreams{},
228+ expectedUpstreams: []Upstream{},
227 errMsg: "",
229 errMsg: "",
228 }),
230 }),
229 Entry("with a valid HTTP upstream", &convertUpstreamsTableInput{
231 Entry("with a valid HTTP upstream", &convertUpstreamsTableInput{
230 upstreamStrings: []string{validHTTP},
232 upstreamStrings: []string{validHTTP},
231− expectedUpstreams: Upstreams{validHTTPUpstream},
233+ expectedUpstreams: []Upstream{validHTTPUpstream},
232 errMsg: "",
234 errMsg: "",
233 }),
235 }),
234 Entry("with a HTTP upstream with an empty path", &convertUpstreamsTableInput{
236 Entry("with a HTTP upstream with an empty path", &convertUpstreamsTableInput{
235 upstreamStrings: []string{emptyPathHTTP},
237 upstreamStrings: []string{emptyPathHTTP},
236− expectedUpstreams: Upstreams{emptyPathHTTPUpstream},
238+ expectedUpstreams: []Upstream{emptyPathHTTPUpstream},
237 errMsg: "",
239 errMsg: "",
238 }),
240 }),
239 Entry("with a valid File upstream with a fragment", &convertUpstreamsTableInput{
241 Entry("with a valid File upstream with a fragment", &convertUpstreamsTableInput{
240 upstreamStrings: []string{validFileWithFragment},
242 upstreamStrings: []string{validFileWithFragment},
241− expectedUpstreams: Upstreams{validFileWithFragmentUpstream},
243+ expectedUpstreams: []Upstream{validFileWithFragmentUpstream},
242 errMsg: "",
244 errMsg: "",
243 }),
245 }),
244 Entry("with a valid static upstream", &convertUpstreamsTableInput{
246 Entry("with a valid static upstream", &convertUpstreamsTableInput{
245 upstreamStrings: []string{validStatic},
247 upstreamStrings: []string{validStatic},
246− expectedUpstreams: Upstreams{validStaticUpstream},
248+ expectedUpstreams: []Upstream{validStaticUpstream},
247 errMsg: "",
249 errMsg: "",
248 }),
250 }),
249 Entry("with an invalid static upstream, code is 200", &convertUpstreamsTableInput{
251 Entry("with an invalid static upstream, code is 200", &convertUpstreamsTableInput{
250 upstreamStrings: []string{invalidStatic},
252 upstreamStrings: []string{invalidStatic},
251− expectedUpstreams: Upstreams{invalidStaticUpstream},
253+ expectedUpstreams: []Upstream{invalidStaticUpstream},
252 errMsg: "",
254 errMsg: "",
253 }),
255 }),
254 Entry("with an invalid HTTP upstream", &convertUpstreamsTableInput{
256 Entry("with an invalid HTTP upstream", &convertUpstreamsTableInput{
255 upstreamStrings: []string{invalidHTTP},
257 upstreamStrings: []string{invalidHTTP},
256− expectedUpstreams: Upstreams{},
258+ expectedUpstreams: []Upstream{},
257 errMsg: invalidHTTPErrMsg,
259 errMsg: invalidHTTPErrMsg,
258 }),
260 }),
259 Entry("with an invalid HTTP upstream and other upstreams", &convertUpstreamsTableInput{
261 Entry("with an invalid HTTP upstream and other upstreams", &convertUpstreamsTableInput{
260 upstreamStrings: []string{validHTTP, invalidHTTP},
262 upstreamStrings: []string{validHTTP, invalidHTTP},
261− expectedUpstreams: Upstreams{},
263+ expectedUpstreams: []Upstream{},
262 errMsg: invalidHTTPErrMsg,
264 errMsg: invalidHTTPErrMsg,
263 }),
265 }),
264 Entry("with multiple valid upstreams", &convertUpstreamsTableInput{
266 Entry("with multiple valid upstreams", &convertUpstreamsTableInput{
265 upstreamStrings: []string{validHTTP, validFileWithFragment, validStatic},
267 upstreamStrings: []string{validHTTP, validFileWithFragment, validStatic},
266− expectedUpstreams: Upstreams{validHTTPUpstream, validFileWithFragmentUpstream, validStaticUpstream},
268+ expectedUpstreams: []Upstream{validHTTPUpstream, validFileWithFragmentUpstream, validStaticUpstream},
267 errMsg: "",
269 errMsg: "",
268 }),
270 }),
269 )
271 )
pkg/apis/options/load_test.go+14−11
@@ -469,11 +469,12 @@ sub:
469
469
470 It("should load a full example AlphaOptions", func() {
470 It("should load a full example AlphaOptions", func() {
471 config := []byte(`
471 config := []byte(`
472+upstreamConfig:
472−upstreams:
473+ upstreams:
473−- id: httpbin
474+ - id: httpbin
474− path: /
475+ path: /
475− uri: http://httpbin
476+ uri: http://httpbin
476− flushInterval: 500ms
477+ flushInterval: 500ms
477injectRequestHeaders:
478injectRequestHeaders:
478- name: X-Forwarded-User
479- name: X-Forwarded-User
479 values:
480 values:
@@ -502,12 +503,14 @@ injectResponseHeaders:
502 flushInterval := Duration(500 * time.Millisecond)
503 flushInterval := Duration(500 * time.Millisecond)
503
504
504 Expect(into).To(Equal(&AlphaOptions{
505 Expect(into).To(Equal(&AlphaOptions{
506+ UpstreamConfig: UpstreamConfig{
505− Upstreams: []Upstream{
507+ Upstreams: []Upstream{
506− {
508+ {
507− ID: "httpbin",
509+ ID: "httpbin",
508− Path: "/",
510+ Path: "/",
509− URI: "http://httpbin",
511+ URI: "http://httpbin",
510− FlushInterval: &flushInterval,
512+ FlushInterval: &flushInterval,
513+ },
511 },
514 },
512 },
515 },
513 InjectRequestHeaders: []Header{
516 InjectRequestHeaders: []Header{
pkg/apis/options/options.go+1−1
@@ -41,7 +41,7 @@ type Options struct {
41
41
42 // Not used in the legacy config, name not allowed to match an external key (upstreams)
42 // Not used in the legacy config, name not allowed to match an external key (upstreams)
43 // TODO(JoelSpeed): Rename when legacy config is removed
43 // TODO(JoelSpeed): Rename when legacy config is removed
44− UpstreamServers Upstreams `cfg:",internal"`
44+ UpstreamServers UpstreamConfig `cfg:",internal"`
45
45
46 InjectRequestHeaders []Header `cfg:",internal"`
46 InjectRequestHeaders []Header `cfg:",internal"`
47 InjectResponseHeaders []Header `cfg:",internal"`
47 InjectResponseHeaders []Header `cfg:",internal"`
pkg/apis/options/upstreams.go+10−2
@@ -7,8 +7,16 @@ const (
7 DefaultUpstreamFlushInterval = 1 * time.Second
7 DefaultUpstreamFlushInterval = 1 * time.Second
8)
8)
9
9
10−// Upstreams is a collection of definitions for upstream servers.
10+// UpstreamConfig is a collection of definitions for upstream servers.
11+type UpstreamConfig struct {
12+ // ProxyRawPath will pass the raw url path to upstream allowing for url's
13+ // like: "/%2F/" which would otherwise be redirected to "/"
14+ ProxyRawPath bool `json:"proxyRawPath,omitempty"`
15+
16+ // Upstreams represents the configuration for the upstream servers.
17+ // Requests will be proxied to this upstream if the path matches the request path.
11−type Upstreams []Upstream
18+ Upstreams []Upstream `json:"upstreams,omitempty"`
19+}
12
20
13// Upstream represents the configuration for an upstream server.
21// Upstream represents the configuration for an upstream server.
14// Requests will be proxied to this upstream if the path matches the request path.
22// Requests will be proxied to this upstream if the path matches the request path.
pkg/upstream/proxy.go+7−3
@@ -22,12 +22,16 @@ type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
22
22
23// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
23// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
24// multiple upstreams.
24// multiple upstreams.
25−func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, writer pagewriter.Writer) (http.Handler, error) {
25+func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData, writer pagewriter.Writer) (http.Handler, error) {
26 m := &multiUpstreamProxy{
26 m := &multiUpstreamProxy{
27 serveMux: mux.NewRouter(),
27 serveMux: mux.NewRouter(),
28 }
28 }
29
29
30+ if upstreams.ProxyRawPath {
31+ m.serveMux.UseEncodedPath()
32+ }
33+
30− for _, upstream := range sortByPathLongest(upstreams) {
34+ for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
31 if upstream.Static {
35 if upstream.Static {
32 if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
36 if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
33 return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
37 return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
@@ -153,7 +157,7 @@ func registerTrailingSlashHandler(serveMux *mux.Router) {
153// precedence (note this is the input to the rewrite logic).
157// precedence (note this is the input to the rewrite logic).
154// This does not account for when a rewrite would actually make the path shorter.
158// This does not account for when a rewrite would actually make the path shorter.
155// This should maintain the sorting behaviour of the standard go serve mux.
159// This should maintain the sorting behaviour of the standard go serve mux.
156−func sortByPathLongest(in options.Upstreams) options.Upstreams {
160+func sortByPathLongest(in []options.Upstream) []options.Upstream {
157 sort.Slice(in, func(i, j int) bool {
161 sort.Slice(in, func(i, j int) bool {
158 iRW := in[i].RewriteTarget
162 iRW := in[i].RewriteTarget
159 jRW := in[j].RewriteTarget
163 jRW := in[j].RewriteTarget
pkg/upstream/proxy_test.go+137−110
@@ -16,94 +16,94 @@ import (
16)
16)
17
17
18var _ = Describe("Proxy Suite", func() {
18var _ = Describe("Proxy Suite", func() {
19− var upstreamServer http.Handler
19+ type proxyTableInput struct {
20+ target string
21+ response testHTTPResponse
22+ upstream string
23+ upstreams options.UpstreamConfig
24+ }
20
25
21 Context("multiUpstreamProxy", func() {
26 Context("multiUpstreamProxy", func() {
22− BeforeEach(func() {
27+ DescribeTable("Proxy ServeHTTP",
28+ func(in *proxyTableInput) {
23− sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
29+ sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
24−
25− writer := &pagewriter.WriterFuncs{
26− ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
27− rw.WriteHeader(502)
28− rw.Write([]byte("Proxy Error"))
29− },
30− }
31
30
32− ok := http.StatusOK
31+ writer := &pagewriter.WriterFuncs{
33− accepted := http.StatusAccepted
32+ ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
33+ rw.WriteHeader(502)
34+ rw.Write([]byte("Proxy Error"))
35+ },
36+ }
34
37
35− upstreams := options.Upstreams{
38+ ok := http.StatusOK
36− {
39+ accepted := http.StatusAccepted
37− ID: "http-backend",
38− Path: "/http/",
39− URI: serverAddr,
40− },
41− {
42− ID: "file-backend",
43− Path: "/files/",
44− URI: fmt.Sprintf("file:///%s", filesDir),
45− },
46− {
47− ID: "static-backend",
48− Path: "/static/",
49− Static: true,
50− StaticCode: &ok,
51− },
52− {
53− ID: "static-backend-no-trailing-slash",
54− Path: "/static",
55− Static: true,
56− StaticCode: &accepted,
57− },
58− {
59− ID: "static-backend-long",
60− Path: "/static/long",
61− Static: true,
62− StaticCode: &accepted,
63− },
64− {
65− ID: "bad-http-backend",
66− Path: "/bad-http/",
67− URI: "http://::1",
68− },
69− {
70− ID: "single-path-backend",
71− Path: "/single-path",
72− Static: true,
73− StaticCode: &ok,
74− },
75− {
76− ID: "backend-with-rewrite-prefix",
77− Path: "^/rewrite-prefix/(.*)",
78− RewriteTarget: "/different/backend/path/$1",
79− URI: serverAddr,
80− },
81− {
82− ID: "double-match-plain",
83− Path: "/double-match/",
84− URI: serverAddr,
85− },
86− {
87− ID: "double-match-rewrite",
88− Path: "^/double-match/(.*)",
89− RewriteTarget: "/double-match/rewrite/$1",
90− URI: serverAddr,
91− },
92− }
93
40
94− var err error
41+ // Allows for specifying settings and even individual upstreams for specific tests and uses the default upstreams/configs otherwise
95− upstreamServer, err = NewProxy(upstreams, sigData, writer)
42+ upstreams := in.upstreams
96− Expect(err).ToNot(HaveOccurred())
43+ if len(in.upstreams.Upstreams) == 0 {
44+ upstreams.Upstreams = []options.Upstream{
45+ {
46+ ID: "http-backend",
47+ Path: "/http/",
48+ URI: serverAddr,
49+ },
50+ {
51+ ID: "file-backend",
52+ Path: "/files/",
53+ URI: fmt.Sprintf("file:///%s", filesDir),
54+ },
55+ {
56+ ID: "static-backend",
57+ Path: "/static/",
58+ Static: true,
59+ StaticCode: &ok,
60+ },
61+ {
62+ ID: "static-backend-no-trailing-slash",
63+ Path: "/static",
64+ Static: true,
65+ StaticCode: &accepted,
66+ },
67+ {
68+ ID: "static-backend-long",
69+ Path: "/static/long",
70+ Static: true,
71+ StaticCode: &accepted,
72+ },
73+ {
74+ ID: "bad-http-backend",
75+ Path: "/bad-http/",
76+ URI: "http://::1",
77+ },
78+ {
79+ ID: "single-path-backend",
80+ Path: "/single-path",
81+ Static: true,
82+ StaticCode: &ok,
83+ },
84+ {
85+ ID: "backend-with-rewrite-prefix",
86+ Path: "^/rewrite-prefix/(.*)",
87+ RewriteTarget: "/different/backend/path/$1",
88+ URI: serverAddr,
89+ },
90+ {
91+ ID: "double-match-plain",
92+ Path: "/double-match/",
93+ URI: serverAddr,
97− })
94+ },
95+ {
96+ ID: "double-match-rewrite",
97+ Path: "^/double-match/(.*)",
98+ RewriteTarget: "/double-match/rewrite/$1",
99+ URI: serverAddr,
100+ },
101+ }
102+ }
98
103
99− type proxyTableInput struct {
104+ upstreamServer, err := NewProxy(upstreams, sigData, writer)
100− target string
105+ Expect(err).ToNot(HaveOccurred())
101− response testHTTPResponse
102− upstream string
103− }
104
106
105− DescribeTable("Proxy ServeHTTP",
106− func(in *proxyTableInput) {
107 req := middlewareapi.AddRequestScope(
107 req := middlewareapi.AddRequestScope(
108 httptest.NewRequest("", in.target, nil),
108 httptest.NewRequest("", in.target, nil),
109 &middlewareapi.RequestScope{},
109 &middlewareapi.RequestScope{},
@@ -131,10 +131,12 @@ var _ = Describe("Proxy Suite", func() {
131 }
131 }
132
132
133 // Compare the reflected request to the upstream
133 // Compare the reflected request to the upstream
134+ if body != nil {
134− request := testHTTPRequest{}
135+ request := testHTTPRequest{}
135− Expect(json.Unmarshal(body, &request)).To(Succeed())
136+ Expect(json.Unmarshal(body, &request)).To(Succeed())
136− testSanitizeRequestHeader(request.Header)
137+ testSanitizeRequestHeader(request.Header)
137− Expect(request).To(Equal(in.response.request))
138+ Expect(request).To(Equal(in.response.request))
139+ }
138 },
140 },
139 Entry("with a request to the HTTP service", &proxyTableInput{
141 Entry("with a request to the HTTP service", &proxyTableInput{
140 target: "http://example.localhost/http/1234",
142 target: "http://example.localhost/http/1234",
@@ -310,33 +312,58 @@ var _ = Describe("Proxy Suite", func() {
310 },
312 },
311 upstream: "double-match-rewrite",
313 upstream: "double-match-rewrite",
312 }),
314 }),
315+ Entry("containing an escaped '/' without ProxyRawPath", &proxyTableInput{
316+ target: "http://example.localhost/%2F/test1/%2F/test2",
317+ response: testHTTPResponse{
318+ code: 301,
319+ header: map[string][]string{
320+ "Location": {
321+ "http://example.localhost/test1/test2",
322+ },
323+ },
324+ },
325+ upstream: "",
326+ }),
327+ Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{
328+ upstreams: options.UpstreamConfig{ProxyRawPath: true},
329+ target: "http://example.localhost/%2F/test1/%2F/test2",
330+ response: testHTTPResponse{
331+ code: 404,
332+ header: map[string][]string{
333+ "X-Content-Type-Options": {"nosniff"},
334+ contentType: {textPlainUTF8},
335+ },
336+ raw: "404 page not found\n",
337+ },
338+ upstream: "",
339+ }),
313 )
340 )
314 })
341 })
315
342
316 Context("sortByPathLongest", func() {
343 Context("sortByPathLongest", func() {
317 type sortByPathLongestTableInput struct {
344 type sortByPathLongestTableInput struct {
318− input options.Upstreams
345+ input []options.Upstream
319− expectedOutput options.Upstreams
346+ expectedOutput []options.Upstream
320 }
347 }
321
348
322− var httpPath = options.Upstream{
349+ httpPath := options.Upstream{
323 Path: "/http/",
350 Path: "/http/",
324 }
351 }
325
352
326− var httpSubPath = options.Upstream{
353+ httpSubPath := options.Upstream{
327 Path: "/http/subpath/",
354 Path: "/http/subpath/",
328 }
355 }
329
356
330− var longerPath = options.Upstream{
357+ longerPath := options.Upstream{
331 Path: "/longer-than-http",
358 Path: "/longer-than-http",
332 }
359 }
333
360
334− var shortPathWithRewrite = options.Upstream{
361+ shortPathWithRewrite := options.Upstream{
335 Path: "^/h/(.*)",
362 Path: "^/h/(.*)",
336 RewriteTarget: "/$1",
363 RewriteTarget: "/$1",
337 }
364 }
338
365
339− var shortSubPathWithRewrite = options.Upstream{
366+ shortSubPathWithRewrite := options.Upstream{
340 Path: "^/h/bar/(.*)",
367 Path: "^/h/bar/(.*)",
341 RewriteTarget: "/$1",
368 RewriteTarget: "/$1",
342 }
369 }
@@ -346,40 +373,40 @@ var _ = Describe("Proxy Suite", func() {
346 Expect(sortByPathLongest(in.input)).To(Equal(in.expectedOutput))
373 Expect(sortByPathLongest(in.input)).To(Equal(in.expectedOutput))
347 },
374 },
348 Entry("with a mix of paths registered", sortByPathLongestTableInput{
375 Entry("with a mix of paths registered", sortByPathLongestTableInput{
349− input: options.Upstreams{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite},
376+ input: []options.Upstream{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite},
350− expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath},
377+ expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath},
351 }),
378 }),
352 Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{
379 Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{
353− input: options.Upstreams{httpSubPath, httpPath},
380+ input: []options.Upstream{httpSubPath, httpPath},
354− expectedOutput: options.Upstreams{httpSubPath, httpPath},
381+ expectedOutput: []options.Upstream{httpSubPath, httpPath},
355 }),
382 }),
356 Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{
383 Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{
357− input: options.Upstreams{httpPath, httpSubPath},
384+ input: []options.Upstream{httpPath, httpSubPath},
358− expectedOutput: options.Upstreams{httpSubPath, httpPath},
385+ expectedOutput: []options.Upstream{httpSubPath, httpPath},
359 }),
386 }),
360 Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{
387 Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{
361− input: options.Upstreams{longerPath, httpPath},
388+ input: []options.Upstream{longerPath, httpPath},
362− expectedOutput: options.Upstreams{longerPath, httpPath},
389+ expectedOutput: []options.Upstream{longerPath, httpPath},
363 }),
390 }),
364 Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{
391 Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{
365− input: options.Upstreams{httpPath, longerPath},
392+ input: []options.Upstream{httpPath, longerPath},
366− expectedOutput: options.Upstreams{longerPath, httpPath},
393+ expectedOutput: []options.Upstream{longerPath, httpPath},
367 }),
394 }),
368 Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{
395 Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{
369− input: options.Upstreams{shortPathWithRewrite, longerPath},
396+ input: []options.Upstream{shortPathWithRewrite, longerPath},
370− expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath},
397+ expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
371 }),
398 }),
372 Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{
399 Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{
373− input: options.Upstreams{longerPath, shortPathWithRewrite},
400+ input: []options.Upstream{longerPath, shortPathWithRewrite},
374− expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath},
401+ expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
375 }),
402 }),
376 Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{
403 Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{
377− input: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
404+ input: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
378− expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
405+ expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
379 }),
406 }),
380 Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{
407 Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{
381− input: options.Upstreams{shortPathWithRewrite, shortSubPathWithRewrite},
408+ input: []options.Upstream{shortPathWithRewrite, shortSubPathWithRewrite},
382− expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
409+ expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
383 }),
410 }),
384 )
411 )
385 })
412 })
pkg/validation/options_test.go+1−1
@@ -22,7 +22,7 @@ const (
22
22
23func testOptions() *options.Options {
23func testOptions() *options.Options {
24 o := options.NewOptions()
24 o := options.NewOptions()
25− o.UpstreamServers = append(o.UpstreamServers, options.Upstream{
25+ o.UpstreamServers.Upstreams = append(o.UpstreamServers.Upstreams, options.Upstream{
26 ID: "upstream",
26 ID: "upstream",
27 Path: "/",
27 Path: "/",
28 URI: "http://127.0.0.1:8080/",
28 URI: "http://127.0.0.1:8080/",
pkg/validation/upstreams.go+2−2
@@ -7,12 +7,12 @@ import (
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
8)
8)
9
9
10−func validateUpstreams(upstreams options.Upstreams) []string {
10+func validateUpstreams(upstreams options.UpstreamConfig) []string {
11 msgs := []string{}
11 msgs := []string{}
12 ids := make(map[string]struct{})
12 ids := make(map[string]struct{})
13 paths := make(map[string]struct{})
13 paths := make(map[string]struct{})
14
14
15− for _, upstream := range upstreams {
15+ for _, upstream := range upstreams.Upstreams {
16 msgs = append(msgs, validateUpstream(upstream, ids, paths)...)
16 msgs = append(msgs, validateUpstream(upstream, ids, paths)...)
17 }
17 }
18
18
pkg/validation/upstreams_test.go+93−71
@@ -11,7 +11,7 @@ import (
11
11
12var _ = Describe("Upstreams", func() {
12var _ = Describe("Upstreams", func() {
13 type validateUpstreamTableInput struct {
13 type validateUpstreamTableInput struct {
14− upstreams options.Upstreams
14+ upstreams options.UpstreamConfig
15 errStrings []string
15 errStrings []string
16 }
16 }
17
17
@@ -54,88 +54,104 @@ var _ = Describe("Upstreams", func() {
54 Expect(validateUpstreams(o.upstreams)).To(ConsistOf(o.errStrings))
54 Expect(validateUpstreams(o.upstreams)).To(ConsistOf(o.errStrings))
55 },
55 },
56 Entry("with no upstreams", &validateUpstreamTableInput{
56 Entry("with no upstreams", &validateUpstreamTableInput{
57− upstreams: options.Upstreams{},
57+ upstreams: options.UpstreamConfig{},
58 errStrings: []string{},
58 errStrings: []string{},
59 }),
59 }),
60 Entry("with valid upstreams", &validateUpstreamTableInput{
60 Entry("with valid upstreams", &validateUpstreamTableInput{
61− upstreams: options.Upstreams{
61+ upstreams: options.UpstreamConfig{
62+ Upstreams: []options.Upstream{
62− validHTTPUpstream,
63+ validHTTPUpstream,
63− validStaticUpstream,
64+ validStaticUpstream,
64− validFileUpstream,
65+ validFileUpstream,
66+ },
65 },
67 },
66 errStrings: []string{},
68 errStrings: []string{},
67 }),
69 }),
68 Entry("with an empty ID", &validateUpstreamTableInput{
70 Entry("with an empty ID", &validateUpstreamTableInput{
69− upstreams: options.Upstreams{
71+ upstreams: options.UpstreamConfig{
70− {
72+ Upstreams: []options.Upstream{
73+ {
71− ID: "",
74+ ID: "",
72− Path: "/foo",
75+ Path: "/foo",
73− URI: "http://localhost:8080",
76+ URI: "http://localhost:8080",
77+ },
74 },
78 },
75 },
79 },
76 errStrings: []string{emptyIDMsg},
80 errStrings: []string{emptyIDMsg},
77 }),
81 }),
78 Entry("with an empty Path", &validateUpstreamTableInput{
82 Entry("with an empty Path", &validateUpstreamTableInput{
79− upstreams: options.Upstreams{
83+ upstreams: options.UpstreamConfig{
80− {
84+ Upstreams: []options.Upstream{
85+ {
81− ID: "foo",
86+ ID: "foo",
82− Path: "",
87+ Path: "",
83− URI: "http://localhost:8080",
88+ URI: "http://localhost:8080",
89+ },
84 },
90 },
85 },
91 },
86 errStrings: []string{emptyPathMsg},
92 errStrings: []string{emptyPathMsg},
87 }),
93 }),
88 Entry("with an empty Path", &validateUpstreamTableInput{
94 Entry("with an empty Path", &validateUpstreamTableInput{
89− upstreams: options.Upstreams{
95+ upstreams: options.UpstreamConfig{
90− {
96+ Upstreams: []options.Upstream{
97+ {
91− ID: "foo",
98+ ID: "foo",
92− Path: "",
99+ Path: "",
93− URI: "http://localhost:8080",
100+ URI: "http://localhost:8080",
101+ },
94 },
102 },
95 },
103 },
96 errStrings: []string{emptyPathMsg},
104 errStrings: []string{emptyPathMsg},
97 }),
105 }),
98 Entry("with an empty URI", &validateUpstreamTableInput{
106 Entry("with an empty URI", &validateUpstreamTableInput{
99− upstreams: options.Upstreams{
107+ upstreams: options.UpstreamConfig{
100− {
108+ Upstreams: []options.Upstream{
109+ {
101− ID: "foo",
110+ ID: "foo",
102− Path: "/foo",
111+ Path: "/foo",
103− URI: "",
112+ URI: "",
113+ },
104 },
114 },
105 },
115 },
106 errStrings: []string{emptyURIMsg},
116 errStrings: []string{emptyURIMsg},
107 }),
117 }),
108 Entry("with an invalid URI", &validateUpstreamTableInput{
118 Entry("with an invalid URI", &validateUpstreamTableInput{
109− upstreams: options.Upstreams{
119+ upstreams: options.UpstreamConfig{
110− {
120+ Upstreams: []options.Upstream{
121+ {
111− ID: "foo",
122+ ID: "foo",
112− Path: "/foo",
123+ Path: "/foo",
113− URI: ":",
124+ URI: ":",
125+ },
114 },
126 },
115 },
127 },
116 errStrings: []string{invalidURIMsg},
128 errStrings: []string{invalidURIMsg},
117 }),
129 }),
118 Entry("with an invalid URI scheme", &validateUpstreamTableInput{
130 Entry("with an invalid URI scheme", &validateUpstreamTableInput{
119− upstreams: options.Upstreams{
131+ upstreams: options.UpstreamConfig{
120− {
132+ Upstreams: []options.Upstream{
133+ {
121− ID: "foo",
134+ ID: "foo",
122− Path: "/foo",
135+ Path: "/foo",
123− URI: "ftp://foo",
136+ URI: "ftp://foo",
137+ },
124 },
138 },
125 },
139 },
126 errStrings: []string{invalidURISchemeMsg},
140 errStrings: []string{invalidURISchemeMsg},
127 }),
141 }),
128 Entry("with a static upstream and invalid optons", &validateUpstreamTableInput{
142 Entry("with a static upstream and invalid optons", &validateUpstreamTableInput{
129− upstreams: options.Upstreams{
143+ upstreams: options.UpstreamConfig{
130− {
144+ Upstreams: []options.Upstream{
145+ {
131− ID: "foo",
146+ ID: "foo",
132− Path: "/foo",
147+ Path: "/foo",
133− URI: "ftp://foo",
148+ URI: "ftp://foo",
134− Static: true,
149+ Static: true,
135− FlushInterval: &flushInterval,
150+ FlushInterval: &flushInterval,
136− PassHostHeader: &truth,
151+ PassHostHeader: &truth,
137− ProxyWebSockets: &truth,
152+ ProxyWebSockets: &truth,
138− InsecureSkipTLSVerify: true,
153+ InsecureSkipTLSVerify: true,
154+ },
139 },
155 },
140 },
156 },
141 errStrings: []string{
157 errStrings: []string{
@@ -147,41 +163,47 @@ var _ = Describe("Upstreams", func() {
147 },
163 },
148 }),
164 }),
149 Entry("with duplicate IDs", &validateUpstreamTableInput{
165 Entry("with duplicate IDs", &validateUpstreamTableInput{
150− upstreams: options.Upstreams{
166+ upstreams: options.UpstreamConfig{
167+ Upstreams: []options.Upstream{
151− {
168+ {
152− ID: "foo",
169+ ID: "foo",
153− Path: "/foo1",
170+ Path: "/foo1",
154− URI: "http://foo",
171+ URI: "http://foo",
155− },
172+ },
156− {
173+ {
157− ID: "foo",
174+ ID: "foo",
158− Path: "/foo2",
175+ Path: "/foo2",
159− URI: "http://foo",
176+ URI: "http://foo",
177+ },
160 },
178 },
161 },
179 },
162 errStrings: []string{multipleIDsMsg},
180 errStrings: []string{multipleIDsMsg},
163 }),
181 }),
164 Entry("with duplicate Paths", &validateUpstreamTableInput{
182 Entry("with duplicate Paths", &validateUpstreamTableInput{
165− upstreams: options.Upstreams{
183+ upstreams: options.UpstreamConfig{
184+ Upstreams: []options.Upstream{
166− {
185+ {
167− ID: "foo1",
186+ ID: "foo1",
168− Path: "/foo",
187+ Path: "/foo",
169− URI: "http://foo",
188+ URI: "http://foo",
170− },
189+ },
171− {
190+ {
172− ID: "foo2",
191+ ID: "foo2",
173− Path: "/foo",
192+ Path: "/foo",
174− URI: "http://foo",
193+ URI: "http://foo",
194+ },
175 },
195 },
176 },
196 },
177 errStrings: []string{multiplePathsMsg},
197 errStrings: []string{multiplePathsMsg},
178 }),
198 }),
179 Entry("when a static code is supplied without static", &validateUpstreamTableInput{
199 Entry("when a static code is supplied without static", &validateUpstreamTableInput{
180− upstreams: options.Upstreams{
200+ upstreams: options.UpstreamConfig{
181− {
201+ Upstreams: []options.Upstream{
202+ {
182− ID: "foo",
203+ ID: "foo",
183− Path: "/foo",
204+ Path: "/foo",
184− StaticCode: &staticCode200,
205+ StaticCode: &staticCode200,
206+ },
185 },
207 },
186 },
208 },
187 errStrings: []string{emptyURIMsg, staticCodeMsg},
209 errStrings: []string{emptyURIMsg, staticCodeMsg},