Merge branch 'master' into verified

This commit is contained in:
Joel Speed 2019-04-11 13:49:56 +01:00 committed by GitHub
commit d00e3bddf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 230 additions and 19 deletions

View File

@ -3,10 +3,13 @@
## Changes since v3.1.0 ## Changes since v3.1.0
- [#96](https://github.com/bitly/oauth2_proxy/pull/96) Check if email is verified on GitHub (@caarlos0) - [#96](https://github.com/bitly/oauth2_proxy/pull/96) Check if email is verified on GitHub (@caarlos0)
- [#110](https://github.com/pusher/oauth2_proxy/pull/110) Added GCP healthcheck option (@timothy-spencer)
- [#112](https://github.com/pusher/oauth2_proxy/pull/112) Improve websocket support (@gyson)
- [#63](https://github.com/pusher/oauth2_proxy/pull/63) Use encoding/json for SessionState serialization (@yaegashi) - [#63](https://github.com/pusher/oauth2_proxy/pull/63) Use encoding/json for SessionState serialization (@yaegashi)
- Use JSON to encode session state to be stored in browser cookies - Use JSON to encode session state to be stored in browser cookies
- Implement legacy decode function to support existing cookies generated by older versions - Implement legacy decode function to support existing cookies generated by older versions
- Add detailed table driven tests in session_state_test.go - Add detailed table driven tests in session_state_test.go
- [#120](https://github.com/pusher/oauth2_proxy/pull/120) Encrypting user/email from cookie (@costelmoraru)
- [#55](https://github.com/pusher/oauth2_proxy/pull/55) Added login.gov provider (@timothy-spencer) - [#55](https://github.com/pusher/oauth2_proxy/pull/55) Added login.gov provider (@timothy-spencer)
- [#55](https://github.com/pusher/oauth2_proxy/pull/55) Added environment variables for all config options (@timothy-spencer) - [#55](https://github.com/pusher/oauth2_proxy/pull/55) Added environment variables for all config options (@timothy-spencer)
- [#70](https://github.com/pusher/oauth2_proxy/pull/70) Fix handling of splitted cookies (@einfachchr) - [#70](https://github.com/pusher/oauth2_proxy/pull/70) Fix handling of splitted cookies (@einfachchr)
@ -17,6 +20,7 @@
- [#41](https://github.com/pusher/oauth2_proxy/pull/41) Added option to manually specify OIDC endpoints instead of relying on discovery - [#41](https://github.com/pusher/oauth2_proxy/pull/41) Added option to manually specify OIDC endpoints instead of relying on discovery
- [#83](https://github.com/pusher/oauth2_proxy/pull/83) Add `id_token` refresh to Google provider (@leki75) - [#83](https://github.com/pusher/oauth2_proxy/pull/83) Add `id_token` refresh to Google provider (@leki75)
- [#10](https://github.com/pusher/oauth2_proxy/pull/10) fix redirect url param handling (@dt-rush) - [#10](https://github.com/pusher/oauth2_proxy/pull/10) fix redirect url param handling (@dt-rush)
- [#122](https://github.com/pusher/oauth2_proxy/pull/122) Expose -cookie-path as configuration parameter (@costelmoraru)
# v3.1.0 # v3.1.0

View File

@ -48,7 +48,7 @@ Valid providers are :
- [GitHub](#github-auth-provider) - [GitHub](#github-auth-provider)
- [GitLab](#gitlab-auth-provider) - [GitLab](#gitlab-auth-provider)
- [LinkedIn](#linkedin-auth-provider) - [LinkedIn](#linkedin-auth-provider)
- [login.gov](#login.gov-provider) - [login.gov](#logingov-provider)
The provider can be selected using the `provider` configuration value. The provider can be selected using the `provider` configuration value.
@ -172,12 +172,12 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma
login.gov is an OIDC provider for the US Government. login.gov is an OIDC provider for the US Government.
If you are a US Government agency, you can contact the login.gov team through the contact information If you are a US Government agency, you can contact the login.gov team through the contact information
that you can find on https://login.gov/developers/ and work with them to understand how to get login.gov that you can find on https://login.gov/developers/ and work with them to understand how to get login.gov
accounts for integration/test and production access. accounts for integration/test and production access.
A developer guide is available here: https://developers.login.gov/, though this proxy handles everything A developer guide is available here: https://developers.login.gov/, though this proxy handles everything
but the data you need to create to register your application in the login.gov dashboard. but the data you need to create to register your application in the login.gov dashboard.
As a demo, we will assume that you are running your application that you want to secure locally on As a demo, we will assume that you are running your application that you want to secure locally on
http://localhost:3000/, that you will be starting your proxy up on http://localhost:4180/, and that http://localhost:3000/, that you will be starting your proxy up on http://localhost:4180/, and that
you have an agency integration account for testing. you have an agency integration account for testing.
@ -261,6 +261,7 @@ Usage of oauth2_proxy:
-client-secret string: the OAuth Client Secret -client-secret string: the OAuth Client Secret
-config string: path to config file -config string: path to config file
-cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com) -cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com)
-cookie-path string: an optional cookie path to force cookies to (ie: /foo)
-cookie-expire duration: expire timeframe for cookie (default 168h0m0s) -cookie-expire duration: expire timeframe for cookie (default 168h0m0s)
-cookie-httponly: set HttpOnly cookie flag (default true) -cookie-httponly: set HttpOnly cookie flag (default true)
-cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy") -cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy")
@ -272,6 +273,7 @@ Usage of oauth2_proxy:
-email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
-flush-interval: period between flushing response buffers when streaming responses (default "1s") -flush-interval: period between flushing response buffers when streaming responses (default "1s")
-footer string: custom footer string. Use "-" to disable default footer. -footer string: custom footer string. Use "-" to disable default footer.
-gcp-healthchecks: will enable /liveness_check, /readiness_check, and / (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses (default false)
-github-org string: restrict logins to members of this organisation -github-org string: restrict logins to members of this organisation
-github-team string: restrict logins to members of any of these teams (slug), separated by a comma -github-team string: restrict logins to members of any of these teams (slug), separated by a comma
-google-admin-email string: the google admin to impersonate for api calls -google-admin-email string: the google admin to impersonate for api calls
@ -335,6 +337,7 @@ The following environment variables can be used in place of the corresponding co
- `OAUTH2_PROXY_COOKIE_NAME` - `OAUTH2_PROXY_COOKIE_NAME`
- `OAUTH2_PROXY_COOKIE_SECRET` - `OAUTH2_PROXY_COOKIE_SECRET`
- `OAUTH2_PROXY_COOKIE_DOMAIN` - `OAUTH2_PROXY_COOKIE_DOMAIN`
- `OAUTH2_PROXY_COOKIE_PATH`
- `OAUTH2_PROXY_COOKIE_EXPIRE` - `OAUTH2_PROXY_COOKIE_EXPIRE`
- `OAUTH2_PROXY_COOKIE_REFRESH` - `OAUTH2_PROXY_COOKIE_REFRESH`
- `OAUTH2_PROXY_SIGNATURE_KEY` - `OAUTH2_PROXY_SIGNATURE_KEY`
@ -411,7 +414,7 @@ The command line to run `oauth2_proxy` in this configuration would look like thi
OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable. OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable.
- /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info - /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info
- /ping - returns a 200 OK response, which is intended for use with health checks - /ping - returns a 200 OK response, which is intended for use with health checks
- /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies) - /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
- /oauth2/start - a URL that will redirect to start the OAuth cycle - /oauth2/start - a URL that will redirect to start the OAuth cycle
- /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url. - /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url.
@ -505,7 +508,7 @@ server {
auth_request_set $auth_cookie $upstream_http_set_cookie; auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie; add_header Set-Cookie $auth_cookie;
# When using the --set-authorization flag, some provider's cookies can exceed the 4kb # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
# limit and so the OAuth2 Proxy splits these into multiple parts. # limit and so the OAuth2 Proxy splits these into multiple parts.
# Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response, # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
# so if your cookies are larger than 4kb, you will need to extract additional cookies manually. # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.

39
http.go
View File

@ -24,6 +24,45 @@ func (s *Server) ListenAndServe() {
} }
} }
// Used with gcpHealthcheck()
const userAgentHeader = "User-Agent"
const googleHealthCheckUserAgent = "GoogleHC/1.0"
const rootPath = "/"
// gcpHealthcheck handles healthcheck queries from GCP.
func gcpHealthcheck(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check for liveness and readiness: used for Google App Engine
if r.URL.EscapedPath() == "/liveness_check" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
return
}
if r.URL.EscapedPath() == "/readiness_check" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
return
}
// Check for GKE ingress healthcheck: The ingress requires the root
// path of the target to return a 200 (OK) to indicate the service's good health. This can be quite a challenging demand
// depending on the application's path structure. This middleware filters out the requests from the health check by
//
// 1. checking that the request path is indeed the root path
// 2. ensuring that the User-Agent is "GoogleHC/1.0", the health checker
// 3. ensuring the request method is "GET"
if r.URL.Path == rootPath &&
r.Header.Get(userAgentHeader) == googleHealthCheckUserAgent &&
r.Method == http.MethodGet {
w.WriteHeader(http.StatusOK)
return
}
h.ServeHTTP(w, r)
})
}
// ServeHTTP constructs a net.Listener and starts handling HTTP requests // ServeHTTP constructs a net.Listener and starts handling HTTP requests
func (s *Server) ServeHTTP() { func (s *Server) ServeHTTP() {
HTTPAddress := s.Opts.HTTPAddress HTTPAddress := s.Opts.HTTPAddress

105
http_test.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGCPHealthcheckLiveness(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/liveness_check", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
h.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "OK", rw.Body.String())
}
func TestGCPHealthcheckReadiness(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/readiness_check", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
h.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "OK", rw.Body.String())
}
func TestGCPHealthcheckNotHealthcheck(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/not_any_check", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
h.ServeHTTP(rw, r)
assert.Equal(t, "test", rw.Body.String())
}
func TestGCPHealthcheckIngress(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "", rw.Body.String())
}
func TestGCPHealthcheckNotIngress(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/foo", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r)
assert.Equal(t, "test", rw.Body.String())
}
func TestGCPHealthcheckNotIngressPut(t *testing.T) {
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := gcpHealthcheck(http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("PUT", "/", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r)
assert.Equal(t, "test", rw.Body.String())
}

View File

@ -4,6 +4,8 @@
package main package main
import ( import (
"bufio"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -32,6 +34,14 @@ func (l *responseLogger) Header() http.Header {
return l.w.Header() return l.w.Header()
} }
// Support Websocket
func (l *responseLogger) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
if hj, ok := l.w.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, errors.New("http.Hijacker is not available on writer")
}
// ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's // ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's
// Header // Header
func (l *responseLogger) ExtractGAPMetadata() { func (l *responseLogger) ExtractGAPMetadata() {

View File

@ -24,6 +24,11 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
for _, test := range tests { for _, test := range tests {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
handler := func(w http.ResponseWriter, req *http.Request) { handler := func(w http.ResponseWriter, req *http.Request) {
_, ok := w.(http.Hijacker)
if !ok {
t.Error("http.Hijacker is not available")
}
w.Write([]byte("test")) w.Write([]byte("test"))
} }

11
main.go
View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"net/http"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -68,6 +69,7 @@ func main() {
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates") flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)") flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*")
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable") flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
@ -92,6 +94,7 @@ func main() {
flagSet.String("acr-values", "http://idmanagement.gov/ns/assurance/loa/1", "acr values string: optional, used by login.gov") flagSet.String("acr-values", "http://idmanagement.gov/ns/assurance/loa/1", "acr values string: optional, used by login.gov")
flagSet.String("jwt-key", "", "private key used to sign JWT: required by login.gov") flagSet.String("jwt-key", "", "private key used to sign JWT: required by login.gov")
flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov") flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov")
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
flagSet.Parse(os.Args[1:]) flagSet.Parse(os.Args[1:])
@ -139,8 +142,14 @@ func main() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
var handler http.Handler
if opts.GCPHealthChecks {
handler = gcpHealthcheck(LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat))
} else {
handler = LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat)
}
s := &Server{ s := &Server{
Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat), Handler: handler,
Opts: opts, Opts: opts,
} }
s.ListenAndServe() s.ListenAndServe()

View File

@ -56,6 +56,7 @@ type OAuthProxy struct {
CookieName string CookieName string
CSRFCookieName string CSRFCookieName string
CookieDomain string CookieDomain string
CookiePath string
CookieSecure bool CookieSecure bool
CookieHTTPOnly bool CookieHTTPOnly bool
CookieExpire time.Duration CookieExpire time.Duration
@ -110,7 +111,7 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth")) r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
u.auth.SignRequest(r) u.auth.SignRequest(r)
} }
if u.wsHandler != nil && r.Header.Get("Connection") == "Upgrade" && r.Header.Get("Upgrade") == "websocket" { if u.wsHandler != nil && strings.ToLower(r.Header.Get("Connection")) == "upgrade" && r.Header.Get("Upgrade") == "websocket" {
u.wsHandler.ServeHTTP(w, r) u.wsHandler.ServeHTTP(w, r)
} else { } else {
u.handler.ServeHTTP(w, r) u.handler.ServeHTTP(w, r)
@ -214,7 +215,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
refresh = fmt.Sprintf("after %s", opts.CookieRefresh) refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
} }
log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, refresh) log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
var cipher *cookie.Cipher var cipher *cookie.Cipher
if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) { if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
@ -230,6 +231,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"), CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
CookieSeed: opts.CookieSecret, CookieSeed: opts.CookieSecret,
CookieDomain: opts.CookieDomain, CookieDomain: opts.CookieDomain,
CookiePath: opts.CookiePath,
CookieSecure: opts.CookieSecure, CookieSecure: opts.CookieSecure,
CookieHTTPOnly: opts.CookieHTTPOnly, CookieHTTPOnly: opts.CookieHTTPOnly,
CookieExpire: opts.CookieExpire, CookieExpire: opts.CookieExpire,
@ -430,7 +432,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
return &http.Cookie{ return &http.Cookie{
Name: name, Name: name,
Value: value, Value: value,
Path: "/", Path: p.CookiePath,
Domain: p.CookieDomain, Domain: p.CookieDomain,
HttpOnly: p.CookieHTTPOnly, HttpOnly: p.CookieHTTPOnly,
Secure: p.CookieSecure, Secure: p.CookieSecure,

View File

@ -49,6 +49,7 @@ type Options struct {
CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"` CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"` CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"` CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
CookiePath string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"` CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"` CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"` CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
@ -86,10 +87,11 @@ type Options struct {
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"` SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"` AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"` JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"` PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
// internal values that are set after config validation // internal values that are set after config validation
redirectURL *url.URL redirectURL *url.URL

View File

@ -268,3 +268,9 @@ func TestSkipOIDCDiscovery(t *testing.T) {
assert.Equal(t, nil, o.Validate()) assert.Equal(t, nil, o.Validate())
} }
func TestGCPHealthcheck(t *testing.T) {
o := testOptions()
o.GCPHealthChecks = true
assert.Equal(t, nil, o.Validate())
}

View File

@ -62,6 +62,18 @@ func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
} else { } else {
ss = *s ss = *s
var err error var err error
if ss.Email != "" {
ss.Email, err = c.Encrypt(ss.Email)
if err != nil {
return "", err
}
}
if ss.User != "" {
ss.User, err = c.Encrypt(ss.User)
if err != nil {
return "", err
}
}
if ss.AccessToken != "" { if ss.AccessToken != "" {
ss.AccessToken, err = c.Encrypt(ss.AccessToken) ss.AccessToken, err = c.Encrypt(ss.AccessToken)
if err != nil { if err != nil {
@ -172,6 +184,20 @@ func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
User: ss.User, User: ss.User,
} }
} else { } else {
// Backward compatibility with using unecrypted Email
if ss.Email != "" {
decryptedEmail, errEmail := c.Decrypt(ss.Email)
if errEmail == nil {
ss.Email = decryptedEmail
}
}
// Backward compatibility with using unecrypted User
if ss.User != "" {
decryptedUser, errUser := c.Decrypt(ss.User)
if errUser == nil {
ss.User = decryptedUser
}
}
if ss.AccessToken != "" { if ss.AccessToken != "" {
ss.AccessToken, err = c.Decrypt(ss.AccessToken) ss.AccessToken, err = c.Decrypt(ss.AccessToken)
if err != nil { if err != nil {

View File

@ -41,8 +41,8 @@ func TestSessionStateSerialization(t *testing.T) {
ss, err = DecodeSessionState(encoded, c2) ss, err = DecodeSessionState(encoded, c2)
t.Logf("%#v", ss) t.Logf("%#v", ss)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "user", ss.User) assert.NotEqual(t, "user", ss.User)
assert.Equal(t, s.Email, ss.Email) assert.NotEqual(t, s.Email, ss.Email)
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
assert.NotEqual(t, s.AccessToken, ss.AccessToken) assert.NotEqual(t, s.AccessToken, ss.AccessToken)
assert.NotEqual(t, s.IDToken, ss.IDToken) assert.NotEqual(t, s.IDToken, ss.IDToken)
@ -77,8 +77,8 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
ss, err = DecodeSessionState(encoded, c2) ss, err = DecodeSessionState(encoded, c2)
t.Logf("%#v", ss) t.Logf("%#v", ss)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, s.User, ss.User) assert.NotEqual(t, s.User, ss.User)
assert.Equal(t, s.Email, ss.Email) assert.NotEqual(t, s.Email, ss.Email)
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix()) assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
assert.NotEqual(t, s.AccessToken, ss.AccessToken) assert.NotEqual(t, s.AccessToken, ss.AccessToken)
assert.NotEqual(t, s.RefreshToken, ss.RefreshToken) assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
@ -229,7 +229,7 @@ func TestDecodeSessionState(t *testing.T) {
ExpiresOn: e, ExpiresOn: e,
RefreshToken: "refresh4321", RefreshToken: "refresh4321",
}, },
Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString), Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString),
Cipher: c, Cipher: c,
}, },
{ {
@ -237,7 +237,7 @@ func TestDecodeSessionState(t *testing.T) {
Email: "user@domain.com", Email: "user@domain.com",
User: "just-user", User: "just-user",
}, },
Encoded: `{"Email":"user@domain.com","User":"just-user"}`, Encoded: `{"Email":"EGTllJcOFC16b7LBYzLekaHAC5SMMSPdyUrg8hd25g==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw=="}`,
Cipher: c, Cipher: c,
}, },
{ {