From 6eb56ed7ff5dfafab665e2fb5cd7e9ce90dfbb0c Mon Sep 17 00:00:00 2001 From: Lukasz Siudut Date: Wed, 18 Nov 2015 16:47:19 +0100 Subject: [PATCH 1/2] Add support for returning authenticated user As for now, if oauth2_proxy is used only as authenticator, it doesn't return information about authenticated user. This patch introduces header X-Authenticated-User which is returned from proxy upstream. This way further user-based authorization is possible. --- main.go | 1 + oauthproxy.go | 55 +++++++++++++++++++++++++--------------------- oauthproxy_test.go | 29 ++++++++++++++++++++++++ options.go | 42 ++++++++++++++++++----------------- 4 files changed, 82 insertions(+), 45 deletions(-) diff --git a/main.go b/main.go index a8d3f1b6f..96b8e39b1 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ func main() { flagSet.String("tls-key", "", "path to private key file") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path") + flagSet.Bool("return-authenticated-user", false, "return X-Authorized-User header (useful when used as authenticator only)") flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") diff --git a/oauthproxy.go b/oauthproxy.go index dd69d6a41..8ec97ba8c 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -51,20 +51,21 @@ type OAuthProxy struct { OAuthCallbackPath string AuthOnlyPath string - redirectURL *url.URL // the url to receive requests at - provider providers.Provider - ProxyPrefix string - SignInMessage string - HtpasswdFile *HtpasswdFile - DisplayHtpasswdForm bool - serveMux http.Handler - PassBasicAuth bool - BasicAuthPassword string - PassAccessToken bool - CookieCipher *cookie.Cipher - skipAuthRegex []string - compiledRegex []*regexp.Regexp - templates *template.Template + redirectURL *url.URL // the url to receive requests at + provider providers.Provider + ProxyPrefix string + SignInMessage string + HtpasswdFile *HtpasswdFile + DisplayHtpasswdForm bool + serveMux http.Handler + PassBasicAuth bool + ReturnAuthenticatedUser bool + BasicAuthPassword string + PassAccessToken bool + CookieCipher *cookie.Cipher + skipAuthRegex []string + compiledRegex []*regexp.Regexp + templates *template.Template } type UpstreamProxy struct { @@ -186,17 +187,18 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), - ProxyPrefix: opts.ProxyPrefix, - provider: opts.provider, - serveMux: serveMux, - redirectURL: redirectURL, - skipAuthRegex: opts.SkipAuthRegex, - compiledRegex: opts.CompiledRegex, - PassBasicAuth: opts.PassBasicAuth, - BasicAuthPassword: opts.BasicAuthPassword, - PassAccessToken: opts.PassAccessToken, - CookieCipher: cipher, - templates: loadTemplates(opts.CustomTemplatesDir), + ProxyPrefix: opts.ProxyPrefix, + provider: opts.provider, + serveMux: serveMux, + redirectURL: redirectURL, + skipAuthRegex: opts.SkipAuthRegex, + compiledRegex: opts.CompiledRegex, + PassBasicAuth: opts.PassBasicAuth, + ReturnAuthenticatedUser: opts.ReturnAuthenticatedUser, + BasicAuthPassword: opts.BasicAuthPassword, + PassAccessToken: opts.PassAccessToken, + CookieCipher: cipher, + templates: loadTemplates(opts.CustomTemplatesDir), } } @@ -593,6 +595,9 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int req.Header["X-Forwarded-Email"] = []string{session.Email} } } + if p.ReturnAuthenticatedUser { + rw.Header().Set("X-Authenticated-User", session.User) + } if p.PassAccessToken && session.AccessToken != "" { req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken} } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 7af1de18b..c681b114e 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -595,6 +595,35 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { assert.Equal(t, "unauthorized request\n", string(bodyBytes)) } +func TestAuthOnlyEndpointReturnAuthenticatedUser(t *testing.T) { + var pc_test ProcessCookieTest + + pc_test.opts = NewOptions() + pc_test.opts.ReturnAuthenticatedUser = true + pc_test.opts.Validate() + + pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool { + return pc_test.validate_user + }) + pc_test.proxy.provider = &TestProvider{ + ValidToken: true, + } + + pc_test.validate_user = true + + pc_test.rw = httptest.NewRecorder() + pc_test.req, _ = http.NewRequest("GET", + pc_test.opts.ProxyPrefix+"/auth", nil) + + startSession := &providers.SessionState{ + Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"} + pc_test.SaveSession(startSession, time.Now()) + + pc_test.proxy.ServeHTTP(pc_test.rw, pc_test.req) + assert.Equal(t, http.StatusAccepted, pc_test.rw.Code) + assert.Equal(t, "michael.bland", pc_test.rw.HeaderMap["X-Authenticated-User"][0]) +} + type SignatureAuthenticator struct { auth hmacauth.HmacAuth } diff --git a/options.go b/options.go index b64396cbc..c9dc6559a 100644 --- a/options.go +++ b/options.go @@ -43,12 +43,13 @@ type Options struct { CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure"` CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"` - Upstreams []string `flag:"upstream" cfg:"upstreams"` - SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` - PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"` - BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` - PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` - PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` + Upstreams []string `flag:"upstream" cfg:"upstreams"` + SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` + ReturnAuthenticatedUser bool `flag:"return-authenticated-user" cfg:"return_authenticated_user"` + PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"` + BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` + PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` + PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` // These options allow for other providers besides Google, with // potential overrides. @@ -79,20 +80,21 @@ type SignatureData struct { func NewOptions() *Options { return &Options{ - ProxyPrefix: "/oauth2", - HttpAddress: "127.0.0.1:4180", - HttpsAddress: ":443", - DisplayHtpasswdForm: true, - CookieName: "_oauth2_proxy", - CookieSecure: true, - CookieHttpOnly: true, - CookieExpire: time.Duration(168) * time.Hour, - CookieRefresh: time.Duration(0), - PassBasicAuth: true, - PassAccessToken: false, - PassHostHeader: true, - ApprovalPrompt: "force", - RequestLogging: true, + ProxyPrefix: "/oauth2", + HttpAddress: "127.0.0.1:4180", + HttpsAddress: ":443", + DisplayHtpasswdForm: true, + CookieName: "_oauth2_proxy", + CookieSecure: true, + CookieHttpOnly: true, + CookieExpire: time.Duration(168) * time.Hour, + CookieRefresh: time.Duration(0), + ReturnAuthenticatedUser: false, + PassBasicAuth: true, + PassAccessToken: false, + PassHostHeader: true, + ApprovalPrompt: "force", + RequestLogging: true, } } From f3a15aeb08d876ce8bc84882aa03df15006c5587 Mon Sep 17 00:00:00 2001 From: Lukasz Siudut Date: Wed, 23 Dec 2015 10:08:23 +0100 Subject: [PATCH 2/2] Return authenticated Email, as it's more reliable If email is present user name is forged from it, what may duplicate user names when multiple domains are allowed. --- main.go | 2 +- oauthproxy.go | 58 +++++++++++++++++++++++----------------------- oauthproxy_test.go | 6 ++--- options.go | 44 +++++++++++++++++------------------ 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/main.go b/main.go index 96b8e39b1..f27963c56 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ func main() { flagSet.String("tls-key", "", "path to private key file") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path") - flagSet.Bool("return-authenticated-user", false, "return X-Authorized-User header (useful when used as authenticator only)") + flagSet.Bool("return-authenticated-email", false, "return X-Authorized-Email header (useful when used as authenticator only)") flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") diff --git a/oauthproxy.go b/oauthproxy.go index 8ec97ba8c..c06201c9b 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -51,21 +51,21 @@ type OAuthProxy struct { OAuthCallbackPath string AuthOnlyPath string - redirectURL *url.URL // the url to receive requests at - provider providers.Provider - ProxyPrefix string - SignInMessage string - HtpasswdFile *HtpasswdFile - DisplayHtpasswdForm bool - serveMux http.Handler - PassBasicAuth bool - ReturnAuthenticatedUser bool - BasicAuthPassword string - PassAccessToken bool - CookieCipher *cookie.Cipher - skipAuthRegex []string - compiledRegex []*regexp.Regexp - templates *template.Template + redirectURL *url.URL // the url to receive requests at + provider providers.Provider + ProxyPrefix string + SignInMessage string + HtpasswdFile *HtpasswdFile + DisplayHtpasswdForm bool + serveMux http.Handler + PassBasicAuth bool + ReturnAuthenticatedEmail bool + BasicAuthPassword string + PassAccessToken bool + CookieCipher *cookie.Cipher + skipAuthRegex []string + compiledRegex []*regexp.Regexp + templates *template.Template } type UpstreamProxy struct { @@ -187,18 +187,18 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), - ProxyPrefix: opts.ProxyPrefix, - provider: opts.provider, - serveMux: serveMux, - redirectURL: redirectURL, - skipAuthRegex: opts.SkipAuthRegex, - compiledRegex: opts.CompiledRegex, - PassBasicAuth: opts.PassBasicAuth, - ReturnAuthenticatedUser: opts.ReturnAuthenticatedUser, - BasicAuthPassword: opts.BasicAuthPassword, - PassAccessToken: opts.PassAccessToken, - CookieCipher: cipher, - templates: loadTemplates(opts.CustomTemplatesDir), + ProxyPrefix: opts.ProxyPrefix, + provider: opts.provider, + serveMux: serveMux, + redirectURL: redirectURL, + skipAuthRegex: opts.SkipAuthRegex, + compiledRegex: opts.CompiledRegex, + PassBasicAuth: opts.PassBasicAuth, + ReturnAuthenticatedEmail: opts.ReturnAuthenticatedEmail, + BasicAuthPassword: opts.BasicAuthPassword, + PassAccessToken: opts.PassAccessToken, + CookieCipher: cipher, + templates: loadTemplates(opts.CustomTemplatesDir), } } @@ -595,8 +595,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int req.Header["X-Forwarded-Email"] = []string{session.Email} } } - if p.ReturnAuthenticatedUser { - rw.Header().Set("X-Authenticated-User", session.User) + if p.ReturnAuthenticatedEmail { + rw.Header().Set("X-Authenticated-Email", session.Email) } if p.PassAccessToken && session.AccessToken != "" { req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken} diff --git a/oauthproxy_test.go b/oauthproxy_test.go index c681b114e..a77eb13fe 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -595,11 +595,11 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { assert.Equal(t, "unauthorized request\n", string(bodyBytes)) } -func TestAuthOnlyEndpointReturnAuthenticatedUser(t *testing.T) { +func TestAuthOnlyEndpointReturnAuthenticatedEmail(t *testing.T) { var pc_test ProcessCookieTest pc_test.opts = NewOptions() - pc_test.opts.ReturnAuthenticatedUser = true + pc_test.opts.ReturnAuthenticatedEmail = true pc_test.opts.Validate() pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool { @@ -621,7 +621,7 @@ func TestAuthOnlyEndpointReturnAuthenticatedUser(t *testing.T) { pc_test.proxy.ServeHTTP(pc_test.rw, pc_test.req) assert.Equal(t, http.StatusAccepted, pc_test.rw.Code) - assert.Equal(t, "michael.bland", pc_test.rw.HeaderMap["X-Authenticated-User"][0]) + assert.Equal(t, "michael.bland@gsa.gov", pc_test.rw.HeaderMap["X-Authenticated-Email"][0]) } type SignatureAuthenticator struct { diff --git a/options.go b/options.go index c9dc6559a..e72f75c9a 100644 --- a/options.go +++ b/options.go @@ -43,13 +43,13 @@ type Options struct { CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure"` CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"` - Upstreams []string `flag:"upstream" cfg:"upstreams"` - SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` - ReturnAuthenticatedUser bool `flag:"return-authenticated-user" cfg:"return_authenticated_user"` - PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"` - BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` - PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` - PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` + Upstreams []string `flag:"upstream" cfg:"upstreams"` + SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` + ReturnAuthenticatedEmail bool `flag:"return-authenticated-email" cfg:"return_authenticated_email"` + PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"` + BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"` + PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` + PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"` // These options allow for other providers besides Google, with // potential overrides. @@ -80,21 +80,21 @@ type SignatureData struct { func NewOptions() *Options { return &Options{ - ProxyPrefix: "/oauth2", - HttpAddress: "127.0.0.1:4180", - HttpsAddress: ":443", - DisplayHtpasswdForm: true, - CookieName: "_oauth2_proxy", - CookieSecure: true, - CookieHttpOnly: true, - CookieExpire: time.Duration(168) * time.Hour, - CookieRefresh: time.Duration(0), - ReturnAuthenticatedUser: false, - PassBasicAuth: true, - PassAccessToken: false, - PassHostHeader: true, - ApprovalPrompt: "force", - RequestLogging: true, + ProxyPrefix: "/oauth2", + HttpAddress: "127.0.0.1:4180", + HttpsAddress: ":443", + DisplayHtpasswdForm: true, + CookieName: "_oauth2_proxy", + CookieSecure: true, + CookieHttpOnly: true, + CookieExpire: time.Duration(168) * time.Hour, + CookieRefresh: time.Duration(0), + ReturnAuthenticatedEmail: false, + PassBasicAuth: true, + PassAccessToken: false, + PassHostHeader: true, + ApprovalPrompt: "force", + RequestLogging: true, } }