Documentation
¶
Overview ¶
Package auth implements authentication and authorization for the HTTP surface: token auth, TLS, and (post-v1) OIDC. Every code path here is security-sensitive; see gm-m2-auth formula.
Index ¶
- Constants
- func BearerAuth(v Verifier) func(http.Handler) http.Handler
- func BearerOrCookieAuth(v Verifier, cs *CookieSigner) func(http.Handler) http.Handler
- func BootstrapLoginHandler(cs *CookieSigner, bs *BootstrapStore) http.Handler
- func CertFingerprint(der []byte) string
- func CheckCertKeyPermissions(path string) (os.FileMode, string, error)
- func DefaultTokenPath() (string, error)
- func GenerateSelfSignedCert(opts SelfSignedCertOptions) (tls.Certificate, string, error)
- func HashToken(token string) (string, error)
- func LoginHandler(cs *CookieSigner) http.Handler
- func NewToken() (string, error)
- func ReadHash(path string) (string, error)
- func VerifyHash(token, encoded string) (bool, error)
- func WriteHash(path, hash string) error
- type BootstrapStore
- type CookieSigner
- type HashVerifier
- type PlainVerifier
- type SelfSignedCertOptions
- type Verifier
Constants ¶
const DefaultSessionTTL = 24 * time.Hour
DefaultSessionTTL is how long an issued session cookie is valid.
const SessionCookieName = "gemba_session"
SessionCookieName is the cookie name the server issues after a successful bearer exchange at POST /api/auth/login.
Variables ¶
This section is empty.
Functions ¶
func BearerAuth ¶
BearerAuth returns middleware that verifies the Authorization header using v. Retained for call sites (including tests) that only need bearer auth; BearerOrCookieAuth is the full gm-e5.2 surface.
func BearerOrCookieAuth ¶
BearerOrCookieAuth returns middleware that accepts either:
- an Authorization: Bearer <token> header whose token v accepts, or
- a signed session cookie (when cs is non-nil) whose signature matches.
Missing and invalid credentials return 401 with a stable JSON envelope. The middleware runs before route lookup so unauthenticated clients cannot distinguish existing routes from absent ones via 401-vs-404 oracles (gm-b3 / gm-99g).
func BootstrapLoginHandler ¶
func BootstrapLoginHandler(cs *CookieSigner, bs *BootstrapStore) http.Handler
BootstrapLoginHandler exchanges a valid one-time bootstrap token for the same signed session cookie issued by LoginHandler. It intentionally does not accept the primary bearer token; that remains behind /api/auth/login.
func CertFingerprint ¶
CertFingerprint returns the SHA-256 fingerprint of a DER-encoded certificate as a colon-separated uppercase hex string — the canonical "SHA256:AA:BB:..." form curl and browsers display when an operator inspects a self-signed cert.
func CheckCertKeyPermissions ¶
CheckCertKeyPermissions warns if the operator-supplied key file is world- or group-readable. Returns the permission bits and a non-empty warning string when the file is more permissive than 0600; callers log the warning rather than abort so an operator running under a deliberately-relaxed umask can still boot. Missing files return an error so the caller fails fast.
func DefaultTokenPath ¶
DefaultTokenPath returns the on-disk location where the primary token hash lives: ~/.gemba/tokens/primary. Returns an error if the user's home directory cannot be resolved.
func GenerateSelfSignedCert ¶
func GenerateSelfSignedCert(opts SelfSignedCertOptions) (tls.Certificate, string, error)
GenerateSelfSignedCert produces an ephemeral ECDSA P-256 self-signed TLS certificate suitable for `gemba serve --tls-self-signed`. The returned tls.Certificate can be plugged directly into a tls.Config via Certificates; the SHA-256 fingerprint is provided so the operator can pin it (printed at startup per the gm-e5.3 DoD).
ECDSA P-256 is chosen over RSA for speed: a self-signed cert is regenerated on every boot, so a 200ms RSA keygen would slow startup noticeably while contributing nothing to security (the cert is trusted via fingerprint pinning, not chain validation).
func HashToken ¶
HashToken computes an argon2id PHC-formatted hash of token. The returned string contains parameters + salt + hash so Verify does not need any configuration state.
func LoginHandler ¶
func LoginHandler(cs *CookieSigner) http.Handler
LoginHandler returns an http.Handler that issues a signed session cookie. It expects the surrounding middleware to have already verified the request's bearer token; the handler itself just stamps a fresh cookie. Exchanging a valid cookie for a new cookie is harmless and acts as a session refresh.
func NewToken ¶
NewToken returns a hex-encoded 256-bit random token suitable for use as a bearer credential. The return value is the plaintext token and must be shown to the operator exactly once; only its hash should be persisted.
func ReadHash ¶
ReadHash loads the argon2id PHC string from path. Returns ("", nil) if the file does not exist, so callers can distinguish "no token yet" from genuine I/O failure.
func VerifyHash ¶
VerifyHash checks token against a PHC-encoded argon2id hash using a constant-time comparison. It is not constant-time against argon2id's own parameters; the parameters themselves are not secret.
Types ¶
type BootstrapStore ¶
type BootstrapStore struct {
// contains filtered or unexported fields
}
BootstrapStore owns one short-lived, one-time launch token. The token is intended for URL fragments printed at startup; the server never sees the fragment until the SPA explicitly exchanges it.
func NewBootstrapStore ¶
func NewBootstrapStore(token string, expiresAt time.Time) *BootstrapStore
type CookieSigner ¶
type CookieSigner struct {
// contains filtered or unexported fields
}
CookieSigner issues and verifies signed session cookies using HMAC-SHA256 over a randomly generated per-process key.
Cookie format:
base64url(exp_unix_seconds_decimal) + "." + base64url(HMAC-SHA256(key, exp_bytes))
The key is regenerated every process start so restarting the server invalidates every outstanding cookie. Clients recover by re-exchanging their bearer at POST /api/auth/login.
func NewCookieSigner ¶
func NewCookieSigner() (*CookieSigner, error)
NewCookieSigner returns a CookieSigner with a random 256-bit key and the default session TTL. An error is returned only if the system RNG fails.
func (*CookieSigner) Issue ¶
func (c *CookieSigner) Issue(now time.Time) string
Issue builds a signed cookie value whose expiry is now+TTL.
func (*CookieSigner) SetSessionCookie ¶
func (c *CookieSigner) SetSessionCookie(w http.ResponseWriter, r *http.Request, value string)
SetSessionCookie writes the signed cookie onto w with secure defaults. Secure=true is set only when the request arrived over TLS so the cookie still works on the localhost-only default bind.
func (*CookieSigner) TTL ¶
func (c *CookieSigner) TTL() time.Duration
TTL returns how long an issued cookie is valid.
type HashVerifier ¶
type HashVerifier struct {
// contains filtered or unexported fields
}
HashVerifier checks the presented token against an argon2id hash loaded from a file. It re-reads the file when its mtime changes so that `gemba auth token rotate` takes effect without restarting the server.
Rotate-while-running contract:
- Verify() stats the file on every call; a changed mtime triggers a re-read of the hash before the comparison.
- If the file is deleted, subsequent Verify() calls return false.
func NewHashVerifier ¶
func NewHashVerifier(path string) *HashVerifier
NewHashVerifier returns a Verifier that reads a PHC argon2id hash from path on demand.
func (*HashVerifier) Verify ¶
func (h *HashVerifier) Verify(presented string) bool
Verify returns true when the stored hash matches presented under argon2id. Returns false (never logging the presented token) if the hash file is missing, unreadable, malformed, or the hash does not match.
type PlainVerifier ¶
type PlainVerifier struct {
// contains filtered or unexported fields
}
PlainVerifier compares a presented token against a fixed plaintext reference using constant-time comparison. It exists so tests (and the legacy --auth=token flow that accepted a preset --auth-token value) can exercise the middleware without going through the hashed-file path.
func NewPlainVerifier ¶
func NewPlainVerifier(token string) *PlainVerifier
NewPlainVerifier returns a Verifier backed by a fixed token.
func (*PlainVerifier) Verify ¶
func (p *PlainVerifier) Verify(presented string) bool
Verify returns true if presented matches the configured token.
type SelfSignedCertOptions ¶
type SelfSignedCertOptions struct {
// BindHost, when non-empty, is added to the SAN list. If it parses
// as an IP it lands in IPAddresses; otherwise it lands in DNSNames.
// Empty values are dropped.
BindHost string
// ExtraIPs supplements the default 127.0.0.1 / ::1 SANs.
ExtraIPs []net.IP
// ExtraDNS supplements the default "localhost" SAN.
ExtraDNS []string
// ValidFor controls cert lifetime; zero defaults to 1 year per the
// gm-e5.3 DoD ("regenerates on missing or expired cert").
ValidFor time.Duration
}
SelfSignedCertOptions configures GenerateSelfSignedCert. The zero value is usable: it produces a cert valid for 1 year with a SAN list that covers "localhost" and 127.0.0.1 / ::1 — enough for a local-only gemba serve. Callers passing --listen with a remote bind should add the bind IP to ExtraIPs so curl / browsers don't reject the SAN.