git.iko.soy/typeable/oauth2-proxy
on master help sign in
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

commitbb5977095faf0a033a5e01c67597641a7c6db38b authorNick Meves <nick.meves@greenhouse.io> dateTue 14 Jul 2020, 15:02 (6 years ago) parenta09eecc
8 files changed+199−18
CHANGELOG.md+1 docs/configuration/configuration.md+1 pkg/apis/options/options.go+12−13 pkg/apis/options/sessions.go+17−2 pkg/sessions/cookie/session_store.go+14−3 pkg/validation/options.go+1 pkg/validation/sessions.go+32 pkg/validation/sessions_test.go+121
CHANGELOG.md+1
@@ -11,6 +11,7 @@
1111
1212## Changes since v6.0.0
1313
14+- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
1415- [#632](https://github.com/oauth2-proxy/oauth2-proxy/pull/632) Reduce session size by encoding with MessagePack and using LZ4 compression (@NickMeves)
1516- [#675](https://github.com/oauth2-proxy/oauth2-proxy/pull/675) Fix required ruby version and deprecated option for building docs (@mkontani)
1617- [#669](https://github.com/oauth2-proxy/oauth2-proxy/pull/669) Reduce docker context to improve build times (@JoelSpeed)
docs/configuration/configuration.md+1
@@ -107,6 +107,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
107107| `--resource` | string | The resource that is protected (Azure AD only) | |
108108| `--reverse-proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted | false |
109109| `--scope` | string | OAuth scope specification | |
110+| `--session-cookie-minimal` | bool | strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only) | false |
110111| `--session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie |
111112| `--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 |
112113| `--set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false |
pkg/apis/options/options.go+12−13
@@ -145,19 +145,17 @@ func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClie
145145// NewOptions constructs a new Options with defaulted values
146146func NewOptions() *Options {
147147 return &Options{
148− ProxyPrefix: "/oauth2",
149− ProviderType: "google",
150− PingPath: "/ping",
151− ProxyWebSockets: true,
152− HTTPAddress: "127.0.0.1:4180",
153− HTTPSAddress: ":443",
154− RealClientIPHeader: "X-Real-IP",
155− ForceHTTPS: false,
156− DisplayHtpasswdForm: true,
157− Cookie: cookieDefaults(),
158− Session: SessionOptions{
159− Type: "cookie",
160− },
148+ ProxyPrefix: "/oauth2",
149+ ProviderType: "google",
150+ PingPath: "/ping",
151+ ProxyWebSockets: true,
152+ HTTPAddress: "127.0.0.1:4180",
153+ HTTPSAddress: ":443",
154+ RealClientIPHeader: "X-Real-IP",
155+ ForceHTTPS: false,
156+ DisplayHtpasswdForm: true,
157+ Cookie: cookieDefaults(),
158+ Session: sessionOptionsDefaults(),
161159 AzureTenant: "common",
162160 SetXAuthRequest: false,
163161 SkipAuthPreflight: false,
@@ -241,6 +239,7 @@ func NewFlagSet() *pflag.FlagSet {
241239 flagSet.String("ping-user-agent", "", "special User-Agent that will be used for basic health checks")
242240 flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
243241 flagSet.String("session-store-type", "cookie", "the session storage provider to use")
242+ flagSet.Bool("session-cookie-minimal", false, "strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only)")
244243 flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
245244 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")
246245 flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel")
pkg/apis/options/sessions.go+17−2
@@ -2,8 +2,9 @@ package options
22
33// SessionOptions contains configuration options for the SessionStore providers.
44type SessionOptions struct {
5− Type string `flag:"session-store-type" cfg:"session_store_type"`
6− Redis RedisStoreOptions `cfg:",squash"`
5+ Type string `flag:"session-store-type" cfg:"session_store_type"`
6+ Cookie CookieStoreOptions `cfg:",squash"`
7+ Redis RedisStoreOptions `cfg:",squash"`
78}
89
910// CookieSessionStoreType is used to indicate the CookieSessionStore should be
@@ -14,6 +15,11 @@ var CookieSessionStoreType = "cookie"
1415// used for storing sessions.
1516var RedisSessionStoreType = "redis"
1617
18+// CookieStoreOptions contains configuration options for the CookieSessionStore.
19+type CookieStoreOptions struct {
20+ Minimal bool `flag:"session-cookie-minimal" cfg:"session_cookie_minimal"`
21+}
22+
1723// RedisStoreOptions contains configuration options for the RedisSessionStore.
1824type RedisStoreOptions struct {
1925 ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"`
@@ -25,3 +31,12 @@ type RedisStoreOptions struct {
2531 CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path"`
2632 InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"`
2733}
34+
35+func sessionOptionsDefaults() SessionOptions {
36+ return SessionOptions{
37+ Type: CookieSessionStoreType,
38+ Cookie: CookieStoreOptions{
39+ Minimal: false,
40+ },
41+ }
42+}
pkg/sessions/cookie/session_store.go+14−3
@@ -30,6 +30,7 @@ var _ sessions.SessionStore = &SessionStore{}
3030type SessionStore struct {
3131 Cookie *options.Cookie
3232 CookieCipher encryption.Cipher
33+ Minimal bool
3334}
3435
3536// 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
3940 now := time.Now()
4041 ss.CreatedAt = &now
4142 }
42− value, err := cookieForSession(ss, s.CookieCipher)
43+ value, err := s.cookieForSession(ss)
4344 if err != nil {
4445 return err
4546 }
@@ -85,8 +86,17 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
8586}
8687
8788// cookieForSession serializes a session state for storage in a cookie
88−func cookieForSession(s *sessions.SessionState, c encryption.Cipher) ([]byte, error) {
89− return s.EncodeSessionState(c, true)
89+func (s *SessionStore) cookieForSession(ss *sessions.SessionState) ([]byte, error) {
90+ if s.Minimal && (ss.AccessToken != "" || ss.IDToken != "" || ss.RefreshToken != "") {
91+ minimal := *ss
92+ minimal.AccessToken = ""
93+ minimal.IDToken = ""
94+ minimal.RefreshToken = ""
95+
96+ return minimal.EncodeSessionState(s.CookieCipher, true)
97+ }
98+
99+ return ss.EncodeSessionState(s.CookieCipher, true)
90100}
91101
92102// sessionFromCookie deserializes a session from a cookie value
@@ -146,6 +156,7 @@ func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.Coo
146156 return &SessionStore{
147157 CookieCipher: cipher,
148158 Cookie: cookieOpts,
159+ Minimal: opts.Cookie.Minimal,
149160 }, nil
150161}
151162
pkg/validation/options.go+1
@@ -27,6 +27,7 @@ import (
2727// are of the correct format
2828func Validate(o *options.Options) error {
2929 msgs := validateCookie(o.Cookie)
30+ msgs = append(msgs, validateSessionCookieMinimal(o)...)
3031
3132 if o.SSLInsecureSkipVerify {
3233 insecureTransport := &http.Transport{
pkg/validation/sessions.gonew file+32
@@ -0,0 +1,32 @@
1+package validation
2+
3+import (
4+ "time"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
7+)
8+
9+func validateSessionCookieMinimal(o *options.Options) []string {
10+ if !o.Session.Cookie.Minimal {
11+ return []string{}
12+ }
13+
14+ msgs := []string{}
15+ if o.PassAuthorization {
16+ msgs = append(msgs,
17+ "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
18+ }
19+ if o.SetAuthorization {
20+ msgs = append(msgs,
21+ "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
22+ }
23+ if o.PassAccessToken {
24+ msgs = append(msgs,
25+ "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set")
26+ }
27+ if o.Cookie.Refresh != time.Duration(0) {
28+ msgs = append(msgs,
29+ "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set")
30+ }
31+ return msgs
32+}
pkg/validation/sessions_test.gonew file+121
@@ -0,0 +1,121 @@
1+package validation
2+
3+import (
4+ "testing"
5+ "time"
6+
7+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
8+ . "github.com/onsi/gomega"
9+)
10+
11+func Test_validateSessionCookieMinimal(t *testing.T) {
12+ const (
13+ passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
14+ setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
15+ passAccessTokenMsg = "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set"
16+ cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
17+ )
18+
19+ testCases := map[string]struct {
20+ opts *options.Options
21+ errStrings []string
22+ }{
23+ "No minimal cookie session": {
24+ opts: &options.Options{
25+ Session: options.SessionOptions{
26+ Cookie: options.CookieStoreOptions{
27+ Minimal: false,
28+ },
29+ },
30+ },
31+ errStrings: []string{},
32+ },
33+ "No minimal cookie session & passAuthorization": {
34+ opts: &options.Options{
35+ Session: options.SessionOptions{
36+ Cookie: options.CookieStoreOptions{
37+ Minimal: false,
38+ },
39+ },
40+ PassAuthorization: true,
41+ },
42+ errStrings: []string{},
43+ },
44+ "Minimal cookie session no conflicts": {
45+ opts: &options.Options{
46+ Session: options.SessionOptions{
47+ Cookie: options.CookieStoreOptions{
48+ Minimal: true,
49+ },
50+ },
51+ },
52+ errStrings: []string{},
53+ },
54+ "PassAuthorization conflict": {
55+ opts: &options.Options{
56+ Session: options.SessionOptions{
57+ Cookie: options.CookieStoreOptions{
58+ Minimal: true,
59+ },
60+ },
61+ PassAuthorization: true,
62+ },
63+ errStrings: []string{passAuthorizationMsg},
64+ },
65+ "SetAuthorization conflict": {
66+ opts: &options.Options{
67+ Session: options.SessionOptions{
68+ Cookie: options.CookieStoreOptions{
69+ Minimal: true,
70+ },
71+ },
72+ SetAuthorization: true,
73+ },
74+ errStrings: []string{setAuthorizationMsg},
75+ },
76+ "PassAccessToken conflict": {
77+ opts: &options.Options{
78+ Session: options.SessionOptions{
79+ Cookie: options.CookieStoreOptions{
80+ Minimal: true,
81+ },
82+ },
83+ PassAccessToken: true,
84+ },
85+ errStrings: []string{passAccessTokenMsg},
86+ },
87+ "CookieRefresh conflict": {
88+ opts: &options.Options{
89+ Cookie: options.Cookie{
90+ Refresh: time.Hour,
91+ },
92+ Session: options.SessionOptions{
93+ Cookie: options.CookieStoreOptions{
94+ Minimal: true,
95+ },
96+ },
97+ },
98+ errStrings: []string{cookieRefreshMsg},
99+ },
100+ "Multiple conflicts": {
101+ opts: &options.Options{
102+ Session: options.SessionOptions{
103+ Cookie: options.CookieStoreOptions{
104+ Minimal: true,
105+ },
106+ },
107+ PassAuthorization: true,
108+ PassAccessToken: true,
109+ },
110+ errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
111+ },
112+ }
113+
114+ for testName, tc := range testCases {
115+ t.Run(testName, func(t *testing.T) {
116+ errStrings := validateSessionCookieMinimal(tc.opts)
117+ g := NewWithT(t)
118+ g.Expect(errStrings).To(ConsistOf(tc.errStrings))
119+ })
120+ }
121+}