git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #15 from pusher/whitelist-domains

Whitelist domains

commitfa2545636bc5f73e8eb6b1499f6185973b8afd85 authorJoel Speed <Joel.speed@hotmail.co.uk> dateSat 2 Feb 2019, 18:55 (8 years ago) parentc6d2126987b25f
6 files changed+84−2
CHANGELOG.md+2 README.md+3 main.go+2 oauthproxy.go+25−2 oauthproxy_test.go+51 options.go+1
CHANGELOG.md+2
@@ -7,6 +7,8 @@
77 - Implement token refreshing in OIDC provider
88 - Split cookies larger than 4k limit into multiple cookies
99 - Implement token validation in OIDC provider
10+- [#15](https://github.com/pusher/oauth2_proxy/pull/21) WhitelistDomains (@joelspeed)
11+ - Add `--whitelist-domain` flag to allow redirection to approved domains after OAuth flow
1012- [#21](https://github.com/pusher/oauth2_proxy/pull/21) Docker Improvement (@yaegashi)
1113 - Move Docker base image from debian to alpine
1214 - Install ca-certificates in docker image
README.md+3
@@ -237,8 +237,11 @@ Usage of oauth2_proxy:
237237 -upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
238238 -validate-url string: Access token validation endpoint
239239 -version: print version string
240+ -whitelist-domain: allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)
240241```
241242
243+Note, when using the `whitelist-domain` option, any domain prefixed with a `.` will allow any subdomain of the specified domain as a valid redirect URL.
244+
242245See below for provider specific options
243246
244247### Upstreams Configuration
main.go+2
@@ -18,6 +18,7 @@ func main() {
1818 flagSet := flag.NewFlagSet("oauth2_proxy", flag.ExitOnError)
1919
2020 emailDomains := StringArray{}
21+ whitelistDomains := StringArray{}
2122 upstreams := StringArray{}
2223 skipAuthRegex := StringArray{}
2324 googleGroups := StringArray{}
@@ -46,6 +47,7 @@ func main() {
4647 flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
4748
4849 flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
50+ flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
4951 flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
5052 flagSet.String("github-org", "", "restrict logins to members of this organisation")
5153 flagSet.String("github-team", "", "restrict logins to members of this team")
oauthproxy.go+25−2
@@ -70,6 +70,7 @@ type OAuthProxy struct {
7070 AuthOnlyPath string
7171
7272 redirectURL *url.URL // the url to receive requests at
73+ whitelistDomains []string
7374 provider providers.Provider
7475 ProxyPrefix string
7576 SignInMessage string
@@ -224,6 +225,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
224225 provider: opts.provider,
225226 serveMux: serveMux,
226227 redirectURL: redirectURL,
228+ whitelistDomains: opts.WhitelistDomains,
227229 skipAuthRegex: opts.SkipAuthRegex,
228230 skipAuthPreflight: opts.SkipAuthPreflight,
229231 compiledRegex: opts.CompiledRegex,
@@ -567,7 +569,7 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
567569 }
568570
569571 redirect = req.Form.Get("rd")
570− if redirect == "" || !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
572+ if !p.IsValidRedirect(redirect) {
571573 redirect = req.URL.Path
572574 if strings.HasPrefix(redirect, p.ProxyPrefix) {
573575 redirect = "/"
@@ -577,6 +579,27 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
577579 return
578580}
579581
582+// IsValidRedirect checks whether the redirect URL is whitelisted
583+func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
584+ switch {
585+ case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//"):
586+ return true
587+ case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
588+ redirectURL, err := url.Parse(redirect)
589+ if err != nil {
590+ return false
591+ }
592+ for _, domain := range p.whitelistDomains {
593+ if (redirectURL.Host == domain) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectURL.Host, domain)) {
594+ return true
595+ }
596+ }
597+ return false
598+ default:
599+ return false
600+ }
601+}
602+
580603// IsWhitelistedRequest is used to check if auth should be skipped for this request
581604func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
582605 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
@@ -713,7 +736,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
713736 return
714737 }
715738
716− if !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
739+ if !p.IsValidRedirect(redirect) {
717740 redirect = "/"
718741 }
719742
oauthproxy_test.go+51
@@ -93,6 +93,57 @@ func TestRobotsTxt(t *testing.T) {
9393 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
9494}
9595
96+func TestIsValidRedirect(t *testing.T) {
97+ opts := NewOptions()
98+ opts.ClientID = "bazquux"
99+ opts.ClientSecret = "foobar"
100+ opts.CookieSecret = "xyzzyplugh"
101+ // Should match domains that are exactly foo.bar and any subdomain of bar.foo
102+ opts.WhitelistDomains = []string{"foo.bar", ".bar.foo"}
103+ opts.Validate()
104+
105+ proxy := NewOAuthProxy(opts, func(string) bool { return true })
106+
107+ noRD := proxy.IsValidRedirect("")
108+ assert.Equal(t, false, noRD)
109+
110+ singleSlash := proxy.IsValidRedirect("/redirect")
111+ assert.Equal(t, true, singleSlash)
112+
113+ doubleSlash := proxy.IsValidRedirect("//redirect")
114+ assert.Equal(t, false, doubleSlash)
115+
116+ validHTTP := proxy.IsValidRedirect("http://foo.bar/redirect")
117+ assert.Equal(t, true, validHTTP)
118+
119+ validHTTPS := proxy.IsValidRedirect("https://foo.bar/redirect")
120+ assert.Equal(t, true, validHTTPS)
121+
122+ invalidHTTPSubdomain := proxy.IsValidRedirect("http://baz.foo.bar/redirect")
123+ assert.Equal(t, false, invalidHTTPSubdomain)
124+
125+ invalidHTTPSSubdomain := proxy.IsValidRedirect("https://baz.foo.bar/redirect")
126+ assert.Equal(t, false, invalidHTTPSSubdomain)
127+
128+ validHTTPSubdomain := proxy.IsValidRedirect("http://baz.bar.foo/redirect")
129+ assert.Equal(t, true, validHTTPSubdomain)
130+
131+ validHTTPSSubdomain := proxy.IsValidRedirect("https://baz.bar.foo/redirect")
132+ assert.Equal(t, true, validHTTPSSubdomain)
133+
134+ invalidHTTP1 := proxy.IsValidRedirect("http://foo.bar.evil.corp/redirect")
135+ assert.Equal(t, false, invalidHTTP1)
136+
137+ invalidHTTPS1 := proxy.IsValidRedirect("https://foo.bar.evil.corp/redirect")
138+ assert.Equal(t, false, invalidHTTPS1)
139+
140+ invalidHTTP2 := proxy.IsValidRedirect("http://evil.corp/redirect?rd=foo.bar")
141+ assert.Equal(t, false, invalidHTTP2)
142+
143+ invalidHTTPS2 := proxy.IsValidRedirect("https://evil.corp/redirect?rd=foo.bar")
144+ assert.Equal(t, false, invalidHTTPS2)
145+}
146+
96147type TestProvider struct {
97148 *providers.ProviderData
98149 EmailAddress string
options.go+1
@@ -33,6 +33,7 @@ type Options struct {
3333 AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
3434 AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
3535 EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
36+ WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
3637 GitHubOrg string `flag:"github-org" cfg:"github_org"`
3738 GitHubTeam string `flag:"github-team" cfg:"github_team"`
3839 GoogleGroups []string `flag:"google-group" cfg:"google_group"`