mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-04-27 12:32:10 +02:00
Add configuration for cookie 'SameSite' value.
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
This commit is contained in:
parent
90f8117fba
commit
5d0827a028
@ -20,7 +20,7 @@ _oauth2_proxy() {
|
|||||||
COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov" -- ${cur}) )
|
||||||
return 0
|
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
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@ -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-refresh` | duration | refresh the cookie after this duration; `0` to disable | |
|
||||||
| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
|
| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
|
||||||
| `-cookie-secure` | bool | set secure (HTTPS) cookie flag | true |
|
| `-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 | |
|
| `-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 |
|
| `-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 | |
|
| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
|
||||||
|
1
main.go
1
main.go
@ -86,6 +86,7 @@ func main() {
|
|||||||
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")
|
||||||
flagSet.Bool("cookie-httponly", true, "set HttpOnly 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("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])")
|
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/coreos/go-oidc"
|
"github.com/coreos/go-oidc"
|
||||||
"github.com/mbland/hmacauth"
|
"github.com/mbland/hmacauth"
|
||||||
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
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/encryption"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
@ -68,6 +69,7 @@ type OAuthProxy struct {
|
|||||||
CookieHTTPOnly bool
|
CookieHTTPOnly bool
|
||||||
CookieExpire time.Duration
|
CookieExpire time.Duration
|
||||||
CookieRefresh time.Duration
|
CookieRefresh time.Duration
|
||||||
|
CookieSameSite string
|
||||||
Validator func(string) bool
|
Validator func(string) bool
|
||||||
|
|
||||||
RobotsPath string
|
RobotsPath string
|
||||||
@ -260,7 +262,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
|
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{
|
return &OAuthProxy{
|
||||||
CookieName: opts.CookieName,
|
CookieName: opts.CookieName,
|
||||||
@ -272,6 +274,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
CookieHTTPOnly: opts.CookieHTTPOnly,
|
CookieHTTPOnly: opts.CookieHTTPOnly,
|
||||||
CookieExpire: opts.CookieExpire,
|
CookieExpire: opts.CookieExpire,
|
||||||
CookieRefresh: opts.CookieRefresh,
|
CookieRefresh: opts.CookieRefresh,
|
||||||
|
CookieSameSite: opts.CookieSameSite,
|
||||||
Validator: validator,
|
Validator: validator,
|
||||||
|
|
||||||
RobotsPath: "/robots.txt",
|
RobotsPath: "/robots.txt",
|
||||||
@ -380,6 +383,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
|
|||||||
HttpOnly: p.CookieHTTPOnly,
|
HttpOnly: p.CookieHTTPOnly,
|
||||||
Secure: p.CookieSecure,
|
Secure: p.CookieSecure,
|
||||||
Expires: now.Add(expiration),
|
Expires: now.Add(expiration),
|
||||||
|
SameSite: cookies.ParseSameSite(p.CookieSameSite),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 = parseSignatureKey(o, msgs)
|
||||||
msgs = validateCookieName(o, msgs)
|
msgs = validateCookieName(o, msgs)
|
||||||
msgs = setupLogger(o, msgs)
|
msgs = setupLogger(o, msgs)
|
||||||
|
@ -12,4 +12,5 @@ type CookieOptions struct {
|
|||||||
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"`
|
||||||
CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
|
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"`
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package cookies
|
package cookies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// MakeCookie constructs a cookie from the given parameters,
|
// MakeCookie constructs a cookie from the given parameters,
|
||||||
// discovering the domain from the request if not specified.
|
// 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 != "" {
|
if domain != "" {
|
||||||
host := req.Host
|
host := req.Host
|
||||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||||
@ -31,11 +32,28 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
|
|||||||
HttpOnly: httpOnly,
|
HttpOnly: httpOnly,
|
||||||
Secure: secure,
|
Secure: secure,
|
||||||
Expires: now.Add(expiration),
|
Expires: now.Add(expiration),
|
||||||
|
SameSite: sameSite,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
|
// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
|
||||||
// value and creation time
|
// value and creation time
|
||||||
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
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/encryption"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/sessions"
|
||||||
sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
||||||
@ -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() {
|
It("have a signature timestamp matching session.CreatedAt", func() {
|
||||||
for _, cookie := range cookies {
|
for _, cookie := range cookies {
|
||||||
if cookie.Value != "" {
|
if cookie.Value != "" {
|
||||||
@ -159,7 +165,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
By("Using a valid cookie with a different providers session encoding")
|
By("Using a valid cookie with a different providers session encoding")
|
||||||
broken := "BrokenSessionFromADifferentSessionImplementation"
|
broken := "BrokenSessionFromADifferentSessionImplementation"
|
||||||
value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
|
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)
|
request.AddCookie(cookie)
|
||||||
|
|
||||||
err := ss.Save(response, request, session)
|
err := ss.Save(response, request, session)
|
||||||
@ -338,6 +344,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
CookieSecure: false,
|
CookieSecure: false,
|
||||||
CookieHTTPOnly: false,
|
CookieHTTPOnly: false,
|
||||||
CookieDomain: "example.com",
|
CookieDomain: "example.com",
|
||||||
|
CookieSameSite: "strict",
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -379,6 +386,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
CookieRefresh: time.Duration(1) * time.Hour,
|
CookieRefresh: time.Duration(1) * time.Hour,
|
||||||
CookieSecure: true,
|
CookieSecure: true,
|
||||||
CookieHTTPOnly: true,
|
CookieHTTPOnly: true,
|
||||||
|
CookieSameSite: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
session = &sessionsapi.SessionState{
|
session = &sessionsapi.SessionState{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user