Skip to content

Commit

Permalink
Add configuration for cookie 'SameSite' value.
Browse files Browse the repository at this point in the history
Values of 'lax' and 'strict' can improve and mitigate
some categories of cross-site traffic tampering.

Given that the nature of this proxy is often to proxy
private tools, this is useful to take advantage of.

See: https://www.owasp.org/index.php/SameSite
  • Loading branch information
Paul Groudas committed Jan 6, 2020
1 parent 90f8117 commit 5d0827a
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 6 deletions.
2 changes: 1 addition & 1 deletion contrib/oauth2_proxy_autocomplete.sh
Expand Up @@ -20,7 +20,7 @@ _oauth2_proxy() {
COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov" -- ${cur}) )
return 0
;;
-@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|redist-sentinel-master-name|redist-sentinel-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
-@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url))
return 0
;;
esac
Expand Down
1 change: 1 addition & 0 deletions docs/configuration/configuration.md
Expand Up @@ -38,6 +38,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
| `-cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable | |
| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
| `-cookie-secure` | bool | set secure (HTTPS) cookie flag | true |
| `-cookie-samesite` | string | set SameSite cookie attribute (ie: `"lax"`, `"strict"`, `"none"`, or `""`). | `""` |
| `-custom-templates-dir` | string | path to custom html templates | |
| `-display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
Expand Down
1 change: 1 addition & 0 deletions main.go
Expand Up @@ -86,6 +86,7 @@ func main() {
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-httponly", true, "set HttpOnly cookie flag")
flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ")

flagSet.String("session-store-type", "cookie", "the session storage provider to use")
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
Expand Down
6 changes: 5 additions & 1 deletion oauthproxy.go
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/coreos/go-oidc"
"github.com/mbland/hmacauth"
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/pusher/oauth2_proxy/pkg/cookies"
"github.com/pusher/oauth2_proxy/pkg/encryption"
"github.com/pusher/oauth2_proxy/pkg/logger"
"github.com/pusher/oauth2_proxy/providers"
Expand Down Expand Up @@ -68,6 +69,7 @@ type OAuthProxy struct {
CookieHTTPOnly bool
CookieExpire time.Duration
CookieRefresh time.Duration
CookieSameSite string
Validator func(string) bool

RobotsPath string
Expand Down Expand Up @@ -260,7 +262,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
}

logger.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)
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s samesite:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, opts.CookieSameSite, refresh)

return &OAuthProxy{
CookieName: opts.CookieName,
Expand All @@ -272,6 +274,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
CookieHTTPOnly: opts.CookieHTTPOnly,
CookieExpire: opts.CookieExpire,
CookieRefresh: opts.CookieRefresh,
CookieSameSite: opts.CookieSameSite,
Validator: validator,

RobotsPath: "/robots.txt",
Expand Down Expand Up @@ -380,6 +383,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
HttpOnly: p.CookieHTTPOnly,
Secure: p.CookieSecure,
Expires: now.Add(expiration),
SameSite: cookies.ParseSameSite(p.CookieSameSite),
}
}

Expand Down
6 changes: 6 additions & 0 deletions options.go
Expand Up @@ -372,6 +372,12 @@ func (o *Options) Validate() error {
}
}

switch o.CookieSameSite {
case "", "none", "lax", "strict":
default:
msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
}

msgs = parseSignatureKey(o, msgs)
msgs = validateCookieName(o, msgs)
msgs = setupLogger(o, msgs)
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/options/cookie.go
Expand Up @@ -12,4 +12,5 @@ type CookieOptions struct {
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"`
CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
CookieSameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
}
22 changes: 20 additions & 2 deletions pkg/cookies/cookies.go
@@ -1,6 +1,7 @@
package cookies

import (
"fmt"
"net"
"net/http"
"strings"
Expand All @@ -12,7 +13,7 @@ import (

// MakeCookie constructs a cookie from the given parameters,
// discovering the domain from the request if not specified.
func MakeCookie(req *http.Request, name string, value string, path string, domain string, httpOnly bool, secure bool, expiration time.Duration, now time.Time) *http.Cookie {
func MakeCookie(req *http.Request, name string, value string, path string, domain string, httpOnly bool, secure bool, expiration time.Duration, now time.Time, sameSite http.SameSite) *http.Cookie {
if domain != "" {
host := req.Host
if h, _, err := net.SplitHostPort(host); err == nil {
Expand All @@ -31,11 +32,28 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
HttpOnly: httpOnly,
Secure: secure,
Expires: now.Add(expiration),
SameSite: sameSite,
}
}

// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
// value and creation time
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now)
return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
}

// Parse a valid http.SameSite value from a user supplied string for use of making cookies.
func ParseSameSite(v string) http.SameSite {
switch v {
case "lax":
return http.SameSiteLaxMode
case "strict":
return http.SameSiteStrictMode
case "none":
return http.SameSiteNoneMode
case "":
return http.SameSiteDefaultMode
default:
panic(fmt.Sprintf("Invalid value for SameSite: %s", v))
}
}
12 changes: 10 additions & 2 deletions pkg/sessions/session_store_test.go
Expand Up @@ -15,7 +15,7 @@ import (
. "github.com/onsi/gomega"
"github.com/pusher/oauth2_proxy/pkg/apis/options"
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/pusher/oauth2_proxy/pkg/cookies"
cookiesapi "github.com/pusher/oauth2_proxy/pkg/cookies"
"github.com/pusher/oauth2_proxy/pkg/encryption"
"github.com/pusher/oauth2_proxy/pkg/sessions"
sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
Expand Down Expand Up @@ -79,6 +79,12 @@ var _ = Describe("NewSessionStore", func() {
}
})

It("have the correct SameSite set", func() {
for _, cookie := range cookies {
Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.CookieSameSite)))
}
})

It("have a signature timestamp matching session.CreatedAt", func() {
for _, cookie := range cookies {
if cookie.Value != "" {
Expand Down Expand Up @@ -159,7 +165,7 @@ var _ = Describe("NewSessionStore", func() {
By("Using a valid cookie with a different providers session encoding")
broken := "BrokenSessionFromADifferentSessionImplementation"
value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
request.AddCookie(cookie)

err := ss.Save(response, request, session)
Expand Down Expand Up @@ -338,6 +344,7 @@ var _ = Describe("NewSessionStore", func() {
CookieSecure: false,
CookieHTTPOnly: false,
CookieDomain: "example.com",
CookieSameSite: "strict",
}

var err error
Expand Down Expand Up @@ -379,6 +386,7 @@ var _ = Describe("NewSessionStore", func() {
CookieRefresh: time.Duration(1) * time.Hour,
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: "",
}

session = &sessionsapi.SessionState{
Expand Down

0 comments on commit 5d0827a

Please sign in to comment.