fix: cookie handling (#654)

* feat: set cookie prefix and max age

* cookie prefix on csrf cookie

* fix: check user agent cookie in login

* update oidc pkg

* cleanup
This commit is contained in:
Livio Amstutz
2020-08-31 08:49:35 +02:00
committed by GitHub
parent 1089193faf
commit c1c85e632b
26 changed files with 262 additions and 205 deletions

View File

@@ -8,9 +8,15 @@ import (
"github.com/caos/zitadel/internal/errors"
)
const (
prefixSecure = "__Secure-"
prefixHost = "__Host-"
)
type CookieHandler struct {
securecookie *securecookie.SecureCookie
secureOnly bool
httpOnly bool
sameSite http.SameSite
path string
maxAge int
@@ -20,6 +26,7 @@ type CookieHandler struct {
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
c := &CookieHandler{
secureOnly: true,
httpOnly: true,
sameSite: http.SameSiteLaxMode,
path: "/",
}
@@ -44,6 +51,12 @@ func WithUnsecure() CookieHandlerOpt {
}
}
func WithNonHttpOnly() CookieHandlerOpt {
return func(c *CookieHandler) {
c.httpOnly = false
}
}
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
return func(c *CookieHandler) {
c.sameSite = sameSite
@@ -69,6 +82,16 @@ func WithDomain(domain string) CookieHandlerOpt {
}
}
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
if !secureOnly {
return name
}
if domain != "" || path != "/" {
return prefixSecure + name
}
return prefixHost + name
}
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
cookie, err := r.Cookie(name)
if err != nil {
@@ -78,7 +101,7 @@ func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, er
}
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
cookie, err := r.Cookie(name)
cookie, err := r.Cookie(SetCookiePrefix(name, c.domain, c.path, c.secureOnly))
if err != nil {
return err
}
@@ -110,12 +133,12 @@ func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
http.SetCookie(w, &http.Cookie{
Name: name,
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly),
Value: value,
Domain: c.domain,
Path: c.path,
MaxAge: maxage,
HttpOnly: true,
HttpOnly: c.httpOnly,
Secure: c.secureOnly,
SameSite: c.sameSite,
})

View File

@@ -41,13 +41,13 @@ var (
remoteAddr key
)
func CopyHeadersToContext(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func CopyHeadersToContext(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), httpHeaders, r.Header)
ctx = context.WithValue(ctx, remoteAddr, r.RemoteAddr)
r = r.WithContext(ctx)
h(w, r)
}
h.ServeHTTP(w, r)
})
}
func HeadersFromCtx(ctx context.Context) (http.Header, bool) {

View File

@@ -9,10 +9,10 @@ import (
http_utils "github.com/caos/zitadel/internal/api/http"
)
type key int
type securityKey int
const (
nonceKey key = 0
nonceKey securityKey = 0
DefaultNonceLength = uint(32)
)

View File

@@ -0,0 +1,103 @@
package middleware
import (
"context"
"net/http"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
)
type cookieKey int
var (
userAgentKey cookieKey = 0
)
func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
userAgentID, ok := ctx.Value(userAgentKey).(string)
return userAgentID, ok
}
type UserAgent struct {
ID string
}
type userAgentHandler struct {
cookieHandler *http_utils.CookieHandler
cookieName string
idGenerator id.Generator
nextHandler http.Handler
}
type UserAgentCookieConfig struct {
Name string
Domain string
Key *crypto.KeyConfig
MaxAge types.Duration
}
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator, localDevMode bool) (func(http.Handler) http.Handler, error) {
key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
}
cookieKey := []byte(key)
opts := []http_utils.CookieHandlerOpt{
http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(config.Domain),
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
}
if localDevMode {
opts = append(opts, http_utils.WithUnsecure())
}
return func(handler http.Handler) http.Handler {
return &userAgentHandler{
nextHandler: handler,
cookieName: config.Name,
cookieHandler: http_utils.NewCookieHandler(opts...),
idGenerator: idGenerator,
}
}, nil
}
func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
agent, err := ua.getUserAgent(r)
if err != nil {
agent, err = ua.newUserAgent()
}
if err == nil {
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
r = r.WithContext(ctx)
ua.setUserAgent(w, agent)
}
ua.nextHandler.ServeHTTP(w, r)
}
func (ua *userAgentHandler) newUserAgent() (*UserAgent, error) {
agentID, err := ua.idGenerator.Next()
if err != nil {
return nil, err
}
return &UserAgent{ID: agentID}, nil
}
func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
userAgent := new(UserAgent)
err := ua.cookieHandler.GetEncryptedCookieValue(r, ua.cookieName, userAgent)
if err != nil {
return nil, errors.ThrowPermissionDenied(err, "HTTP-YULqH4", "cannot read user agent cookie")
}
return userAgent, nil
}
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error {
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent)
if err != nil {
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
}
return nil
}

View File

@@ -1,68 +0,0 @@
package http
import (
"net/http"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
)
type UserAgent struct {
ID string
}
type UserAgentHandler struct {
handler *CookieHandler
cookieName string
idGenerator id.Generator
}
type UserAgentCookieConfig struct {
Name string
Domain string
Key *crypto.KeyConfig
}
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator) (*UserAgentHandler, error) {
key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
}
cookieKey := []byte(key)
handler := NewCookieHandler(
WithEncryption(cookieKey, cookieKey),
WithDomain(config.Domain),
WithUnsecure(),
)
return &UserAgentHandler{
cookieName: config.Name,
handler: handler,
idGenerator: idGenerator,
}, nil
}
func (ua *UserAgentHandler) NewUserAgent() (*UserAgent, error) {
agentID, err := ua.idGenerator.Next()
if err != nil {
return nil, err
}
return &UserAgent{ID: agentID}, nil
}
func (ua *UserAgentHandler) GetUserAgent(r *http.Request) (*UserAgent, error) {
userAgent := new(UserAgent)
err := ua.handler.GetEncryptedCookieValue(r, ua.cookieName, userAgent)
if err != nil {
return nil, errors.ThrowPermissionDenied(err, "HTTP-YULqH4", "cannot read user agent cookie")
}
return userAgent, nil
}
func (ua *UserAgentHandler) SetUserAgent(w http.ResponseWriter, agent *UserAgent) error {
err := ua.handler.SetEncryptedCookie(w, ua.cookieName, agent)
if err != nil {
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
}
return nil
}