Documentation
¶
Overview ¶
Package session implements Culvert's HMAC-SHA256-signed session tokens and the revocation machinery behind them (ADR-0002 extraction; engine was session.go in package main).
The package owns: the signing key (with synchronized access — the key is written at runtime by the cluster snapshot sync while the MAC path reads it concurrently), token encode/decode (base64(json).HMAC), the revocation list (token- and user-level, with lazy expiry eviction, gossip export/merge, and optional disk persistence), the session TTL, and jti generation.
package main keeps: the HTTP cookie helpers (they need isSecureRequest and the Identity hub type), the startup wiring (env/config key priority — env is read in the startup shim per the slice convention), and the Session→Identity conversion (Identity is a main hub type this package must not import).
Index ¶
- Constants
- Variables
- func Encode(s *Session) (string, error)
- func HasSigningKey() bool
- func InitRandomKey()
- func MAC(data string) string
- func NewJti() string
- func RevocationsPath() string
- func SetRevocationsPath(p string)
- func SetSigningKey(key []byte)
- func SetTTL(d time.Duration)
- func SigningKey() []byte
- func TTL() time.Duration
- type RevocationEntry
- type RevocationList
- func (r *RevocationList) Count() int
- func (r *RevocationList) ExportRevocations() []RevocationEntry
- func (r *RevocationList) IsRevoked(token string) bool
- func (r *RevocationList) IsUserRevoked(username string) bool
- func (r *RevocationList) LoadRevocations() error
- func (r *RevocationList) MergeRevocations(entries []RevocationEntry) int
- func (r *RevocationList) Revoke(token string, exp time.Time)
- func (r *RevocationList) RevokeUser(username string)
- func (r *RevocationList) SaveRevocations() error
- func (r *RevocationList) SwapForTest() (restore func())
- type Session
Constants ¶
const CookieName = "ps_session"
CookieName is the session cookie name.
Variables ¶
var Revoked = NewRevocationList()
Revoked is the process-wide revocation list consulted by Decode.
Functions ¶
func Encode ¶
Encode serialises session data, signs it with HMAC-SHA256, and returns a cookie-safe string: base64(json).HMAC.
func InitRandomKey ¶
func InitRandomKey()
InitRandomKey installs a fresh random 32-byte key. Fails loudly: session machinery that cannot generate randomness is unsafe to keep running.
func MAC ¶
MAC computes the base64url HMAC-SHA256 of data under the current signing key. Exported for tests that hand-craft legacy-shaped tokens.
func NewJti ¶
func NewJti() string
NewJti returns a fresh 128-bit random session identifier, hex encoded. Stamped into every newly-issued Session by the cookie issuers so two same-second logins for the same user produce distinct b64 payloads (and therefore distinct HMACs and cookie values). The only collision space is 2^128, far beyond birthday-bound concerns for any plausible deployment.
crypto/rand failure is treated the same as InitRandomKey's: fail loudly.
func RevocationsPath ¶
func RevocationsPath() string
RevocationsPath returns the configured persistence path ("" = disabled).
func SetRevocationsPath ¶
func SetRevocationsPath(p string)
SetRevocationsPath configures where SaveRevocations/LoadRevocations persist.
func SetSigningKey ¶
func SetSigningKey(key []byte)
SetSigningKey installs the HMAC key. Callers own validation (length/hex); the cluster snapshot path and the startup shim both funnel through here. Synchronized: the DP config-sync path replaces the key at runtime while concurrent requests compute MACs (previously an unguarded global write — latent data race, fixed with this extraction).
func SigningKey ¶
func SigningKey() []byte
SigningKey returns a copy of the current HMAC key (nil when unset). Used by the CP snapshot export (hex-encoded into ConfigSnapshot.SessionHMAC) and by test snapshot/restore helpers.
Types ¶
type RevocationEntry ¶
type RevocationEntry struct {
Token string `json:"token"`
Expiry int64 `json:"expiry"` // Unix timestamp
}
RevocationEntry is a single revoked session token for gRPC gossip.
type RevocationList ¶
type RevocationList struct {
// contains filtered or unexported fields
}
RevocationList tracks revoked session tokens and user-level revocations.
func NewRevocationList ¶
func NewRevocationList() *RevocationList
NewRevocationList returns an empty list (used by tests to swap the package singleton, mirroring the bl pointer-swap idiom).
func (*RevocationList) Count ¶
func (r *RevocationList) Count() int
Count returns the number of active revoked sessions.
func (*RevocationList) ExportRevocations ¶
func (r *RevocationList) ExportRevocations() []RevocationEntry
ExportRevocations returns all non-expired revocation entries for syncing.
func (*RevocationList) IsRevoked ¶
func (r *RevocationList) IsRevoked(token string) bool
IsRevoked reports whether the token is currently revoked, lazily evicting entries whose original expiry has passed.
func (*RevocationList) IsUserRevoked ¶
func (r *RevocationList) IsUserRevoked(username string) bool
IsUserRevoked returns true if all sessions for the given username are revoked.
func (*RevocationList) LoadRevocations ¶
func (r *RevocationList) LoadRevocations() error
LoadRevocations reads revocations from disk and merges them.
func (*RevocationList) MergeRevocations ¶
func (r *RevocationList) MergeRevocations(entries []RevocationEntry) int
MergeRevocations imports remote revocation entries (from other cluster nodes). Only adds entries that are not yet expired and not already present.
func (*RevocationList) Revoke ¶
func (r *RevocationList) Revoke(token string, exp time.Time)
Revoke marks a token (its b64 payload) as revoked until exp.
func (*RevocationList) RevokeUser ¶
func (r *RevocationList) RevokeUser(username string)
RevokeUser invalidates all sessions for a username. Active session cookies for this user are rejected until the revocation expiry (max session TTL). Called when a user account is deleted (Finding 5.2).
func (*RevocationList) SaveRevocations ¶
func (r *RevocationList) SaveRevocations() error
SaveRevocations writes all non-expired revocations to disk as JSON.
func (*RevocationList) SwapForTest ¶
func (r *RevocationList) SwapForTest() (restore func())
SwapForTest replaces the list's maps with empty ones and returns a restore function. Mirrors the snapshot/restore pattern documented in CLAUDE.md for tests that touch shared revocation state.
type Session ¶
type Session struct {
Sub string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
Groups []string `json:"grp,omitempty"`
Provider string `json:"pvd"`
Role string `json:"role,omitempty"` // UI admin role: admin|operator|viewer
Exp int64 `json:"exp"` // Unix timestamp
// Jti is a 128-bit random session identifier (hex-encoded). Added in
// Phase C5.1 to make the JSON payload unique per login even when two
// logins for the same user happen in the same wall-clock second —
// without Jti the (Sub, Role, Exp-in-seconds) tuple yielded a
// byte-identical payload, identical b64, identical HMAC, and an
// effectively-revoked cookie if any prior login of that tuple had
// been revoked. omitempty keeps legacy cookies (issued before C5.1)
// decoding cleanly: their Jti unmarshals to "" and the field is
// simply absent on the wire.
Jti string `json:"jti,omitempty"`
}
Session is the payload stored inside the signed proxy session cookie. It carries just enough identity data to reconstruct an Identity object without talking to the IdP on every request. (The Session→Identity conversion lives in package main — Identity is a main hub type.)