Skip to content

Releases: govengers/auth

v1.3.0

Choose a tag to compare

@mxcd mxcd released this 31 Oct 10:40
486bfa3

New

  • session.Guard.Identify(ctx, request): the middleware's session lookup without a response writer, for long-lived connections such as a WebSocket re-validating the cookies of its upgrade request. It ends an expired impersonation, runs a due refresh and reports to the Observer exactly like RequireAPI; a refused refresh ends the session in the store and surfaces as the error. See Sessions. (#13)

No breaking changes.

v1.2.0

Choose a tag to compare

@mxcd mxcd released this 31 Oct 00:06
42c7c99

Moves the library to github.com/govengers/wide-event-logger v1. Upgrade the logger together with auth; see docs/migration.md (D62). The GitHub wiki now mirrors the docs of every release.

Breaking changes

  • ginadapter / wide events: Observe and Require record on events of github.com/govengers/wide-event-logger v1 instead of github.com/mxcd/go-wide-event-logger v0.3.x. Mount the logger's v1 gin middleware; with the old middleware the code still compiles, but nothing is recorded.
  • ginadapter: RedactQuery takes the v1 *wideevent.Event and is a deprecated no-op: the v1 middleware redacts every query value itself unless the parameter is in Options.QueryAllowlist.
  • forwardauth image events: duration replaces response.latency and response.latency_ms; name is http.request; response.gin_errors is a list; request.id is generated when X-Request-ID is missing or invalid and echoed in the response header; method, host, path and client IP are capped at 32, 256, 2048 and 64 bytes. Adjust dashboards and alerts that read the old fields.

Changed

  • forwardauth records only service.version and forward.* itself; the logger records and caps the request fields, and each field is emitted once.
  • The wiki is published by CI for every newest stable release from that release's docs/wiki (D63).

Image: ghcr.io/govengers/forwardauth:1.2.0.

v1.1.1

Choose a tag to compare

@mxcd mxcd released this 30 Oct 23:47
ef43813

Security fix for the password provider. No API or configuration change.

Fixed

  • password: a login pending its second factor (or its TOTP enrollment) is now bound to the credentials its password step verified. When the password or the second factor changes before the second step, TOTP verify ends the pending login with no_pending_login, and setup and enable of a pending enrollment refuse the same way. Before, an attacker holding the old password plus a TOTP or backup code could complete a login started before a password reset within PendingTTL (default 5 minutes) and keep a session that passed every refresh (D61). Found by security review run 3.

Upgrade notes

  • Logins that are waiting for their second factor while you deploy carry no credential stamp and answer no_pending_login once; the user logs in again.

Image: ghcr.io/govengers/forwardauth:1.1.1 (the image has no password provider; it is rebuilt for the release).

v1.1.0

Choose a tag to compare

@mxcd mxcd released this 30 Oct 23:19
b85c298

Security hardening from audit run 2 and impersonation as a library construct. Some changes break code or configuration that relied on the old behaviour; each is listed below and in docs/migration.md under "After v1.0.0". Decisions D42-D60 in docs/design.md.

New: impersonation (session.Impersonator, D60)

  • session.NewImpersonator(&session.ImpersonatorOptions{Guard, Load, ...}) is the only way to start or stop an impersonation: POST /auth/impersonation {"subject"} and DELETE /auth/impersonation behind the Guard, or Start/Stop in your own handlers.
  • Actor (default authorization.IsAdmin()) decides who may impersonate, Target (default Not(IsAdmin())) who may be impersonated. Always enforced: no nesting, active impersonator and target, no self-impersonation, a swap against the identity the Guard read.
  • An impersonation ends when stopped, with the session, or after MaximumDuration (default 1 h); the next request runs as the impersonator again.
  • JWTs of an impersonating identity carry the impersonator as the RFC 8693 act claim; Verify returns it as Identity.Impersonator (without roles).
  • New client codes already_impersonating, not_impersonating, unknown_impersonation_target, self_impersonation; outcomes impersonation_start, impersonation_stop; wide-event keys auth.impersonation.* and auth.impersonated_subject.

