From 583ec18fa23e12b8c8b44ff870b754c3bc7b04a4 Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Sun, 28 Jul 2019 15:54:39 +0200 Subject: [PATCH 01/10] Add keycloak provider --- main.go | 1 + options.go | 3 + providers/keycloak.go | 86 +++++++++++++++++++++ providers/keycloak_test.go | 148 +++++++++++++++++++++++++++++++++++++ providers/providers.go | 2 + 5 files changed, 240 insertions(+) create mode 100644 providers/keycloak.go create mode 100644 providers/keycloak_test.go diff --git a/main.go b/main.go index 97e2357d..60d64d02 100644 --- a/main.go +++ b/main.go @@ -54,6 +54,7 @@ func main() { flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") + flagSet.String("keycloak-group", "", "restrict login to members of this group.") flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") flagSet.String("github-org", "", "restrict logins to members of this organisation") flagSet.String("github-team", "", "restrict logins to members of this team") diff --git a/options.go b/options.go index 7dcb12bb..3c7d0787 100644 --- a/options.go +++ b/options.go @@ -41,6 +41,7 @@ type Options struct { TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"` AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"` + KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group"` AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"` EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"` WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"` @@ -394,6 +395,8 @@ func parseProviderInfo(o *Options, msgs []string) []string { p.Configure(o.AzureTenant) case *providers.GitHubProvider: p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam) + case *providers.KeycloakProvider: + p.SetGroup(o.KeycloakGroup) case *providers.GoogleProvider: if o.GoogleServiceAccountJSON != "" { file, err := os.Open(o.GoogleServiceAccountJSON) diff --git a/providers/keycloak.go b/providers/keycloak.go new file mode 100644 index 00000000..ae1ef693 --- /dev/null +++ b/providers/keycloak.go @@ -0,0 +1,86 @@ +package providers + +import ( + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "log" + "net/http" + "net/url" + + "github.com/bitly/oauth2_proxy/api" +) + +type KeycloakProvider struct { + *ProviderData + Group string +} + +func NewKeycloakProvider(p *ProviderData) *KeycloakProvider { + p.ProviderName = "Keycloak" + if p.LoginURL == nil || p.LoginURL.String() == "" { + p.LoginURL = &url.URL{ + Scheme: "https", + Host: "keycloak.org", + Path: "/oauth/authorize", + } + } + if p.RedeemURL == nil || p.RedeemURL.String() == "" { + p.RedeemURL = &url.URL{ + Scheme: "https", + Host: "keycloak.org", + Path: "/oauth/token", + } + } + if p.ValidateURL == nil || p.ValidateURL.String() == "" { + p.ValidateURL = &url.URL{ + Scheme: "https", + Host: "keycloak.org", + Path: "/api/v3/user", + } + } + if p.Scope == "" { + p.Scope = "api" + } + return &KeycloakProvider{ProviderData: p} +} + +func (p *KeycloakProvider) SetGroup(group string) { + p.Group = group +} + +func (p *KeycloakProvider) GetEmailAddress(s *sessions.SessionState) (string, error) { + + req, err := http.NewRequest("GET", p.ValidateURL.String(), nil) + req.Header.Set("Authorization", "Bearer "+s.AccessToken) + if err != nil { + log.Printf("failed building request %s", err) + return "", err + } + json, err := api.Request(req) + if err != nil { + log.Printf("failed making request %s", err) + return "", err + } + + if p.Group != "" { + var groups, err = json.Get("groups").Array() + if err != nil { + log.Printf("groups not found %s", err) + return "", err + } + + var found = false + for i := range groups { + if groups[i].(string) == p.Group { + found = true + break + } + } + + if found != true { + log.Printf("group not found, access denied") + return "", nil + } + } + + return json.Get("email").String() +} diff --git a/providers/keycloak_test.go b/providers/keycloak_test.go new file mode 100644 index 00000000..f64df12f --- /dev/null +++ b/providers/keycloak_test.go @@ -0,0 +1,148 @@ +package providers + +import ( + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/bmizerany/assert" +) + +func testKeycloakProvider(hostname, group string) *KeycloakProvider { + p := NewKeycloakProvider( + &ProviderData{ + ProviderName: "", + LoginURL: &url.URL{}, + RedeemURL: &url.URL{}, + ProfileURL: &url.URL{}, + ValidateURL: &url.URL{}, + Scope: ""}) + + if group != "" { + p.SetGroup(group) + } + + if hostname != "" { + updateURL(p.Data().LoginURL, hostname) + updateURL(p.Data().RedeemURL, hostname) + updateURL(p.Data().ProfileURL, hostname) + updateURL(p.Data().ValidateURL, hostname) + } + return p +} + +func testKeycloakBackend(payload string) *httptest.Server { + path := "/api/v3/user" + + return httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + url := r.URL + if url.Path != path { + w.WriteHeader(404) + } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" { + w.WriteHeader(403) + } else { + w.WriteHeader(200) + w.Write([]byte(payload)) + } + })) +} + +func TestKeycloakProviderDefaults(t *testing.T) { + p := testKeycloakProvider("", "") + assert.NotEqual(t, nil, p) + assert.Equal(t, "Keycloak", p.Data().ProviderName) + assert.Equal(t, "https://keycloak.org/oauth/authorize", + p.Data().LoginURL.String()) + assert.Equal(t, "https://keycloak.org/oauth/token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://keycloak.org/api/v3/user", + p.Data().ValidateURL.String()) + assert.Equal(t, "api", p.Data().Scope) +} + +func TestKeycloakProviderOverrides(t *testing.T) { + p := NewKeycloakProvider( + &ProviderData{ + LoginURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/auth"}, + RedeemURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/token"}, + ValidateURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/api/v3/user"}, + Scope: "profile"}) + assert.NotEqual(t, nil, p) + assert.Equal(t, "Keycloak", p.Data().ProviderName) + assert.Equal(t, "https://example.com/oauth/auth", + p.Data().LoginURL.String()) + assert.Equal(t, "https://example.com/oauth/token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://example.com/api/v3/user", + p.Data().ValidateURL.String()) + assert.Equal(t, "profile", p.Data().Scope) +} + +func TestKeycloakProviderGetEmailAddress(t *testing.T) { + b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testKeycloakProvider(b_url.Host, "") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +func TestKeycloakProviderGetEmailAddressAndGroup(t *testing.T) { + b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testKeycloakProvider(b_url.Host, "test-grp1") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +// Note that trying to trigger the "failed building request" case is not +// practical, since the only way it can fail is if the URL fails to parse. +func TestKeycloakProviderGetEmailAddressFailedRequest(t *testing.T) { + b := testKeycloakBackend("unused payload") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testKeycloakProvider(b_url.Host, "") + + // We'll trigger a request failure by using an unexpected access + // token. Alternatively, we could allow the parsing of the payload as + // JSON to fail. + session := &sessions.SessionState{AccessToken: "unexpected_access_token"} + email, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) + assert.Equal(t, "", email) +} + +func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { + b := testKeycloakBackend("{\"foo\": \"bar\"}") + defer b.Close() + + b_url, _ := url.Parse(b.URL) + p := testKeycloakProvider(b_url.Host, "") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) + assert.Equal(t, "", email) +} diff --git a/providers/providers.go b/providers/providers.go index baf723d9..bbb86755 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -28,6 +28,8 @@ func New(provider string, p *ProviderData) Provider { return NewFacebookProvider(p) case "github": return NewGitHubProvider(p) + case "keycloak": + return NewKeycloakProvider(p) case "azure": return NewAzureProvider(p) case "gitlab": From 800a3694c247bceaf8f9a358fcfeed4d12bccb9c Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Sun, 28 Jul 2019 16:26:09 +0200 Subject: [PATCH 02/10] Add docs and record in CHANGELOG --- CHANGELOG.md | 1 + docs/2_auth.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f09c28..fdbb6d99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ## Changes since v3.2.0 +- [#226](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka) - [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) - [#209](https://github.com/pusher/outh2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) - [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent (@JoelSpeed) diff --git a/docs/2_auth.md b/docs/2_auth.md index 7a9bebd3..2d53e1f6 100644 --- a/docs/2_auth.md +++ b/docs/2_auth.md @@ -15,6 +15,7 @@ Valid providers are : - [Azure](#azure-auth-provider) - [Facebook](#facebook-auth-provider) - [GitHub](#github-auth-provider) +- [Keycloak](#keycloak-auth-provider) - [GitLab](#gitlab-auth-provider) - [LinkedIn](#linkedin-auth-provider) - [login.gov](#logingov-provider) @@ -101,6 +102,20 @@ If you are using GitHub enterprise, make sure you set the following to the appro -redeem-url="http(s):///login/oauth/access_token" -validate-url="http(s):///api/v3" +### Keycloak Auth Provider + +1. Create new client in your Keycloak with **Access Type** 'confidental'. +2. Create a mapper with **Mapper Type** 'Group Membership'. + +Make sure you set the following to the appropriate url: + + -provider=keycloak + -client-id= + -client-secret= + -login-url="http(s):///realms//protocol/openid-connect/auth" + -redeem-url="http(s):///realms/master//openid-connect/auth/token" + -validate-url="http(s):///realms/master//openid-connect/userinfo" + ### GitLab Auth Provider Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html) From 53524875d1e62f11485c7cbc6d07967fc0830d8f Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Sun, 28 Jul 2019 16:46:16 +0200 Subject: [PATCH 03/10] Get rid of dependencies on bitly/oauth2_proxy/api --- providers/keycloak.go | 16 ++++++++-------- providers/keycloak_test.go | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/providers/keycloak.go b/providers/keycloak.go index ae1ef693..27153726 100644 --- a/providers/keycloak.go +++ b/providers/keycloak.go @@ -1,12 +1,12 @@ package providers import ( - "github.com/pusher/oauth2_proxy/pkg/apis/sessions" - "log" "net/http" "net/url" - "github.com/bitly/oauth2_proxy/api" + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" + "github.com/pusher/oauth2_proxy/pkg/requests" ) type KeycloakProvider struct { @@ -52,19 +52,19 @@ func (p *KeycloakProvider) GetEmailAddress(s *sessions.SessionState) (string, er req, err := http.NewRequest("GET", p.ValidateURL.String(), nil) req.Header.Set("Authorization", "Bearer "+s.AccessToken) if err != nil { - log.Printf("failed building request %s", err) + logger.Printf("failed building request %s", err) return "", err } - json, err := api.Request(req) + json, err := requests.Request(req) if err != nil { - log.Printf("failed making request %s", err) + logger.Printf("failed making request %s", err) return "", err } if p.Group != "" { var groups, err = json.Get("groups").Array() if err != nil { - log.Printf("groups not found %s", err) + logger.Printf("groups not found %s", err) return "", err } @@ -77,7 +77,7 @@ func (p *KeycloakProvider) GetEmailAddress(s *sessions.SessionState) (string, er } if found != true { - log.Printf("group not found, access denied") + logger.Printf("group not found, access denied") return "", nil } } diff --git a/providers/keycloak_test.go b/providers/keycloak_test.go index f64df12f..ced528bf 100644 --- a/providers/keycloak_test.go +++ b/providers/keycloak_test.go @@ -94,8 +94,8 @@ func TestKeycloakProviderGetEmailAddress(t *testing.T) { b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}") defer b.Close() - b_url, _ := url.Parse(b.URL) - p := testKeycloakProvider(b_url.Host, "") + bUrl, _ := url.Parse(b.URL) + p := testKeycloakProvider(bUrl.Host, "") session := &sessions.SessionState{AccessToken: "imaginary_access_token"} email, err := p.GetEmailAddress(session) @@ -107,8 +107,8 @@ func TestKeycloakProviderGetEmailAddressAndGroup(t *testing.T) { b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}") defer b.Close() - b_url, _ := url.Parse(b.URL) - p := testKeycloakProvider(b_url.Host, "test-grp1") + bUrl, _ := url.Parse(b.URL) + p := testKeycloakProvider(bUrl.Host, "test-grp1") session := &sessions.SessionState{AccessToken: "imaginary_access_token"} email, err := p.GetEmailAddress(session) @@ -122,8 +122,8 @@ func TestKeycloakProviderGetEmailAddressFailedRequest(t *testing.T) { b := testKeycloakBackend("unused payload") defer b.Close() - b_url, _ := url.Parse(b.URL) - p := testKeycloakProvider(b_url.Host, "") + bUrl, _ := url.Parse(b.URL) + p := testKeycloakProvider(bUrl.Host, "") // We'll trigger a request failure by using an unexpected access // token. Alternatively, we could allow the parsing of the payload as @@ -138,8 +138,8 @@ func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { b := testKeycloakBackend("{\"foo\": \"bar\"}") defer b.Close() - b_url, _ := url.Parse(b.URL) - p := testKeycloakProvider(b_url.Host, "") + bUrl, _ := url.Parse(b.URL) + p := testKeycloakProvider(bUrl.Host, "") session := &sessions.SessionState{AccessToken: "imaginary_access_token"} email, err := p.GetEmailAddress(session) From 4eab98e65b3247c2fa61340f693d2f958450b7af Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Sun, 28 Jul 2019 16:58:11 +0200 Subject: [PATCH 04/10] Fix travis analysis --- providers/keycloak_test.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/providers/keycloak_test.go b/providers/keycloak_test.go index ced528bf..b5d2625b 100644 --- a/providers/keycloak_test.go +++ b/providers/keycloak_test.go @@ -1,15 +1,18 @@ package providers import ( - "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "net/http" "net/http/httptest" "net/url" "testing" "github.com/bmizerany/assert" + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" ) +const imaginaryAccessToken = "imaginary_access_token" +const bearerAccessToken = "Bearer " + imaginaryAccessToken + func testKeycloakProvider(hostname, group string) *KeycloakProvider { p := NewKeycloakProvider( &ProviderData{ @@ -41,7 +44,7 @@ func testKeycloakBackend(payload string) *httptest.Server { url := r.URL if url.Path != path { w.WriteHeader(404) - } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" { + } else if r.Header.Get("Authorization") != bearerAccessToken { w.WriteHeader(403) } else { w.WriteHeader(200) @@ -94,10 +97,10 @@ func TestKeycloakProviderGetEmailAddress(t *testing.T) { b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}") defer b.Close() - bUrl, _ := url.Parse(b.URL) - p := testKeycloakProvider(bUrl.Host, "") + bURL, _ := url.Parse(b.URL) + p := testKeycloakProvider(bURL.Host, "") - session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + session := &sessions.SessionState{AccessToken: imaginaryAccessToken} email, err := p.GetEmailAddress(session) assert.Equal(t, nil, err) assert.Equal(t, "michael.bland@gsa.gov", email) @@ -107,10 +110,10 @@ func TestKeycloakProviderGetEmailAddressAndGroup(t *testing.T) { b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}") defer b.Close() - bUrl, _ := url.Parse(b.URL) - p := testKeycloakProvider(bUrl.Host, "test-grp1") + bURL, _ := url.Parse(b.URL) + p := testKeycloakProvider(bURL.Host, "test-grp1") - session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + session := &sessions.SessionState{AccessToken: imaginaryAccessToken} email, err := p.GetEmailAddress(session) assert.Equal(t, nil, err) assert.Equal(t, "michael.bland@gsa.gov", email) @@ -122,8 +125,8 @@ func TestKeycloakProviderGetEmailAddressFailedRequest(t *testing.T) { b := testKeycloakBackend("unused payload") defer b.Close() - bUrl, _ := url.Parse(b.URL) - p := testKeycloakProvider(bUrl.Host, "") + bURL, _ := url.Parse(b.URL) + p := testKeycloakProvider(bURL.Host, "") // We'll trigger a request failure by using an unexpected access // token. Alternatively, we could allow the parsing of the payload as @@ -138,10 +141,10 @@ func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { b := testKeycloakBackend("{\"foo\": \"bar\"}") defer b.Close() - bUrl, _ := url.Parse(b.URL) - p := testKeycloakProvider(bUrl.Host, "") + bURL, _ := url.Parse(b.URL) + p := testKeycloakProvider(bURL.Host, "") - session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + session := &sessions.SessionState{AccessToken: imaginaryAccessToken} email, err := p.GetEmailAddress(session) assert.NotEqual(t, nil, err) assert.Equal(t, "", email) From a025228a6ddea9e563d841426d0d6b93198c5d7c Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Wed, 31 Jul 2019 14:36:13 +0200 Subject: [PATCH 05/10] Set env tag appropriately --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 3c7d0787..8eaf21e0 100644 --- a/options.go +++ b/options.go @@ -41,7 +41,7 @@ type Options struct { TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"` AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"` - KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group"` + KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_AUTHENTICATED_KEYCLOAK_GROUP"` AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"` EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"` WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"` From 436936836d57cffe2df0b9104f3c5961267fafa6 Mon Sep 17 00:00:00 2001 From: Karel Pokorny Date: Wed, 31 Jul 2019 14:39:34 +0200 Subject: [PATCH 06/10] Fix typo in env tag --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 8eaf21e0..1b56400f 100644 --- a/options.go +++ b/options.go @@ -41,7 +41,7 @@ type Options struct { TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"` AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"` - KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_AUTHENTICATED_KEYCLOAK_GROUP"` + KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"` AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"` EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"` WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"` From a209a52df1e8462796918392308a56f5b5d959ff Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sat, 17 Aug 2019 15:50:37 -0500 Subject: [PATCH 07/10] More fully support X-Auth-Request-Redirect header Docs showed that the X-Auth-Request-Redirect header can specify a redirect URI, but only the rd POST parameter was being honored This fixes that. --- docs/configuration/configuration.md | 2 ++ oauthproxy.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 05cc2998..8a182ddb 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -247,6 +247,8 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_set_header X-Auth-Request-Redirect $request_uri; + # or, if you are handling multiple domains: + # proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; } location = /oauth2/auth { proxy_pass http://127.0.0.1:4180; diff --git a/oauthproxy.go b/oauthproxy.go index 2418e736..5af2e9cb 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -480,7 +480,10 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) return } - redirect = req.Form.Get("rd") + redirect = req.Header.Get("X-Auth-Request-Redirect") + if req.Form.Get("rd") != "" { + redirect = req.Form.Get("rd") + } if !p.IsValidRedirect(redirect) { redirect = req.URL.Path if strings.HasPrefix(redirect, p.ProxyPrefix) { From 18a77e66180161112d1eec7326304a849e552a17 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Thu, 19 Sep 2019 11:26:13 -0500 Subject: [PATCH 08/10] Reflect #248 PR in CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44cd0cf6..7a0feb86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ # v4.0.0 +- [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored + ## Release Highlights - Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/) - Health check logging can now be disabled for quieter logs From a122ac60e4862f405e1f7827e7b879d220798e17 Mon Sep 17 00:00:00 2001 From: Dan Bond Date: Wed, 25 Sep 2019 13:33:58 -0700 Subject: [PATCH 09/10] Fix CHANGELOG errors --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f16e752..2c48091b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Changes since v4.0.0 -- [#226](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka) +- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka) # v4.0.0 @@ -51,8 +51,6 @@ reconfigure their proxies. Please read the Breaking Changes below thoroughly. ## Changes since v3.2.0 -- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) -- [#209](https://github.com/pusher/outh2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) - [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger) - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) - [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv) From 513af9b714a1250aa9baa5526c80812c7685b796 Mon Sep 17 00:00:00 2001 From: T S Date: Tue, 1 Oct 2019 12:28:00 -0700 Subject: [PATCH 10/10] Escape original request URI in sample kubernetes ingress configuration The current sample configuration for kubernetes ingress demonstrates using the `auth-signin` annotation to redirect a user to oauth2_proxy's signin page. It constructs the link to do so by directly concatenating `$request_uri` as the `rd` parameter, so the sign-in page knows where to send the user after signin is complete. However, this does not work correctly if the original request URI contains multiple query parameters separated by an ampersand, as that ampersand is interpereted as separating query parameters of the `/oauth2/start` URI. For example: If the user requests a URL: https://example.com/foo?q1=v1&q2=v2 they may be redirected to the signin url https://example.com/oauth2/start?rd=https://example.com/foo?q1=v1&q2=v2 and after completing signin, oauth2_proxy will redirect them to https://example.com/foo?q1=v1 nginx-ingress added an $escaped_request_uri variable about a year ago, to help resolve this kind of issue (https://github.com/kubernetes/ingress-nginx/pull/2811) --- docs/configuration/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index db47e691..d30f85b5 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -310,7 +310,7 @@ If you use ingress-nginx in Kubernetes (which includes the Lua module), you also ```yaml nginx.ingress.kubernetes.io/auth-response-headers: Authorization -nginx.ingress.kubernetes.io/auth-signin: https://$host/oauth2/start?rd=$request_uri +nginx.ingress.kubernetes.io/auth-signin: https://$host/oauth2/start?rd=$escaped_request_uri nginx.ingress.kubernetes.io/auth-url: https://$host/oauth2/auth nginx.ingress.kubernetes.io/configuration-snippet: | auth_request_set $name_upstream_1 $upstream_cookie_name_1;