[Phase 1] Auth gateway: OSIS migration, secp256k1 signature auth, OAuth enforcement, identity injection #8
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_proxy#8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The OAuth2 infrastructure (providers, sessions, domain route auth policies) is fully implemented in the DB layer and management API, but not yet enforced in the proxy request path. Requests to OAuth-protected domain routes are forwarded directly without checking
auth_modeor validating session cookies.Current State
What exists and works:
domain_routes(withauth_mode: "none"|"bearer"|"oauth",oauth_provider,allowed_emails),oauth_providers,oauth_sessionsoauth.set_provider,oauth.list_providers,oauth.remove_provider)domain_route.add/update/remove)oauth.rs:make_auth_url(),exchange_code_for_email(), session cookie utilities, CSRF state storeauth.rs)What is NOT wired up:
dispatch_domain_route()inproxy.rs:58forwards requests directly without checkingroute.auth_mode. No code validates a session cookie, redirects to OAuth, or blocks unauthenticated requests on OAuth-protected routes.No
/oauth/callbackHTTP endpoint —make_auth_url()generates redirect URLs to{base}/oauth/callbackbutbuild_proxy_router()inlib.rshas no route to handle the callback.No identity header injection —
forward_axum()inweblib/src/proxy/forward.rsonly strips theHostheader and addsX-Forwarded-Prefix. After OAuth validation, it should inject identity headers so backend services know WHO the user is.Implementation Plan
1. OAuth enforcement middleware in
dispatch_domain_route()Before forwarding, check
route.auth_mode:"none"→ forward directly (current behavior)"bearer"→ validateAuthorization: Bearer <token>(existingauth.rslogic)"oauth"→ check for validhero_proxy_sessioncookie → if missing/expired, redirect to OAuth provider → if valid, continue to forwarding2.
/oauth/callbackendpointAdd to
build_proxy_router():The plumbing already exists in
oauth.rs— just needs an Axum handler that callsexchange_code_for_email(), checksroute.allowed_emails, creates anoauth_sessionsrow, and sets the cookie.3. Identity header injection in
forward_axum()After session validation, inject headers into the forwarded request:
This enables the two-layer auth model where hero_proxy handles web/session auth at the edge, and backend services (e.g., znzfreezone_backend) handle API-level auth (secp256k1, API keys, per-RPC-method enforcement) while knowing the authenticated user's identity.
Context
znzfreezone_code/znzfreezone_backendissues #28 (backend auth consolidation) and #29 (cross-context auth architecture)weblibcrate's own auth system (auth/middleware.rs,auth/session.rs) is for protectinghero_proxy_uiadmin panel, NOT for proxy domain route enforcement — these are separate concernsFiles to modify
crates/hero_proxy_server/src/proxy.rs— add auth_mode checking indispatch_domain_route()crates/hero_proxy_server/src/lib.rs— add/oauth/callbackroute tobuild_proxy_router()crates/weblib/src/proxy/forward.rs— inject identity headers inforward_axum()andforward_hyper()crates/hero_proxy_server/src/oauth.rs— add callback handler functionfor 3. Identity header injection in forward_axum()
i'm not sure its a good idea to have then the api services also handle their own auth, instead it would be cleaner if authenticity of secp256k1, API keys etc were also checked in proxy. authorization given such identities can always be handled in the proxied services themselves. as such maybe we need to add functionality to proxy to also verify keypairs and endpoints which they can do this with. Also maybe even payload signature verification would be helpful to verify the integrity of payloads.
Reply: Moving All Authentication to hero_proxy
Agreed — the cleaner model is:
This is a significant but clean shift. Here's what it looks like concretely.
Current State in znzfreezone_backend
The backend currently does both authentication AND authorization in two middleware layers:
auth_middleware.rs— extracts auth from HTTP headers:X-Public-Key+X-Signature+X-Signature-Timestamp→ secp256k1 (admin ops)X-Api-Key→ API key (reseller ops)Authorization: Bearer→ JWT (user ops)k256crate for ECDSA verification,sha2for hashingrpc_auth.rs— enforces per-method + per-context rules:AdminRegistrationService.*→ must be signed by admin pubkeyRegistrationRequestService.*→ must have valid API key or signaturereseller_{their_id}contextSHA256(timestamp + body)signature with 5-minute replay windowThe key insight: authentication (crypto verification) and authorization (business rules) are mixed together in
rpc_auth.rs. They should be separated.Proposed: hero_proxy Handles All Authentication
New
auth_modevalues for DomainRouteExtend the current
"none" | "bearer" | "oauth"enum:"apikey"— validateX-Api-Keyheader against proxy's key store"signature"— verify secp256k1 signature (X-Public-Key+X-Signature+X-Signature-Timestamp) including payload integrity"apikey+signature"— accept either (current znzfreezone reseller behavior)New
api_keystable in proxy SQLiteNew
allowed_pubkeyson DomainRouteSimilar to how
OAuthProviderhasallowed_emails, domain routes need:Payload signature verification in
dispatch_domain_route()This is the most involved piece. For
auth_mode: "signature", before forwarding:X-Public-Key,X-Signature,X-Signature-Timestampheaderssecp256k1_verify(pubkey, SHA256(timestamp + body), signature)allowed_pubkeysif setThis requires adding
k256(orsecp256k1) +sha2as dependencies to hero_proxy.Identity headers injected after authentication
Regardless of auth method, on success hero_proxy injects:
Backend services then TRUST these headers (since they come from the proxy on the internal network / Unix socket) and only do authorization.
API key management via OpenRPC
New management methods:
apikey.create { owner_id, route_id?, scopes? }→ returns the raw key (only shown once)apikey.list { owner_id?, route_id? }→ list keys (prefix + metadata only)apikey.revoke { key_id }→ deactivate a keyapikey.rotate { key_id }→ revoke old + create newWhat Changes in znzfreezone_backend
auth_middleware.rssimplifies dramatically:rpc_auth.rskeeps ONLY the authorization logic:The
k256,sha2,hexdependencies move OUT of znzfreezone_backend and INTO hero_proxy.Migration Concern: API Key Storage
Currently API keys are stored in znzfreezone_backend's OSIS
ApiKeymodel (application data). Moving to proxy means:X-Proxy-Api-Key-Ownerapi_keystableapikey.createon hero_proxy instead of creating an OSIS recordThis is cleaner long-term because API keys become a proxy-level concern shared across ALL backend services, not just znzfreezone.
Security Consideration: Trust Boundary
The backend trusts
X-Proxy-*headers because:X-Proxy-*headers from external requests before injecting its own (important!)Updated Issue Scope
This issue should now cover:
OAuth enforcement in dispatch(original scope — still needed)(original scope — still needed)/oauth/callbackendpointapi_keystable, validation in dispatch, management APIk256dependency, payload verification,allowed_pubkeysX-Proxy-*headersX-Proxy-*headers — security: never trust externally-supplied proxy headersImplementation Order
Reply: OSIS as Data Layer + Keypair-Only Auth
1. OSIS Instead of SQLite for hero_proxy
Agreed this is the right direction. znzfreezone_backend already proves the pattern — it embeds OSIS directly via
AxumRpcServerwith a 107KB.oschemafile, full codegen, and SmartID-based storage.hero_proxy currently uses SQLite with 7 tables:
domain_routes— routing rules with auth_modetls_domains— Let's Encrypt / selfsigned configoauth_providers— Google, GitHub, custom OAuthoauth_sessions— cookie-based sessionslisteners— TCP listener configsssh_tunnels— remote port forwardingsettings— key/value storeAll of these map cleanly to OSIS types. The migration path:
Step 1: Define
proxy.oschemaStep 2: Add
hero_rpc_osisdependency (same pattern as znzfreezone_backend)Step 3: Replace
ProxyDb(SQLite) with generated OSIS domain, embed viaAxumRpcServerStep 4: Remove
rusqlitedependency entirelyPractical considerations:
find_route_for_host()with wildcard matching) needs to be done in application code. This is already a simple loop over ~10-50 routes, so no performance concern..otomlfile you can inspect directly.2. Keypair-Only Auth (Drop API Keys)
Strongly agree. This simplifies everything:
What we need now:
auth_mode: "signature"on DomainRoutesecp256k1(pubkey, SHA256(timestamp + body), signature)fromX-Public-Key+X-Signature+X-Signature-Timestampheadersallowed_pubkeysfield — which pubkeys are allowed for this route (empty = any valid sig)X-Proxy-User-Pubkey+X-Proxy-Auth-Method: signature+X-Proxy-Signature-Verified: trueand forwardk256+sha2dependencies to hero_proxyWhat we defer:
API key tables, API key management, API key validation— not needed if resellers use keypairsReseller flow becomes:
What stays in znzfreezone_backend:
What moves OUT of znzfreezone_backend:
verify_secp256k1_signature()fromauth_middleware.rs— proxy does this nowverify_request_signature()(timestamp + body verification) fromrpc_auth.rs— proxy does this nowk256,sha2,hexdependencies — move to hero_proxyznzfreezone_backend's
auth_middleware.rssimplifies to readingX-Proxy-User-Pubkeyfrom headers and trusting it (since the connection is via Unix socket from proxy).Revised Issue Scope
This Issue is Phase 1 — Critical Path
Full roadmap posted on znzfreezone_backend#29. This issue (hero_proxy#8) is the critical path — everything downstream depends on it.
Execution order within this issue:
Blocked by this issue:
Wire OAuth enforcement into domain route dispatch + identity header injectionto [Phase 1] Auth gateway: OSIS migration, secp256k1 signature auth, OAuth enforcement, identity injectionImplemented: auth_mode enforcement + signature auth (commit
a4aa070)This commit wires
auth_modeenforcement intodispatch_domain_route()as described in comment #15682 (Phase 2 & 3, OSIS migration deferred to separate PR).What's new
Auth enforcement on domain routes — previously
auth_modewas stored but never checked. Now every proxied request goes through auth verification:nonebearerAuthorization: Bearer <token>against server auth tokensignatureX-Public-Key,X-Signature,X-Signature-Timestamp) with 5-minute replay windowoauthSecurity: header stripping + identity injection
X-Proxy-*headers are stripped (prevents spoofing)X-Proxy-Auth-Method,X-Proxy-User-Email,X-Proxy-User-Pubkey,X-Proxy-Signature-VerifiedNew endpoint:
/oauth/callbackSchema changes
allowed_pubkeysfield on DomainRoute (comma-separated hex-encoded secp256k1 public keys)"signature"auth_mode andallowed_pubkeysparameterFiles changed (10 files, +853/-148)
Cargo.toml— k256, sha2, hex depssrc/signature.rs— new secp256k1 verification module (4 unit tests)src/db.rs— allowed_pubkeys field + migrationsrc/proxy.rs— auth enforcement, header stripping, OAuth callbacksrc/lib.rs— module + route registrationsrc/auth.rs— /oauth/callback bypassopenrpc.json— spec updatesRemaining