Breaking changes

  • session: SaveIdentity and SwapIdentity return an error for an identity or impersonator of another provider or subject than the session holds, and a login cannot save an impersonating identity. Replace code that set Identity.Impersonator itself with session.Impersonator; impersonations stored the old way end at their next request (D60).
  • jwt: Sign refuses an identity whose impersonator is inactive; authorization.Real(...) on a verified token now sees the act impersonator (D60). VerifierOptions.Audience is required (D54). JWKSURL must be https (plain http on loopback only) unless InsecureJWKSURL: true (D54). New MaximumKeyAge (default 24 h): keys are no longer trusted forever while the JWKS is unreachable (D55).
  • oidc: Issuer: oidc.GoogleIssuer needs AllowedEmailDomains, OnLogin or AllowAnyAccount: true; with Google, a non-consumer allowed domain also requires the hd claim, so only Workspace accounts of that domain pass (D56).
  • password: sessions are bound to a credential stamp; a password change or TOTP enable/disable ends the user's other sessions, and every password user logs in once more after the upgrade (D45).
  • session: ParseKey and NewStore refuse an encryption key that repeats one byte (all zero included) (D44); SaveIdentity and SwapIdentity no longer reset the Guard's refresh clock (D44).
  • authorization: every check except IsAuthenticated refuses an identity whose impersonator is inactive (D47).
  • forwardauth image: JWT_AUDIENCE required with JWT_ENABLED=true (D54); Google needs OIDC_ALLOWED_EMAIL_DOMAINS, an enforced KEYCLOAK_REQUIRED_* list or OIDC_ALLOW_ANY_ACCOUNT=true (D56); percent-encoded route variants answer 404 and /auth/me refuses inactive identities (D50); the checks refuse an identity whose impersonator is inactive (D60); request.client_ip is the peer address unless TRUSTED_PROXIES lists it (D51); a malformed SESSION_REDIS_ADDRESS fails validation at boot.

Security fixes (audit run 2)

  • Memory backend evicts through an expiry heap (O(log n)), so login floods no longer stall every request (D42).
  • Redis backend: an evicted session index reads as logged out, so a logout always takes effect (D43).
  • Guard: application saves no longer postpone the refresh; a failed refresh claim calls the provider only when it committed (D44).
  • OIDC access-token roles only for the same sub; raw Keycloak role claims are not copied into JWTs (D48); logout work bounded at 10 s; the Prometheus adapter never panics on invalid label values (D49).
  • GitHub OAuth tokens are revoked whenever the Guard or the provider ends a session (session.Revoker, D57).

Forward-auth image

  • Redis Sentinel (SESSION_REDIS_SENTINEL_*), TLS including mTLS (SESSION_REDIS_TLS_*) and an ACL username (SESSION_REDIS_USERNAME) (D52, D53).
  • TRUSTED_PROXIES (default none) decides whose X-Forwarded-For the logged client IP trusts (D51).
  • Published only after lint, tests, e2e and vulncheck passed, with a signed build provenance attestation; :latest, :1 and :1.1 move only for the newest stable tag (D59).

Image: ghcr.io/govengers/forwardauth:1.1.0.

v1.0.0

Choose a tag to compare

@mxcd mxcd released this 30 Oct 10:45
4dd33be

First release of github.com/govengers/auth. It merges mxcd/go-basicauth (v1.5.x), mxcd/oidc-fwd-auth (v0.9.x) and mxcd/github-fwd-auth (v1.4.x) into one module and adds authorization checks, resource permissions, API keys, a gin and a Prometheus adapter, a Redis session backend and the forward-auth image. There are no compatibility shims; docs/migration.md maps every old symbol and environment variable and gives a recipe per library.

