From 8e097749582be1b99b1ddc31239ef5f248446d35 Mon Sep 17 00:00:00 2001 From: gearnode Date: Wed, 5 Mar 2025 17:34:42 +0100 Subject: [PATCH] Sign session cookie to avoid tapping Signed-off-by: gearnode --- pkg/api/console/v1/auth_handlers.go | 9 ++++++++- pkg/api/console/v1/resolver.go | 27 ++++++++++++++++----------- pkg/probod/auth_config.go | 23 ++++++++++++++++++++--- pkg/probod/probod.go | 8 ++++++++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/pkg/api/console/v1/auth_handlers.go b/pkg/api/console/v1/auth_handlers.go index 01cf76e10..a6ef2c4e7 100644 --- a/pkg/api/console/v1/auth_handlers.go +++ b/pkg/api/console/v1/auth_handlers.go @@ -176,8 +176,15 @@ func LogoutHandler(usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFu return } + // Verify the cookie signature + originalValue, err := verifyCookieValue(cookie.Value, authCfg.CookieSecret) + if err != nil { + http.Error(w, "Invalid session cookie", http.StatusBadRequest) + return + } + // Parse the session ID - sessionID, err := gid.ParseGID(cookie.Value) + sessionID, err := gid.ParseGID(originalValue) if err != nil { http.Error(w, "Invalid session ID", http.StatusBadRequest) return diff --git a/pkg/api/console/v1/resolver.go b/pkg/api/console/v1/resolver.go index 2cc8b6e8f..a880831c7 100644 --- a/pkg/api/console/v1/resolver.go +++ b/pkg/api/console/v1/resolver.go @@ -44,6 +44,7 @@ type ( CookieDomain string CookiePath string SessionDuration time.Duration + CookieSecret string } Resolver struct { @@ -143,20 +144,24 @@ func graphqlHandler(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg // Extract session from cookie cookie, err := r.Cookie(authCfg.CookieName) if err == nil && cookie.Value != "" { - // Parse the session ID - sessionID, err := gid.ParseGID(cookie.Value) + // Verify the cookie signature + originalValue, err := verifyCookieValue(cookie.Value, authCfg.CookieSecret) if err == nil { - // Get the session - session, err := usrmgrSvc.GetSession(r.Context(), sessionID) + // Parse the session ID + sessionID, err := gid.ParseGID(originalValue) if err == nil { - // Add session to context - ctx = context.WithValue(ctx, sessionContextKey, session) - - // Get the user - user, err := usrmgrSvc.GetUserBySession(r.Context(), sessionID) + // Get the session + session, err := usrmgrSvc.GetSession(r.Context(), sessionID) if err == nil { - // Add user to context - ctx = context.WithValue(ctx, userContextKey, user) + // Add session to context + ctx = context.WithValue(ctx, sessionContextKey, session) + + // Get the user + user, err := usrmgrSvc.GetUserBySession(r.Context(), sessionID) + if err == nil { + // Add user to context + ctx = context.WithValue(ctx, userContextKey, user) + } } } } diff --git a/pkg/probod/auth_config.go b/pkg/probod/auth_config.go index eb5a9b836..a1dc7099f 100644 --- a/pkg/probod/auth_config.go +++ b/pkg/probod/auth_config.go @@ -30,16 +30,15 @@ type ( CookieHTTPOnly bool `json:"cookie-http-only"` CookieDomain string `json:"cookie-domain"` CookiePath string `json:"cookie-path"` + CookieSecret string `json:"cookie-secret"` } ) -// GetPepperBytes returns the pepper as a byte array func (c authConfig) GetPepperBytes() ([]byte, error) { if c.Pepper == "" { return nil, fmt.Errorf("pepper cannot be empty") } - // If the pepper is base64 encoded, decode it if decoded, err := base64.StdEncoding.DecodeString(c.Pepper); err == nil { if len(decoded) < 32 { return nil, fmt.Errorf("decoded pepper must be at least 32 bytes long") @@ -47,10 +46,28 @@ func (c authConfig) GetPepperBytes() ([]byte, error) { return decoded, nil } - // Otherwise use the raw string as the pepper if len(c.Pepper) < 32 { return nil, fmt.Errorf("pepper must be at least 32 bytes long") } return []byte(c.Pepper), nil } + +func (c authConfig) GetCookieSecretBytes() ([]byte, error) { + if c.CookieSecret == "" { + return nil, fmt.Errorf("cookie secret cannot be empty") + } + + if decoded, err := base64.StdEncoding.DecodeString(c.CookieSecret); err == nil { + if len(decoded) < 32 { + return nil, fmt.Errorf("decoded cookie secret must be at least 32 bytes long") + } + return decoded, nil + } + + if len(c.CookieSecret) < 32 { + return nil, fmt.Errorf("cookie secret must be at least 32 bytes long") + } + + return []byte(c.CookieSecret), nil +} diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 674fc444b..137e958cd 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -80,6 +80,7 @@ func New() *Implm { CookieHTTPOnly: true, CookieDomain: "localhost", CookiePath: "/", + CookieSecret: "this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes", }, AWS: awsConfig{ Region: "us-east-1", @@ -122,6 +123,12 @@ func (impl *Implm) Run( return fmt.Errorf("cannot get pepper bytes: %w", err) } + // Validate cookie secret + _, err = impl.cfg.Auth.GetCookieSecretBytes() + if err != nil { + return fmt.Errorf("cannot get cookie secret bytes: %w", err) + } + awsConfig := awsconfig.NewConfig( l, httpclient.DefaultPooledClient( @@ -162,6 +169,7 @@ func (impl *Implm) Run( CookieDomain: impl.cfg.Auth.CookieDomain, CookiePath: impl.cfg.Auth.CookiePath, SessionDuration: time.Duration(impl.cfg.Auth.SessionDuration) * time.Hour, + CookieSecret: impl.cfg.Auth.CookieSecret, }, }, )