mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-03-21 21:47:11 +02:00
Add option to remove tokens from cookie sessions (#673)
* Add option to remove tokens from cookie sessions * Move Minimal to be an option on CookieSession * Add sessionOptionsDefaults helper
This commit is contained in:
parent
a09eecc6a2
commit
bb5977095f
@ -11,6 +11,7 @@
|
||||
|
||||
## Changes since v6.0.0
|
||||
|
||||
- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
|
||||
- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
|
||||
- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
|
||||
- [#669](https://github.com/oauth2-proxy/oauth2-proxy/pull/669) Reduce docker context to improve build times (@JoelSpeed)
|
||||
|
@ -107,6 +107,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
|
||||
| `--resource` | string | The resource that is protected (Azure AD only) | |
|
||||
| `--reverse-proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted | false |
|
||||
| `--scope` | string | OAuth scope specification | |
|
||||
| `--session-cookie-minimal` | bool | strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only) | false |
|
||||
| `--session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie |
|
||||
| `--set-xauthrequest` | bool | set X-Auth-Request-User, X-Auth-Request-Email and X-Auth-Request-Preferred-Username response headers (useful in Nginx auth_request mode) | false |
|
||||
| `--set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false |
|
||||
|
@ -155,9 +155,7 @@ func NewOptions() *Options {
|
||||
ForceHTTPS: false,
|
||||
DisplayHtpasswdForm: true,
|
||||
Cookie: cookieDefaults(),
|
||||
Session: SessionOptions{
|
||||
Type: "cookie",
|
||||
},
|
||||
Session: sessionOptionsDefaults(),
|
||||
AzureTenant: "common",
|
||||
SetXAuthRequest: false,
|
||||
SkipAuthPreflight: false,
|
||||
@ -241,6 +239,7 @@ func NewFlagSet() *pflag.FlagSet {
|
||||
flagSet.String("ping-user-agent", "", "special User-Agent that will be used for basic health checks")
|
||||
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
|
||||
flagSet.String("session-store-type", "cookie", "the session storage provider to use")
|
||||
flagSet.Bool("session-cookie-minimal", false, "strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only)")
|
||||
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
|
||||
flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature")
|
||||
flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel")
|
||||
|
@ -3,6 +3,7 @@ package options
|
||||
// SessionOptions contains configuration options for the SessionStore providers.
|
||||
type SessionOptions struct {
|
||||
Type string `flag:"session-store-type" cfg:"session_store_type"`
|
||||
Cookie CookieStoreOptions `cfg:",squash"`
|
||||
Redis RedisStoreOptions `cfg:",squash"`
|
||||
}
|
||||
|
||||
@ -14,6 +15,11 @@ var CookieSessionStoreType = "cookie"
|
||||
// used for storing sessions.
|
||||
var RedisSessionStoreType = "redis"
|
||||
|
||||
// CookieStoreOptions contains configuration options for the CookieSessionStore.
|
||||
type CookieStoreOptions struct {
|
||||
Minimal bool `flag:"session-cookie-minimal" cfg:"session_cookie_minimal"`
|
||||
}
|
||||
|
||||
// RedisStoreOptions contains configuration options for the RedisSessionStore.
|
||||
type RedisStoreOptions struct {
|
||||
ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"`
|
||||
@ -25,3 +31,12 @@ type RedisStoreOptions struct {
|
||||
CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path"`
|
||||
InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"`
|
||||
}
|
||||
|
||||
func sessionOptionsDefaults() SessionOptions {
|
||||
return SessionOptions{
|
||||
Type: CookieSessionStoreType,
|
||||
Cookie: CookieStoreOptions{
|
||||
Minimal: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ var _ sessions.SessionStore = &SessionStore{}
|
||||
type SessionStore struct {
|
||||
Cookie *options.Cookie
|
||||
CookieCipher encryption.Cipher
|
||||
Minimal bool
|
||||
}
|
||||
|
||||
// Save takes a sessions.SessionState and stores the information from it
|
||||
@ -39,7 +40,7 @@ func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessi
|
||||
now := time.Now()
|
||||
ss.CreatedAt = &now
|
||||
}
|
||||
value, err := cookieForSession(ss, s.CookieCipher)
|
||||
value, err := s.cookieForSession(ss)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -85,8 +86,17 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||
}
|
||||
|
||||
// cookieForSession serializes a session state for storage in a cookie
|
||||
func cookieForSession(s *sessions.SessionState, c encryption.Cipher) ([]byte, error) {
|
||||
return s.EncodeSessionState(c, true)
|
||||
func (s *SessionStore) cookieForSession(ss *sessions.SessionState) ([]byte, error) {
|
||||
if s.Minimal && (ss.AccessToken != "" || ss.IDToken != "" || ss.RefreshToken != "") {
|
||||
minimal := *ss
|
||||
minimal.AccessToken = ""
|
||||
minimal.IDToken = ""
|
||||
minimal.RefreshToken = ""
|
||||
|
||||
return minimal.EncodeSessionState(s.CookieCipher, true)
|
||||
}
|
||||
|
||||
return ss.EncodeSessionState(s.CookieCipher, true)
|
||||
}
|
||||
|
||||
// sessionFromCookie deserializes a session from a cookie value
|
||||
@ -146,6 +156,7 @@ func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.Coo
|
||||
return &SessionStore{
|
||||
CookieCipher: cipher,
|
||||
Cookie: cookieOpts,
|
||||
Minimal: opts.Cookie.Minimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
// are of the correct format
|
||||
func Validate(o *options.Options) error {
|
||||
msgs := validateCookie(o.Cookie)
|
||||
msgs = append(msgs, validateSessionCookieMinimal(o)...)
|
||||
|
||||
if o.SSLInsecureSkipVerify {
|
||||
insecureTransport := &http.Transport{
|
||||
|
32
pkg/validation/sessions.go
Normal file
32
pkg/validation/sessions.go
Normal file
@ -0,0 +1,32 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
)
|
||||
|
||||
func validateSessionCookieMinimal(o *options.Options) []string {
|
||||
if !o.Session.Cookie.Minimal {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
msgs := []string{}
|
||||
if o.PassAuthorization {
|
||||
msgs = append(msgs,
|
||||
"pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
||||
}
|
||||
if o.SetAuthorization {
|
||||
msgs = append(msgs,
|
||||
"set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
||||
}
|
||||
if o.PassAccessToken {
|
||||
msgs = append(msgs,
|
||||
"pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
||||
}
|
||||
if o.Cookie.Refresh != time.Duration(0) {
|
||||
msgs = append(msgs,
|
||||
"cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
||||
}
|
||||
return msgs
|
||||
}
|
121
pkg/validation/sessions_test.go
Normal file
121
pkg/validation/sessions_test.go
Normal file
@ -0,0 +1,121 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func Test_validateSessionCookieMinimal(t *testing.T) {
|
||||
const (
|
||||
passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||
setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||
passAccessTokenMsg = "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||
cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||
)
|
||||
|
||||
testCases := map[string]struct {
|
||||
opts *options.Options
|
||||
errStrings []string
|
||||
}{
|
||||
"No minimal cookie session": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
errStrings: []string{},
|
||||
},
|
||||
"No minimal cookie session & passAuthorization": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: false,
|
||||
},
|
||||
},
|
||||
PassAuthorization: true,
|
||||
},
|
||||
errStrings: []string{},
|
||||
},
|
||||
"Minimal cookie session no conflicts": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
errStrings: []string{},
|
||||
},
|
||||
"PassAuthorization conflict": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
PassAuthorization: true,
|
||||
},
|
||||
errStrings: []string{passAuthorizationMsg},
|
||||
},
|
||||
"SetAuthorization conflict": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
SetAuthorization: true,
|
||||
},
|
||||
errStrings: []string{setAuthorizationMsg},
|
||||
},
|
||||
"PassAccessToken conflict": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
PassAccessToken: true,
|
||||
},
|
||||
errStrings: []string{passAccessTokenMsg},
|
||||
},
|
||||
"CookieRefresh conflict": {
|
||||
opts: &options.Options{
|
||||
Cookie: options.Cookie{
|
||||
Refresh: time.Hour,
|
||||
},
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
errStrings: []string{cookieRefreshMsg},
|
||||
},
|
||||
"Multiple conflicts": {
|
||||
opts: &options.Options{
|
||||
Session: options.SessionOptions{
|
||||
Cookie: options.CookieStoreOptions{
|
||||
Minimal: true,
|
||||
},
|
||||
},
|
||||
PassAuthorization: true,
|
||||
PassAccessToken: true,
|
||||
},
|
||||
errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, tc := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
errStrings := validateSessionCookieMinimal(tc.opts)
|
||||
g := NewWithT(t)
|
||||
g.Expect(errStrings).To(ConsistOf(tc.errStrings))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user