Breaking changes against all three predecessors:

  • New import path github.com/govengers/auth/...; Go 1.26.
  • Sessions are server-side for every provider; the cookie holds a random ID only; the lifetime is StoreOptions.MaximumAge (Store.MaximumAge(), SESSION_MAXIMUM_AGE_SECONDS). Existing sessions are invalid: every user logs in once after the upgrade. The cookie-only mode, gorilla/sessions and the signing keys are gone; one 32-byte EncryptionKey remains (openssl rand -base64 32). An own session.Backend implements five methods, CompareAndSwap included; ErrSessionRevoked is session.ErrRevoked.
  • Session cookie: a Secure, host-only cookie on / is named __Host-<CookieName> (__Host-auth_session, __Host-forwardauth_session); SameSite Default is refused, and Strict is refused by oidc.New and github.New (the callback would arrive without the cookie), so it is for password-only stores. A refresh that keeps failing for longer than GuardOptions.MaximumUnverifiedAge (default 1 h, SESSION_MAXIMUM_UNVERIFIED_AGE_SECONDS) ends the session.
  • One identity type, auth.Identity, replaces basicauth.User, oidc.SessionData, jwt.User and the GitHub session user. One session.Guard replaces the per-provider middleware; there is no global middleware and no PathRules, you mount the Guard on route groups.
  • Route prefixes: /auth/password/*, /auth/oidc/<name>/*, /auth/github/*, each a PathPrefix of plain segments without a trailing /. Redirect URIs are derived from ExternalURL; register the new callback URLs at your IdP and GitHub. ErrorURL and PostLogoutURL become absolute and refuse fragments.
  • One error body {"code","message"} with stable codes (tfa_* became totp_*, unauthorized became unauthenticated); translated Messages are gone.
  • Hooks are auth.Hook (OnLogin) and auth.LogoutHook; UserTransformer is an application middleware after the Guard. Libraries no longer log; they call an optional auth.Observer.
  • password: default minimum length 12 characters without composition rules; Storage takes a context.Context, gains UpdatePasswordHash (compare-and-swap), ConsumeTOTPStep (a totp_step column; every TOTP code is accepted once) and ErrUserExists, matches usernames and emails ignoring case, and SetTOTP returns (bool, error): it enables TOTP only while it is off, atomically (... WHERE id = $1 AND NOT totp_enabled); OnLogin must keep UserID; TOTP.Required enrolls during login and registration; TOTP routes exist only with TOTP.Enabled, and enrolled users cannot log in while it is off; backup codes are checked only with "backupCode": true (no fallthrough from a wrong TOTP code); MaximumAttempts is a lockout across logins until a verified code or ResetTOTPAttempts, with no unlimited mode; new limits Period <= 3600, Skew <= 10, BackupCodeCount <= 20, backup codes <= 128 bytes and >= 1,000,000 distinct codes; Argon2id hashes on outdated parameters are rehashed at login.
  • OIDC and GitHub: PKCE (and nonce) always on, GitHub logout is GET, logout (also password logout) refuses Sec-Fetch-Site: cross-site with 403, so logout links must live on the app's own site, GitHub AllowedTeams is required (an empty list no longer admits everyone), GitHub Scopes must include one that lists teams (read:org, write:org, admin:org, user, repo), the endpoints OIDC discovery names must be https (or loopback http), no component follows a redirect of its token, discovery, JWKS, Keycloak, GitHub revocation or GitHub user and team endpoint (point the URLs at the final location), Keycloak enrichment moved from gocloak to oidc/keycloak and defaults to client credentials, GitHub UserProfile and friends are gone (Identity.Teams, HTTPClient).
  • API keys are named, at least 16 bytes, unique, and grant only the roles and permissions you give them (the old "API key bypasses admin" behaviour needs Roles: []string{auth.RoleAdmin}). Authorization: Bearer is matched in any case, so a foreign bearer token on a route behind apikey is 401.
  • JWT: RS512 only, jwx v3, exp required, default lifetime 5 minutes (was 30), issuer defaults to EXTERNAL_URL, GitHub sub is the numeric user ID and the login is preferred_username (uid is gone), JWKS at /auth/jwks, no generated signing key, Sign refuses an inactive identity, the verifier uses only RSA JWKS keys with an RS*/PS* alg and a kid and answers 401 with WWW-Authenticate: Bearer.
  • Forward-auth: one image ghcr.io/govengers/forwardauth selected by PROVIDER replaces oidc-fwd-auth and github-fwd-auth; renamed environment variables, new routes (/auth/ui, /auth/api, /auth/jwks), an explicit Traefik router for /auth (without the checks and probes) is required, Google multi-provider mode is removed, JWT_PRIVATE_KEY is required when JWT is on, API keys need API_KEYS_ENABLED=true (API_KEYS alone is ignored) and carry permissions (API_KEY_PERMISSIONS) but no roles. EXTERNAL_URL is an origin without a path, a route with a trailing / is 404 (no redirect), SESSION_SAME_SITE takes lax or none, SHUTDOWN_DELAY_SECONDS at most 10, the boot fails when Redis does not answer or when a plain http EXTERNAL_URL off loopback comes without SESSION_INSECURE_COOKIE=true; list X-API-Key next to Authorization in authResponseHeaders.

New: authorization (incl. HasPermission), resource permissions (auth.Permission, ginadapter.RequireMethodPermission, the JWT permissions claim), apikey, ginadapter, prometheusadapter, session/redisbackend, TOTP enrollment during login, Refreshers that re-check teams and users every minute, IdentityHandler, wide-event and Prometheus reporting in the image, ginadapter.RedactQuery (redacts query values on the wide event; mount it or OAuth callback codes reach your logs). Agent docs: llms.txt, docs/wiki/, runnable examples/.

Migration guide: docs/migration.md. Image: ghcr.io/govengers/forwardauth:1.0.0.