diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 6f23f887..5d3e57c7 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -184,8 +184,9 @@ They may change between releases without notice. | `injectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added
to responses from the proxy.
This is typically used when using the proxy as an external authentication
provider in conjunction with another proxy such as NGINX and its
auth_request module.
Headers may source values from either the authenticated user's session
or from a static secret value. | | `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | | `metricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | -| `providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | +| `providers` | _[Providers](#providers)_ | Providers is used to configure your provider.
**Multiple-providers is not yet working.**
[This feature is tracked in #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | | `cookie` | _[Cookie](#cookie)_ | Cookie is used to configure the cookies used by OAuth2 Proxy.
This includes session and CSRF cookies. | +| `session` | _[SessionOptions](#sessionoptions)_ | Session is used to configure session options used by OAuth2 Proxy.
This includes session storage options. | ### AzureOptions @@ -230,19 +231,27 @@ Cookie contains configuration options relating session and CSRF cookies | Field | Type | Description | | ----- | ---- | ----------- | | `name` | _string_ | Name is the name of the cookie | -| `secret` | _string_ | Secret is the secret used to encrypt/sign the cookie value | -| `secretFile` | _string_ | SecretFile is a file containing the secret used to encrypt/sign the cookie value
instead of specifying it directly in the config. Secret takes precedence over SecretFile | +| `secret` | _[SecretSource](#secretsource)_ | Secret is the secret source used to encrypt/sign the cookie value | | `domains` | _[]string_ | Domains is a list of domains for which the cookie is valid | | `path` | _string_ | Path is the path for which the cookie is valid | | `expire` | _duration_ | Expire is the duration before the cookie expires | -| `refresh` | _duration_ | Refresh is the duration after which the cookie is refreshable | -| `secure` | _bool_ | Secure indicates whether the cookie is only sent over HTTPS | -| `httpOnly` | _bool_ | HTTPOnly indicates whether the cookie is inaccessible to JavaScript | -| `sameSite` | _string_ | SameSite sets the SameSite attribute on the cookie | -| `csrfPerRequest` | _bool_ | CSRFPerRequest indicates whether a unique CSRF token is generated for each request
Enables parallel requests from clients (e.g., multiple tabs) | +| `insecure` | _bool_ | Insecure indicates whether the cookie allows to be sent over HTTP
Default is false, which requires HTTPS | +| `scriptAccess` | _[ScriptAccess](#scriptaccess)_ | ScriptAccess is a wrapper enum for HTTPOnly; indicates whether the
cookie is accessible to JavaScript. Default is deny which translates
to true for HTTPOnly, which helps mitigate certain XSS attacks | +| `sameSite` | _[SameSiteMode](#samesitemode)_ | SameSite sets the SameSite attribute on the cookie | +| `csrfPerRequest` | _bool_ | CSRFPerRequest indicates whether a unique CSRF token is generated for each request
Enables parallel requests from clients (e.g., multiple tabs)
Default is false, which uses a single CSRF token per session | | `csrfPerRequestLimit` | _int_ | CSRFPerRequestLimit sets a limit on the number of valid CSRF tokens when CSRFPerRequest is enabled
Used to prevent unbounded memory growth from storing too many tokens | | `csrfExpire` | _duration_ | CSRFExpire sets the duration before a CSRF token expires | +### CookieStoreOptions + +(**Appears on:** [SessionOptions](#sessionoptions)) + +CookieStoreOptions contains configuration options for the CookieSessionStore. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `minimal` | _bool_ | Minimal indicates whether to use minimal cookies for session storage
Default is false | + ### GitHubOptions (**Appears on:** [Provider](#provider)) @@ -510,9 +519,44 @@ AlphaConfig](https://oauth2-proxy.github.io/oauth2-proxy/configuration/alpha-con However, [**the feature to implement multiple providers is not complete**](https://github.com/oauth2-proxy/oauth2-proxy/issues/926). +### RedisStoreOptions + +(**Appears on:** [SessionOptions](#sessionoptions)) + +RedisStoreOptions contains configuration options for the RedisSessionStore. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `connectionURL` | _string_ | ConnectionURL is the Redis connection URL | +| `username` | _string_ | Username is the Redis username | +| `password` | _string_ | Password is the Redis password | +| `useSentinel` | _bool_ | UseSentinel indicates whether to use Redis Sentinel
Default is false | +| `sentinelPassword` | _string_ | SentinelPassword is the Redis Sentinel password | +| `sentinelMasterName` | _string_ | SentinelMasterName is the Redis Sentinel master name | +| `sentinelConnectionURLs` | _[]string_ | SentinelConnectionURLs is a list of Redis Sentinel connection URLs | +| `useCluster` | _bool_ | UseCluster indicates whether to use Redis Cluster
Default is false | +| `clusterConnectionURLs` | _[]string_ | ClusterConnectionURLs is a list of Redis Cluster connection URLs | +| `caPath` | _string_ | CAPath is the path to the CA certificate for Redis TLS connections | +| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify indicates whether to skip TLS verification for Redis connections | +| `idleTimeout` | _int_ | IdleTimeout is the Redis connection idle timeout in seconds | + +### SameSiteMode +#### (`string` alias) + +(**Appears on:** [Cookie](#cookie)) + + + +### ScriptAccess +#### (`string` alias) + +(**Appears on:** [Cookie](#cookie)) + + + ### SecretSource -(**Appears on:** [ClaimSource](#claimsource), [HeaderValue](#headervalue), [TLS](#tls)) +(**Appears on:** [ClaimSource](#claimsource), [Cookie](#cookie), [HeaderValue](#headervalue), [TLS](#tls)) SecretSource references an individual secret value. Only one source within the struct should be defined at any time. @@ -535,6 +579,26 @@ Server represents the configuration for an HTTP(S) server | `secureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.
Leave blank or set to "-" to disable. | | `tls` | _[TLS](#tls)_ | TLS contains the information for loading the certificate and key for the
secure traffic and further configuration for the TLS server. | +### SessionOptions + +(**Appears on:** [AlphaOptions](#alphaoptions)) + +SessionOptions contains configuration options for the SessionStore providers. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `type` | _[SessionStoreType](#sessionstoretype)_ | Type is the type of session store to use
Options are "cookie" or "redis"
Default is "cookie" | +| `refresh` | _duration_ | Refresh is the duration after which the session is refreshable | +| `cookie` | _[CookieStoreOptions](#cookiestoreoptions)_ | Cookie is the configuration options for the CookieSessionStore | +| `redis` | _[RedisStoreOptions](#redisstoreoptions)_ | Redis is the configuration options for the RedisSessionStore | + +### SessionStoreType +#### (`string` alias) + +(**Appears on:** [SessionOptions](#sessionoptions)) + + + ### TLS (**Appears on:** [Server](#server)) diff --git a/oauthproxy.go b/oauthproxy.go index 86ebfebc..c22ac5b2 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -180,7 +180,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr refresh = fmt.Sprintf("after %s", opts.Session.Refresh) } - logger.Printf("Cookie settings: name:%s insecure(http):%v nothttponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Insecure, opts.Cookie.NotHttpOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh) + logger.Printf("Cookie settings: name:%s insecure(http):%v scriptaccess:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Insecure, opts.Cookie.ScriptAccess, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh) trustedIPs := ip.NewNetSet() for _, ipStr := range opts.TrustedIPs { diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index 977bdb18..c75347a9 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -41,14 +41,18 @@ type AlphaOptions struct { // To use the secure server you must configure a TLS certificate and key. MetricsServer Server `yaml:"metricsServer,omitempty"` - // Providers is used to configure your provider. **Multiple-providers is not - // yet working.** [This feature is tracked in - // #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) + // Providers is used to configure your provider. + // **Multiple-providers is not yet working.** + // [This feature is tracked in #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) Providers Providers `yaml:"providers,omitempty"` // Cookie is used to configure the cookies used by OAuth2 Proxy. // This includes session and CSRF cookies. Cookie Cookie `yaml:"cookie,omitempty"` + + // Session is used to configure session options used by OAuth2 Proxy. + // This includes session storage options. + Session SessionOptions `yaml:"session,omitempty"` } // Initialize alpha options with default values and settings of the core options @@ -68,6 +72,7 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) { a.MetricsServer = opts.MetricsServer a.Providers = opts.Providers a.Cookie = opts.Cookie + a.Session = opts.Session } // MergeOptionsWithDefaults replaces alpha options in the Options struct @@ -80,4 +85,5 @@ func (a *AlphaOptions) MergeOptionsWithDefaults(opts *Options) { opts.MetricsServer = a.MetricsServer opts.Providers = a.Providers opts.Cookie = a.Cookie + opts.Session = a.Session } diff --git a/pkg/apis/options/cookie.go b/pkg/apis/options/cookie.go index 6269bfbf..4b71544a 100644 --- a/pkg/apis/options/cookie.go +++ b/pkg/apis/options/cookie.go @@ -11,8 +11,6 @@ import ( const ( // DefaultCookieInsecure is the default value for Cookie.Insecure DefaultCookieInsecure bool = false - // DefaultCookieNotHttpOnly is the default value for Cookie.NotHttpOnly - DefaultCookieNotHttpOnly bool = false // DefaultCSRFPerRequest is the default value for Cookie.CSRFPerRequest DefaultCSRFPerRequest bool = false ) @@ -26,6 +24,14 @@ const ( SameSiteDefault SameSiteMode = "" ) +type ScriptAccess string + +const ( + ScriptAccessDenied ScriptAccess = "deny" + ScriptAccessAllowed ScriptAccess = "allow" + ScriptAccessNone ScriptAccess = "" +) + // Cookie contains configuration options relating session and CSRF cookies type Cookie struct { // Name is the name of the cookie @@ -41,9 +47,10 @@ type Cookie struct { // Insecure indicates whether the cookie allows to be sent over HTTP // Default is false, which requires HTTPS Insecure *bool `yaml:"insecure,omitempty"` - // NotHttpOnly is the inverse of HTTPOnly; indicates whether the cookie is accessible to JavaScript - // Default is false, which helps mitigate certain XSS attacks - NotHttpOnly *bool `yaml:"notHttpOnly,omitempty"` + // ScriptAccess is a wrapper enum for HTTPOnly; indicates whether the + // cookie is accessible to JavaScript. Default is deny which translates + // to true for HTTPOnly, which helps mitigate certain XSS attacks + ScriptAccess ScriptAccess `yaml:"scriptAccess,omitempty"` // SameSite sets the SameSite attribute on the cookie SameSite SameSiteMode `yaml:"sameSite,omitempty"` // CSRFPerRequest indicates whether a unique CSRF token is generated for each request @@ -57,6 +64,8 @@ type Cookie struct { CSRFExpire time.Duration `yaml:"csrfExpire,omitempty"` } +// UnmarshalYAML unmarshalles the strings provided for the +// SameSite property to the enum type SameSiteMode func (m *SameSiteMode) UnmarshalYAML(value *yaml.Node) error { var s string if err := value.Decode(&s); err != nil { @@ -71,6 +80,22 @@ func (m *SameSiteMode) UnmarshalYAML(value *yaml.Node) error { } } +// UnmarshalYAML unmarshalles the strings provided for the +// ScriptAccess property to the enum type ScriptAccess +func (sa *ScriptAccess) UnmarshalYAML(value *yaml.Node) error { + var s string + if err := value.Decode(&s); err != nil { + return err + } + switch ScriptAccess(s) { + case ScriptAccessAllowed, ScriptAccessDenied, ScriptAccessNone: + *sa = ScriptAccess(s) + return nil + default: + return fmt.Errorf("invalid script access: %s", s) + } +} + // GetSecret returns the cookie secret as a string from the SecretSource func (c *Cookie) GetSecret() (string, error) { secret, err := c.Secret.GetSecretValue() @@ -95,8 +120,8 @@ func (c *Cookie) EnsureDefaults() { if c.Insecure == nil { c.Insecure = ptr.To(DefaultCookieInsecure) } - if c.NotHttpOnly == nil { - c.NotHttpOnly = ptr.To(DefaultCookieNotHttpOnly) + if c.ScriptAccess == ScriptAccessNone { + c.ScriptAccess = ScriptAccessDenied } if c.CSRFPerRequest == nil { c.CSRFPerRequest = ptr.To(DefaultCSRFPerRequest) diff --git a/pkg/apis/options/legacy_cookie.go b/pkg/apis/options/legacy_cookie.go index be1235ca..8240e7c8 100644 --- a/pkg/apis/options/legacy_cookie.go +++ b/pkg/apis/options/legacy_cookie.go @@ -1,10 +1,8 @@ package options import ( - "encoding/base64" "time" - "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/spf13/pflag" ) @@ -46,10 +44,13 @@ func legacyCookieFlagSet() *pflag.FlagSet { } func (l *LegacyCookie) convert() Cookie { - // Invert Secure and HTTPOnly to match the new Cookie struct - // which uses Insecure and NotHttpOnly + // Invert Secure and use ScriptAccess property instead of + // HTTPOnly to match new Cookie struct insecure := !l.Secure - notHTTPOnly := !l.HTTPOnly + scriptAccess := ScriptAccessDenied + if !l.HTTPOnly { + scriptAccess = ScriptAccessAllowed + } var secret *SecretSource if l.Secret != "" { @@ -67,7 +68,7 @@ func (l *LegacyCookie) convert() Cookie { Path: l.Path, Expire: l.Expire, Insecure: &insecure, - NotHttpOnly: ¬HTTPOnly, + ScriptAccess: scriptAccess, SameSite: SameSiteMode(l.SameSite), CSRFPerRequest: &l.CSRFPerRequest, CSRFPerRequestLimit: l.CSRFPerRequestLimit, diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go index b6be7a73..d055be25 100644 --- a/pkg/apis/options/legacy_options_test.go +++ b/pkg/apis/options/legacy_options_test.go @@ -1099,7 +1099,7 @@ var _ = Describe("Legacy Options", func() { Path: "/", Expire: time.Duration(168) * time.Hour, Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), + ScriptAccess: ScriptAccessDenied, SameSite: "", CSRFPerRequest: ptr.To(false), CSRFPerRequestLimit: 0, diff --git a/pkg/cookies/cookies.go b/pkg/cookies/cookies.go index 252daeb6..913d9b9b 100644 --- a/pkg/cookies/cookies.go +++ b/pkg/cookies/cookies.go @@ -26,12 +26,17 @@ func MakeCookieFromOptions(req *http.Request, name string, value string, opts *o domain = opts.Domains[len(opts.Domains)-1] } + httpOnly := true + if opts.ScriptAccess == options.ScriptAccessAllowed { + httpOnly = false + } + c := &http.Cookie{ Name: name, Value: value, Path: opts.Path, Domain: domain, - HttpOnly: !ptr.Deref(opts.NotHttpOnly, options.DefaultCookieNotHttpOnly), + HttpOnly: httpOnly, Secure: !ptr.Deref(opts.Insecure, options.DefaultCookieInsecure), SameSite: ParseSameSite(opts.SameSite), } diff --git a/pkg/cookies/cookies_test.go b/pkg/cookies/cookies_test.go index 13131c4b..dece3e95 100644 --- a/pkg/cookies/cookies_test.go +++ b/pkg/cookies/cookies_test.go @@ -114,14 +114,14 @@ var _ = Describe("Cookie Tests", func() { name: validName, value: "1", opts: options.Cookie{ - Name: validName, - Secret: options.SecretSource{Value: validSecret}, - Domains: domains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: options.SecretSource{Value: validSecret}, + Domains: domains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, expiration: 15 * time.Minute, now: now, @@ -132,14 +132,14 @@ var _ = Describe("Cookie Tests", func() { name: validName, value: "1", opts: options.Cookie{ - Name: validName, - Secret: options.SecretSource{Value: validSecret}, - Domains: domains, - Path: "", - Expire: time.Hour * -1, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: options.SecretSource{Value: validSecret}, + Domains: domains, + Path: "", + Expire: time.Hour * -1, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, expiration: time.Hour * -1, now: now, @@ -150,14 +150,14 @@ var _ = Describe("Cookie Tests", func() { name: validName, value: "1", opts: options.Cookie{ - Name: validName, - Secret: options.SecretSource{Value: validSecret}, - Domains: domains, - Path: "", - Expire: 0, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: options.SecretSource{Value: validSecret}, + Domains: domains, + Path: "", + Expire: 0, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, expiration: 0, now: now, diff --git a/pkg/cookies/csrf_per_request_test.go b/pkg/cookies/csrf_per_request_test.go index bb0fcaee..0f9e2963 100644 --- a/pkg/cookies/csrf_per_request_test.go +++ b/pkg/cookies/csrf_per_request_test.go @@ -30,7 +30,7 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() { Path: cookiePath, Expire: time.Hour, Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, CSRFPerRequest: ptr.To(true), CSRFExpire: time.Duration(5) * time.Minute, } diff --git a/pkg/cookies/csrf_test.go b/pkg/cookies/csrf_test.go index dfb0bae7..c89f7d12 100644 --- a/pkg/cookies/csrf_test.go +++ b/pkg/cookies/csrf_test.go @@ -31,7 +31,7 @@ var _ = Describe("CSRF Cookie Tests", func() { Path: cookiePath, Expire: time.Hour, Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, CSRFPerRequest: ptr.To(false), CSRFExpire: time.Hour, } diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 2e982352..d1fd5881 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -50,11 +50,11 @@ var _ = Describe("NewSessionStore", func() { Secret: options.SecretSource{ Value: secretValue, }, - Path: "/", - Expire: time.Duration(168) * time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), - SameSite: "", + Path: "/", + Expire: time.Duration(168) * time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, + SameSite: "", } }) diff --git a/pkg/sessions/tests/session_store_tests.go b/pkg/sessions/tests/session_store_tests.go index 80f59bb1..d78fa679 100644 --- a/pkg/sessions/tests/session_store_tests.go +++ b/pkg/sessions/tests/session_store_tests.go @@ -67,13 +67,13 @@ func RunSessionStoreTests(newSS NewSessionStoreFunc, persistentFastForward Persi // Set default options in CookieOptions cookieOpts := &options.Cookie{ - Name: "_oauth2_proxy", - Path: "/", - Expire: time.Duration(168) * time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), - SameSite: options.SameSiteDefault, - Secret: options.SecretSource{Value: cookieSecret}, + Name: "_oauth2_proxy", + Path: "/", + Expire: time.Duration(168) * time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, + SameSite: options.SameSiteDefault, + Secret: options.SecretSource{Value: cookieSecret}, } expires := time.Now().Add(1 * time.Hour) @@ -117,14 +117,14 @@ func RunSessionStoreTests(newSS NewSessionStoreFunc, persistentFastForward Persi BeforeEach(func() { input.sessionOpts.Refresh = time.Duration(2) * time.Hour input.cookieOpts = &options.Cookie{ - Name: "_cookie_name", - Path: "/path", - Expire: time.Duration(72) * time.Hour, - Insecure: ptr.To(true), - NotHttpOnly: ptr.To(true), - Domains: []string{"example.com"}, - SameSite: options.SameSiteStrict, - Secret: options.SecretSource{Value: cookieSecret}, + Name: "_cookie_name", + Path: "/path", + Expire: time.Duration(72) * time.Hour, + Insecure: ptr.To(true), + ScriptAccess: options.ScriptAccessAllowed, + Domains: []string{"example.com"}, + SameSite: options.SameSiteStrict, + Secret: options.SecretSource{Value: cookieSecret}, } var err error @@ -149,13 +149,13 @@ func RunSessionStoreTests(newSS NewSessionStoreFunc, persistentFastForward Persi input.sessionOpts.Refresh = time.Duration(1) * time.Hour input.cookieOpts = &options.Cookie{ - Name: "_oauth2_proxy_file", - Path: "/", - Expire: time.Duration(168) * time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), - SameSite: options.SameSiteDefault, - Secret: options.SecretSource{FromFile: tmpfile.Name()}, + Name: "_oauth2_proxy_file", + Path: "/", + Expire: time.Duration(168) * time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, + SameSite: options.SameSiteDefault, + Secret: options.SecretSource{FromFile: tmpfile.Name()}, } ss, err = newSS(input.sessionOpts, input.cookieOpts) Expect(err).ToNot(HaveOccurred()) @@ -210,7 +210,14 @@ func CheckCookieOptions(in *testInput) { It("have the correct HTTPOnly set", func() { for _, cookie := range cookies { - Expect(cookie.HttpOnly).To(Equal(!(*in.cookieOpts.NotHttpOnly))) + var httpOnly bool + if in.cookieOpts.ScriptAccess == options.ScriptAccessAllowed { + httpOnly = false + } + if in.cookieOpts.ScriptAccess == options.ScriptAccessDenied { + httpOnly = true + } + Expect(cookie.HttpOnly).To(Equal(httpOnly)) } }) diff --git a/pkg/validation/cookie_test.go b/pkg/validation/cookie_test.go index b919ff2b..cd9a6bdf 100644 --- a/pkg/validation/cookie_test.go +++ b/pkg/validation/cookie_test.go @@ -74,14 +74,14 @@ func TestValidateCookie(t *testing.T) { { name: "with valid configuration", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: domains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: validSecret, + Domains: domains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -94,12 +94,12 @@ func TestValidateCookie(t *testing.T) { Value: nil, FromFile: "", }, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -109,14 +109,14 @@ func TestValidateCookie(t *testing.T) { { name: "with an invalid cookie secret", cookie: options.Cookie{ - Name: validName, - Secret: invalidSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: invalidSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -126,14 +126,14 @@ func TestValidateCookie(t *testing.T) { { name: "with a valid Base64 secret", cookie: options.Cookie{ - Name: validName, - Secret: validBase64Secret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: validBase64Secret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -141,14 +141,14 @@ func TestValidateCookie(t *testing.T) { { name: "with an invalid Base64 secret", cookie: options.Cookie{ - Name: validName, - Secret: invalidBase64Secret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: invalidBase64Secret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -158,14 +158,14 @@ func TestValidateCookie(t *testing.T) { { name: "with an invalid name", cookie: options.Cookie{ - Name: invalidName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: invalidName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -175,14 +175,14 @@ func TestValidateCookie(t *testing.T) { { name: "with a name that is too long", cookie: options.Cookie{ - Name: longName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: longName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -192,14 +192,14 @@ func TestValidateCookie(t *testing.T) { { name: "with refresh longer than expire", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: 15 * time.Minute, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: 15 * time.Minute, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: time.Hour, errStrings: []string{ @@ -209,14 +209,14 @@ func TestValidateCookie(t *testing.T) { { name: "with samesite \"none\"", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "none", + Name: validName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "none", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -224,14 +224,14 @@ func TestValidateCookie(t *testing.T) { { name: "with samesite \"lax\"", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "none", + Name: validName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "none", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -239,14 +239,14 @@ func TestValidateCookie(t *testing.T) { { name: "with samesite \"strict\"", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "none", + Name: validName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "none", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -254,14 +254,14 @@ func TestValidateCookie(t *testing.T) { { name: "with samesite \"invalid\"", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: emptyDomains, - Path: "", - Expire: time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "invalid", + Name: validName, + Secret: validSecret, + Domains: emptyDomains, + Path: "", + Expire: time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "invalid", }, refresh: 15 * time.Minute, errStrings: []string{ @@ -271,14 +271,14 @@ func TestValidateCookie(t *testing.T) { { name: "with a combination of configuration errors", cookie: options.Cookie{ - Name: invalidName, - Secret: invalidSecret, - Domains: domains, - Path: "", - Expire: 15 * time.Minute, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "invalid", + Name: invalidName, + Secret: invalidSecret, + Domains: domains, + Path: "", + Expire: 15 * time.Minute, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "invalid", }, refresh: time.Hour, errStrings: []string{ @@ -291,14 +291,14 @@ func TestValidateCookie(t *testing.T) { { name: "with session cookie configuration", cookie: options.Cookie{ - Name: validName, - Secret: validSecret, - Domains: domains, - Path: "", - Expire: 0, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(true), - SameSite: "", + Name: validName, + Secret: validSecret, + Domains: domains, + Path: "", + Expire: 0, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessAllowed, + SameSite: "", }, refresh: 15 * time.Minute, errStrings: []string{}, @@ -310,12 +310,12 @@ func TestValidateCookie(t *testing.T) { Secret: options.SecretSource{ FromFile: tmpfile.Name(), }, - Domains: domains, - Path: "", - Expire: 24 * time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), - SameSite: "", + Domains: domains, + Path: "", + Expire: 24 * time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, + SameSite: "", }, refresh: 0, errStrings: []string{}, @@ -327,12 +327,12 @@ func TestValidateCookie(t *testing.T) { Secret: options.SecretSource{ FromFile: "/nonexistent/file.txt", }, - Domains: domains, - Path: "", - Expire: 24 * time.Hour, - Insecure: ptr.To(false), - NotHttpOnly: ptr.To(false), - SameSite: "", + Domains: domains, + Path: "", + Expire: 24 * time.Hour, + Insecure: ptr.To(false), + ScriptAccess: options.ScriptAccessDenied, + SameSite: "", }, refresh: 0, errStrings: []string{"could not read cookie secret file: /nonexistent/file.txt"},