diff --git a/README.md b/README.md index 3c0f98fe9..bff52747e 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ Usage of oauth2_proxy: -authenticated-emails-file="": authenticate against emails via file (one per line) -azure-tenant="common": go to a tenant-specific or common (tenant-independent) endpoint. -basic-auth-password="": the password to set when passing the HTTP Basic Auth header + -basic-auth-username="": the username to set when passing the HTTP Basic Auth header -client-id="": the OAuth Client ID: ie: "123456.apps.googleusercontent.com" -client-secret="": the OAuth Client Secret -config="": path to config file diff --git a/main.go b/main.go index dd9a100e8..80c422796 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ func main() { 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("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.String("basic-auth-username", "", "the username to set when passing the HTTP Basic Auth header, overrides session user") flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream") flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)") diff --git a/oauthproxy.go b/oauthproxy.go index 16adf2249..584630d7d 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -60,6 +60,7 @@ type OAuthProxy struct { serveMux http.Handler PassBasicAuth bool BasicAuthPassword string + BasicAuthUsername string PassAccessToken bool CookieCipher *cookie.Cipher skipAuthRegex []string @@ -194,6 +195,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { compiledRegex: opts.CompiledRegex, PassBasicAuth: opts.PassBasicAuth, BasicAuthPassword: opts.BasicAuthPassword, + BasicAuthUsername: opts.BasicAuthUsername, PassAccessToken: opts.PassAccessToken, CookieCipher: cipher, templates: loadTemplates(opts.CustomTemplatesDir), @@ -587,6 +589,9 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int // At this point, the user is authenticated. proxy normally if p.PassBasicAuth { + if p.BasicAuthUsername != "" { + session.User = p.BasicAuthUsername + } req.SetBasicAuth(session.User, p.BasicAuthPassword) req.Header["X-Forwarded-User"] = []string{session.User} if session.Email != "" { diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 7af1de18b..5c988d47b 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -203,6 +203,80 @@ func TestBasicAuthPassword(t *testing.T) { provider_server.Close() } +func TestBasicAuthUsername(t *testing.T) { + provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%#v", r) + url := r.URL + payload := "" + switch url.Path { + case "/oauth/token": + payload = `{"access_token": "my_auth_token"}` + default: + payload = r.Header.Get("Authorization") + if payload == "" { + payload = "No Authorization header found." + } + } + w.WriteHeader(200) + w.Write([]byte(payload)) + })) + opts := NewOptions() + opts.Upstreams = append(opts.Upstreams, provider_server.URL) + // The CookieSecret must be 32 bytes in order to create the AES + // cipher. + opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" + opts.ClientID = "bazquux" + opts.ClientSecret = "foobar" + opts.CookieSecure = false + opts.PassBasicAuth = true + opts.BasicAuthPassword = "This is a secure password" + opts.BasicAuthUsername = "michael" + opts.Validate() + + provider_url, _ := url.Parse(provider_server.URL) + const email_address = "michael.bland@gsa.gov" + const user_name = "michael" + + opts.provider = NewTestProvider(provider_url, email_address) + proxy := NewOAuthProxy(opts, func(email string) bool { + return email == email_address + }) + + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code", + strings.NewReader("")) + proxy.ServeHTTP(rw, req) + cookie := rw.HeaderMap["Set-Cookie"][0] + + cookieName := proxy.CookieName + var value string + key_prefix := cookieName + "=" + + for _, field := range strings.Split(cookie, "; ") { + value = strings.TrimPrefix(field, key_prefix) + if value != field { + break + } else { + value = "" + } + } + + req, _ = http.NewRequest("GET", "/", strings.NewReader("")) + req.AddCookie(&http.Cookie{ + Name: cookieName, + Value: value, + Path: "/", + Expires: time.Now().Add(time.Duration(24)), + HttpOnly: true, + }) + + rw = httptest.NewRecorder() + proxy.ServeHTTP(rw, req) + expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(user_name+":"+opts.BasicAuthPassword)) + assert.Equal(t, expectedHeader, rw.Body.String()) + provider_server.Close() +} + type PassAccessTokenTest struct { provider_server *httptest.Server proxy *OAuthProxy diff --git a/options.go b/options.go index 5d4c86f01..f56b3751d 100644 --- a/options.go +++ b/options.go @@ -48,6 +48,7 @@ type Options struct { 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"` + BasicAuthUsername string `flag:"basic-auth-username" cfg:"basic_auth_username"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"` PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`