git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add LegacyOptions and conversion to new Options

This will be temporary until we switch to structured config, then we can remove the LegacyOptions and conversions

commite932381ba789bbbad86ca5001f7b34c413281009 authorJoel Speed <joel.speed@hotmail.co.uk> dateTue 26 May 2020, 19:56 (6 years ago) parente02f99e
7 files changed+339−66
pkg/apis/options/legacy_options.go+98 pkg/apis/options/legacy_options_test.go+194 pkg/apis/options/load_test.go+5−6 pkg/apis/options/options.go+20−28 pkg/apis/options/options_suite_test.go+16 pkg/validation/options.go+1−11 pkg/validation/options_test.go+5−21
pkg/apis/options/legacy_options.gonew file+98
@@ -0,0 +1,98 @@
1+package options
2+
3+import (
4+ "fmt"
5+ "net/url"
6+ "strconv"
7+ "time"
8+
9+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
10+)
11+
12+type LegacyOptions struct {
13+ // Legacy options related to upstream servers
14+ LegacyFlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval"`
15+ LegacyPassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
16+ LegacyProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
17+ LegacySSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"`
18+ LegacyUpstreams []string `flag:"upstream" cfg:"upstreams"`
19+
20+ Options Options `cfg:",squash"`
21+}
22+
23+func NewLegacyOptions() *LegacyOptions {
24+ return &LegacyOptions{
25+ LegacyPassHostHeader: true,
26+ LegacyProxyWebSockets: true,
27+ LegacyFlushInterval: time.Duration(1) * time.Second,
28+
29+ Options: *NewOptions(),
30+ }
31+}
32+
33+func (l *LegacyOptions) ToOptions() (*Options, error) {
34+ upstreams, err := convertLegacyUpstreams(l.LegacyUpstreams, l.LegacySSLUpstreamInsecureSkipVerify, l.LegacyPassHostHeader, l.LegacyProxyWebSockets, l.LegacyFlushInterval)
35+ if err != nil {
36+ return nil, fmt.Errorf("error converting upstreams: %v", err)
37+ }
38+ l.Options.UpstreamServers = upstreams
39+
40+ return &l.Options, nil
41+}
42+
43+func convertLegacyUpstreams(upstreamStrings []string, skipVerify, passHostHeader, proxyWebSockets bool, flushInterval time.Duration) (Upstreams, error) {
44+ upstreams := Upstreams{}
45+
46+ for _, upstreamString := range upstreamStrings {
47+ u, err := url.Parse(upstreamString)
48+ if err != nil {
49+ return nil, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
50+ }
51+
52+ if u.Path == "" {
53+ u.Path = "/"
54+ }
55+
56+ upstream := Upstream{
57+ ID: u.Path,
58+ Path: u.Path,
59+ URI: upstreamString,
60+ InsecureSkipTLSVerify: skipVerify,
61+ PassHostHeader: passHostHeader,
62+ ProxyWebSockets: proxyWebSockets,
63+ FlushInterval: &flushInterval,
64+ }
65+
66+ switch u.Scheme {
67+ case "file":
68+ if u.Fragment != "" {
69+ upstream.ID = u.Fragment
70+ upstream.Path = u.Fragment
71+ }
72+ case "static":
73+ responseCode, err := strconv.Atoi(u.Host)
74+ if err != nil {
75+ logger.Printf("unable to convert %q to int, use default \"200\"", u.Host)
76+ responseCode = 200
77+ }
78+ upstream.Static = true
79+ upstream.StaticCode = &responseCode
80+
81+ // These are not allowed to be empty and must be unique
82+ upstream.ID = upstreamString
83+ upstream.Path = upstreamString
84+
85+ // Force defaults compatible with static responses
86+ upstream.URI = ""
87+ upstream.InsecureSkipTLSVerify = false
88+ upstream.PassHostHeader = true
89+ upstream.ProxyWebSockets = false
90+ flush := 1 * time.Second
91+ upstream.FlushInterval = &flush
92+ }
93+
94+ upstreams = append(upstreams, upstream)
95+ }
96+
97+ return upstreams, nil
98+}
pkg/apis/options/legacy_options_test.gonew file+194
@@ -0,0 +1,194 @@
1+package options
2+
3+import (
4+ "time"
5+
6+ . "github.com/onsi/ginkgo"
7+ . "github.com/onsi/ginkgo/extensions/table"
8+ . "github.com/onsi/gomega"
9+)
10+
11+var _ = Describe("Legacy Options", func() {
12+ Context("ToOptions", func() {
13+ It("converts the options as expected", func() {
14+ opts := NewOptions()
15+
16+ legacyOpts := NewLegacyOptions()
17+
18+ // Set upstreams and related options to test their conversion
19+ flushInterval := 5 * time.Second
20+ legacyOpts.LegacyFlushInterval = flushInterval
21+ legacyOpts.LegacyPassHostHeader = true
22+ legacyOpts.LegacyProxyWebSockets = true
23+ legacyOpts.LegacySSLUpstreamInsecureSkipVerify = true
24+ legacyOpts.LegacyUpstreams = []string{"http://foo.bar/baz", "file://var/lib/website#/bar"}
25+
26+ opts.UpstreamServers = Upstreams{
27+ {
28+ ID: "/baz",
29+ Path: "/baz",
30+ URI: "http://foo.bar/baz",
31+ FlushInterval: &flushInterval,
32+ InsecureSkipTLSVerify: true,
33+ PassHostHeader: true,
34+ ProxyWebSockets: true,
35+ },
36+ {
37+ ID: "/bar",
38+ Path: "/bar",
39+ URI: "file://var/lib/website#/bar",
40+ FlushInterval: &flushInterval,
41+ InsecureSkipTLSVerify: true,
42+ PassHostHeader: true,
43+ ProxyWebSockets: true,
44+ },
45+ }
46+
47+ converted, err := legacyOpts.ToOptions()
48+ Expect(err).ToNot(HaveOccurred())
49+ Expect(converted).To(Equal(opts))
50+ })
51+ })
52+
53+ Context("Legacy Upstreams", func() {
54+ type convertUpstreamsTableInput struct {
55+ upstreamStrings []string
56+ expectedUpstreams Upstreams
57+ errMsg string
58+ }
59+
60+ defaultFlushInterval := 1 * time.Second
61+
62+ // Non defaults for these options
63+ skipVerify := true
64+ passHostHeader := false
65+ proxyWebSockets := true
66+ flushInterval := 5 * time.Second
67+
68+ // Test cases and expected outcomes
69+ validHTTP := "http://foo.bar/baz"
70+ validHTTPUpstream := Upstream{
71+ ID: "/baz",
72+ Path: "/baz",
73+ URI: validHTTP,
74+ InsecureSkipTLSVerify: skipVerify,
75+ PassHostHeader: passHostHeader,
76+ ProxyWebSockets: proxyWebSockets,
77+ FlushInterval: &flushInterval,
78+ }
79+
80+ // Test cases and expected outcomes
81+ emptyPathHTTP := "http://foo.bar"
82+ emptyPathHTTPUpstream := Upstream{
83+ ID: "/",
84+ Path: "/",
85+ URI: emptyPathHTTP,
86+ InsecureSkipTLSVerify: skipVerify,
87+ PassHostHeader: passHostHeader,
88+ ProxyWebSockets: proxyWebSockets,
89+ FlushInterval: &flushInterval,
90+ }
91+
92+ validFileWithFragment := "file://var/lib/website#/bar"
93+ validFileWithFragmentUpstream := Upstream{
94+ ID: "/bar",
95+ Path: "/bar",
96+ URI: validFileWithFragment,
97+ InsecureSkipTLSVerify: skipVerify,
98+ PassHostHeader: passHostHeader,
99+ ProxyWebSockets: proxyWebSockets,
100+ FlushInterval: &flushInterval,
101+ }
102+
103+ validStatic := "static://204"
104+ validStaticCode := 204
105+ validStaticUpstream := Upstream{
106+ ID: validStatic,
107+ Path: validStatic,
108+ URI: "",
109+ Static: true,
110+ StaticCode: &validStaticCode,
111+ InsecureSkipTLSVerify: false,
112+ PassHostHeader: true,
113+ ProxyWebSockets: false,
114+ FlushInterval: &defaultFlushInterval,
115+ }
116+
117+ invalidStatic := "static://abc"
118+ invalidStaticCode := 200
119+ invalidStaticUpstream := Upstream{
120+ ID: invalidStatic,
121+ Path: invalidStatic,
122+ URI: "",
123+ Static: true,
124+ StaticCode: &invalidStaticCode,
125+ InsecureSkipTLSVerify: false,
126+ PassHostHeader: true,
127+ ProxyWebSockets: false,
128+ FlushInterval: &defaultFlushInterval,
129+ }
130+
131+ invalidHTTP := ":foo"
132+ invalidHTTPErrMsg := "could not parse upstream \":foo\": parse \":foo\": missing protocol scheme"
133+
134+ DescribeTable("convertLegacyUpstreams",
135+ func(o *convertUpstreamsTableInput) {
136+ upstreams, err := convertLegacyUpstreams(o.upstreamStrings, skipVerify, passHostHeader, proxyWebSockets, flushInterval)
137+
138+ if o.errMsg != "" {
139+ Expect(err).To(HaveOccurred())
140+ Expect(err.Error()).To(Equal(o.errMsg))
141+ } else {
142+ Expect(err).ToNot(HaveOccurred())
143+ }
144+
145+ Expect(upstreams).To(ConsistOf(o.expectedUpstreams))
146+ },
147+ Entry("with no upstreams", &convertUpstreamsTableInput{
148+ upstreamStrings: []string{},
149+ expectedUpstreams: Upstreams{},
150+ errMsg: "",
151+ }),
152+ Entry("with a valid HTTP upstream", &convertUpstreamsTableInput{
153+ upstreamStrings: []string{validHTTP},
154+ expectedUpstreams: Upstreams{validHTTPUpstream},
155+ errMsg: "",
156+ }),
157+ Entry("with a HTTP upstream with an empty path", &convertUpstreamsTableInput{
158+ upstreamStrings: []string{emptyPathHTTP},
159+ expectedUpstreams: Upstreams{emptyPathHTTPUpstream},
160+ errMsg: "",
161+ }),
162+ Entry("with a valid File upstream with a fragment", &convertUpstreamsTableInput{
163+ upstreamStrings: []string{validFileWithFragment},
164+ expectedUpstreams: Upstreams{validFileWithFragmentUpstream},
165+ errMsg: "",
166+ }),
167+ Entry("with a valid static upstream", &convertUpstreamsTableInput{
168+ upstreamStrings: []string{validStatic},
169+ expectedUpstreams: Upstreams{validStaticUpstream},
170+ errMsg: "",
171+ }),
172+ Entry("with an invalid static upstream, code is 200", &convertUpstreamsTableInput{
173+ upstreamStrings: []string{invalidStatic},
174+ expectedUpstreams: Upstreams{invalidStaticUpstream},
175+ errMsg: "",
176+ }),
177+ Entry("with an invalid HTTP upstream", &convertUpstreamsTableInput{
178+ upstreamStrings: []string{invalidHTTP},
179+ expectedUpstreams: Upstreams{},
180+ errMsg: invalidHTTPErrMsg,
181+ }),
182+ Entry("with an invalid HTTP upstream and other upstreams", &convertUpstreamsTableInput{
183+ upstreamStrings: []string{validHTTP, invalidHTTP},
184+ expectedUpstreams: Upstreams{},
185+ errMsg: invalidHTTPErrMsg,
186+ }),
187+ Entry("with multiple valid upstreams", &convertUpstreamsTableInput{
188+ upstreamStrings: []string{validHTTP, validFileWithFragment, validStatic},
189+ expectedUpstreams: Upstreams{validHTTPUpstream, validFileWithFragmentUpstream, validStaticUpstream},
190+ errMsg: "",
191+ }),
192+ )
193+ })
194+})
pkg/apis/options/load_test.go+5−6
@@ -4,7 +4,6 @@ import (
44 "fmt"
55 "io/ioutil"
66 "os"
7− "testing"
87
98 . "github.com/onsi/ginkgo"
109 . "github.com/onsi/ginkgo/extensions/table"
@@ -12,11 +11,6 @@ import (
1211 "github.com/spf13/pflag"
1312)
1413
15−func TestOptionsSuite(t *testing.T) {
16− RegisterFailHandler(Fail)
17− RunSpecs(t, "Options Suite")
18−}
19−
2014var _ = Describe("Load", func() {
2115 Context("with a testOptions structure", func() {
2216 type TestOptionSubStruct struct {
@@ -300,6 +294,11 @@ var _ = Describe("Load", func() {
300294 input: &Options{},
301295 expectedOutput: NewOptions(),
302296 }),
297+ Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
298+ flagSet: NewFlagSet,
299+ input: &LegacyOptions{},
300+ expectedOutput: NewLegacyOptions(),
301+ }),
303302 )
304303 })
305304})
pkg/apis/options/options.go+20−28
@@ -24,7 +24,6 @@ type Options struct {
2424 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
2525 PingPath string `flag:"ping-path" cfg:"ping_path"`
2626 PingUserAgent string `flag:"ping-user-agent" cfg:"ping_user_agent"`
27− ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
2827 HTTPAddress string `flag:"http-address" cfg:"http_address"`
2928 HTTPSAddress string `flag:"https-address" cfg:"https_address"`
3029 ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
@@ -64,26 +63,26 @@ type Options struct {
6463 Session SessionOptions `cfg:",squash"`
6564 Logging Logging `cfg:",squash"`
6665
67− Upstreams []string `flag:"upstream" cfg:"upstreams"`
68− SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
69− SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
70− SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
71− ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
72− PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
73− SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
74− PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
75− BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
76− PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
77− PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
78− SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
79− PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
80− SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
81− SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"`
82− SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
83− SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
84− PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
85− SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
86− FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval"`
66+ // Not used in the legacy config, name not allowed to match an external key (upstreams)
67+ // TODO(JoelSpeed): Rename when legacy config is removed
68+ UpstreamServers Upstreams `cfg:",internal"`
69+
70+ SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
71+ SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
72+ SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
73+ ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
74+ PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
75+ SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
76+ PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
77+ BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
78+ PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
79+ SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
80+ PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
81+ SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
82+ SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
83+ SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
84+ PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
85+ SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
8786
8887 // These options allow for other providers besides Google, with
8988 // potential overrides.
@@ -114,7 +113,6 @@ type Options struct {
114113
115114 // internal values that are set after config validation
116115 redirectURL *url.URL
117− proxyURLs []*url.URL
118116 compiledRegex []*regexp.Regexp
119117 provider providers.Provider
120118 signatureData *SignatureData
@@ -125,7 +123,6 @@ type Options struct {
125123
126124// Options for Getting internal values
127125func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
128−func (o *Options) GetProxyURLs() []*url.URL { return o.proxyURLs }
129126func (o *Options) GetCompiledRegex() []*regexp.Regexp { return o.compiledRegex }
130127func (o *Options) GetProvider() providers.Provider { return o.provider }
131128func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
@@ -135,7 +132,6 @@ func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.re
135132
136133// Options for Setting internal values
137134func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
138−func (o *Options) SetProxyURLs(s []*url.URL) { o.proxyURLs = s }
139135func (o *Options) SetCompiledRegex(s []*regexp.Regexp) { o.compiledRegex = s }
140136func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
141137func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
@@ -149,7 +145,6 @@ func NewOptions() *Options {
149145 ProxyPrefix: "/oauth2",
150146 ProviderType: "google",
151147 PingPath: "/ping",
152− ProxyWebSockets: true,
153148 HTTPAddress: "127.0.0.1:4180",
154149 HTTPSAddress: ":443",
155150 RealClientIPHeader: "X-Real-IP",
@@ -160,13 +155,10 @@ func NewOptions() *Options {
160155 AzureTenant: "common",
161156 SetXAuthRequest: false,
162157 SkipAuthPreflight: false,
163− SkipAuthStripHeaders: false,
164− FlushInterval: time.Duration(1) * time.Second,
165158 PassBasicAuth: true,
166159 SetBasicAuth: false,
167160 PassUserHeaders: true,
168161 PassAccessToken: false,
169− PassHostHeader: true,
170162 SetAuthorization: false,
171163 PassAuthorization: false,
172164 PreferEmailToUser: false,
pkg/apis/options/options_suite_test.gonew file+16
@@ -0,0 +1,16 @@
1+package options
2+
3+import (
4+ "testing"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/gomega"
9+)
10+
11+func TestOptionsSuite(t *testing.T) {
12+ logger.SetOutput(GinkgoWriter)
13+
14+ RegisterFailHandler(Fail)
15+ RunSpecs(t, "Options Suite")
16+}
pkg/validation/options.go+1−11
@@ -176,17 +176,7 @@ func Validate(o *options.Options) error {
176176 redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
177177 o.SetRedirectURL(redirectURL)
178178
179− for _, u := range o.Upstreams {
180− upstreamURL, err := url.Parse(u)
181− if err != nil {
182− msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
183− } else {
184− if upstreamURL.Path == "" {
185− upstreamURL.Path = "/"
186− }
187− o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL))
188− }
189− }
179+ msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
190180
191181 for _, u := range o.SkipAuthRegex {
192182 compiledRegex, err := regexp.Compile(u)
pkg/validation/options_test.go+5−21
@@ -22,7 +22,11 @@ const (
2222
2323func testOptions() *options.Options {
2424 o := options.NewOptions()
25− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
25+ o.UpstreamServers = append(o.UpstreamServers, options.Upstream{
26+ ID: "upstream",
27+ Path: "/",
28+ URI: "http://127.0.0.1:8080/",
29+ })
2630 o.Cookie.Secret = cookieSecret
2731 o.ClientID = clientID
2832 o.ClientSecret = clientSecret
@@ -140,26 +144,6 @@ func TestRedirectURL(t *testing.T) {
140144 assert.Equal(t, expected, o.GetRedirectURL())
141145}
142146
143−func TestProxyURLs(t *testing.T) {
144− o := testOptions()
145− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
146− assert.Equal(t, nil, Validate(o))
147− expected := []*url.URL{
148− {Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
149− // note the '/' was added
150− {Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
151− }
152− assert.Equal(t, expected, o.GetProxyURLs())
153−}
154−
155−func TestProxyURLsError(t *testing.T) {
156− o := testOptions()
157− o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
158− err := Validate(o)
159− assert.NotEqual(t, nil, err)
160− assert.Contains(t, err.Error(), "error parsing upstream")
161−}
162−
163147func TestCompiledRegex(t *testing.T) {
164148 o := testOptions()
165149 regexps := []string{"/foo/.*", "/ba[rz]/quux"}