feat(auth): rfc 0029 green (ingest binding) — async auth layer on both listeners - #424
Conversation
…h listeners The §3.3 ingest binding: the RFC 0029 verifier resolves credentials in front of the unchanged RFC 0026 enforcement, on both OTLP listeners. - `AuthResolver` (ourios-ingester, `oidc` feature): the constant-time static store first, then OIDC verification; open mode only when nothing is configured. Async because an unseen-kid miss may refetch the JWKS; the static path never awaits. - gRPC: the sync `AuthInterceptor` is replaced by a tower `AuthLayer`/`AuthService` on the tonic stack — it can await the resolver while still gating before message decode, rejects with a trailers-only UNAUTHENTICATED (grpc-status 16), and threads the `AuthBinding` through request extensions exactly as before. The RFC 0026 suites migrated mechanism (assertions preserved; the served arm now installs the layer precisely as production does). - HTTP: the handler awaits the same resolver. - Server: `ingest_resolver` builds the resolver at startup; OIDC discovery failure (unreachable issuer / mismatch / unusable JWKS) is a startup error naming auth.oidc, per §3.2. The product binary enables the `oidc` features unconditionally; config decides at runtime. - Supply chain: enabling oidc on the binary pulls webpki-roots (the rustls Mozilla CA store) into the default graph — CDLA-Permissive-2.0 allowed as a crate-scoped deny.toml exception. Served evidence (`it::rfc0029_oidc::ingest_binding`): the binary discovers against a loopback fixture issuer at startup; a bearer-less gRPC export is UNAUTHENTICATED before decode; a verified JWT ingests within its tenant claim and an out-of-set batch is whole-batch PERMISSION_DENIED with no WAL append; the HTTP listener 401s through the same resolver; an unreachable issuer exits startup nonzero naming auth.oidc. Full gate: 975 passed / 0 failed, clippy -D warnings, rustdoc, fmt, cargo-deny (advisories/bans/licenses/sources). Query/MCP binding (3b) and the Dex arm (.7) complete the ladder; the RFC stays red until then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughIntroduces ChangesAuthResolver and layer-based authentication
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthLayer
participant AuthResolver
participant OidcVerifier
participant LogsReceiver
Client->>AuthLayer: gRPC request with Authorization header
AuthLayer->>AuthResolver: authenticate(authorization)
alt static token valid
AuthResolver-->>AuthLayer: AuthBinding
else oidc fallback
AuthResolver->>OidcVerifier: verify(token)
OidcVerifier-->>AuthResolver: AuthBinding or error
AuthResolver-->>AuthLayer: AuthBinding or Unauthenticated
end
alt authenticated
AuthLayer->>LogsReceiver: forward request with AuthBinding in extensions
LogsReceiver-->>Client: export response
else rejected
AuthLayer-->>Client: UNAUTHENTICATED response
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR implements RFC 0029 ingest binding (slice 3a) by introducing an async-capable auth resolver and moving gRPC authentication from a synchronous tonic interceptor to a tower layer, so OIDC verification (including JWKS refetch) can occur pre-decode on both OTLP listeners.
Changes:
- Add
AuthResolver(static token store + optional OIDC verifier) and use it for both HTTP and gRPC ingest authentication. - Replace gRPC
AuthInterceptorwith a towerAuthLayer/AuthServicethat canawaitauth resolution before message decode. - Add served-binary integration tests for OIDC ingest binding and update cargo-deny licensing exception for
webpki-roots.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| deny.toml | Adds a crate-scoped license exception for webpki-roots (CDLA-Permissive-2.0). |
| crates/ourios-server/tests/it/rfc0029_oidc.rs | Adds served-binary integration tests for OIDC ingest binding and startup failure behavior. |
| crates/ourios-server/src/receiver.rs | Switches receiver auth configuration from static store to AuthResolver and installs the gRPC auth layer. |
| crates/ourios-server/src/main.rs | Builds the ingest AuthResolver at startup (including OIDC discovery) and wires it into the receiver. |
| crates/ourios-server/Cargo.toml | Enables oidc features and adds dev-deps for the served OIDC test fixture/minting. |
| crates/ourios-ingester/tests/rfc0026_telemetry.rs | Updates telemetry test to assert gRPC-layer unauth rejection behavior. |
| crates/ourios-ingester/tests/it/rfc0026_auth.rs | Migrates RFC 0026 ingest auth tests from interceptor-based gating to resolver/layer-based gating. |
| crates/ourios-ingester/src/receiver/http.rs | Switches HTTP authentication from static store to async AuthResolver. |
| crates/ourios-ingester/src/receiver/grpc.rs | Introduces AuthLayer/AuthService to perform async auth resolution pre-decode. |
| crates/ourios-ingester/src/receiver/auth.rs | Adds AuthResolver (static-first, optional OIDC) with async authenticate. |
| crates/ourios-ingester/src/receiver.rs | Re-exports AuthResolver from the receiver module. |
| crates/ourios-ingester/Cargo.toml | Adds tower + http deps and introduces an oidc feature that forwards to ourios-core/oidc. |
| Cargo.lock | Updates lockfile for new dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ourios-server/tests/it/rfc0029_oidc.rs`:
- Around line 483-489: The `unreachable` fixture in `rfc0029_oidc.rs` is racy
because the `TcpListener` is dropped before discovery starts, allowing another
process to reuse that port. Keep the listener alive through the OIDC discovery
step and make it fail deterministically by handling connections and closing them
immediately (or otherwise refusing requests), so the test remains stable
regardless of local port reuse.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2ea5780d-0fab-437c-84df-8a2067588206
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
crates/ourios-ingester/Cargo.tomlcrates/ourios-ingester/src/receiver.rscrates/ourios-ingester/src/receiver/auth.rscrates/ourios-ingester/src/receiver/grpc.rscrates/ourios-ingester/src/receiver/http.rscrates/ourios-ingester/tests/it/rfc0026_auth.rscrates/ourios-ingester/tests/rfc0026_telemetry.rscrates/ourios-server/Cargo.tomlcrates/ourios-server/src/main.rscrates/ourios-server/src/receiver.rscrates/ourios-server/tests/it/rfc0029_oidc.rsdeny.toml
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
crates/ourios-ingester/tests/it/rfc0026_auth.rs:144
- These doc comments still describe the served-stack auth path as an interceptor installed via
LogsServiceServer::with_interceptor, but the test now uses the towerAuthLayerinstalled viaServer::builder().layer(...). Updating the text keeps the test documentation accurate.
/// Scenario RFC0026.2 (served gRPC stack) — the metadata → interceptor →
/// extension → handler handoff over a real socket: the interceptor is
/// installed exactly as the server role installs it
/// (`LogsServiceServer::with_interceptor`), a missing/unknown bearer is
/// rejected before the handler, and a known bearer's batch lands.
What
RFC 0029 ingest binding (slice 3a) — the verifier (#423) now fronts both OTLP listeners, resolving OIDC bearers onto the unchanged RFC 0026 enforcement.
AuthResolver(oidcfeature on ourios-ingester): static store first (constant-time, never awaits), then OIDC verification; open mode only when nothing is configured.AuthLayer/AuthServiceon the tonic stack can await a JWKS refetch while still gating before message decode; rejection is a trailers-onlyUNAUTHENTICATED(grpc-status 16); theAuthBindingrides request extensions into the handler exactly as before.ingest_resolverat startup — OIDC discovery failure is a startup error namingauth.oidc(§3.2: with no cached keys nothing could ever verify). The binary ships theoidcfeatures unconditionally; config decides at runtime.UNAUTHENTICATED); the served arm now installs the layer exactly as production does (Server::builder().layer(...)).oidc-on-the-binary pullswebpki-roots(rustls Mozilla CA store) into the default graph — CDLA-Permissive-2.0 accepted as a crate-scoped deny.toml exception, not a global allow.Served evidence (
it::rfc0029_oidc::ingest_binding)UNAUTHENTICATEDpre-decode (the layer answers; the handler never runs)ourios_tenants: ["acme"]), in-set batchPERMISSION_DENIED, no WAL appendauth.oidcFull gate: 975 / 0, clippy
-D warnings, rustdoc, fmt, cargo-deny all four checks.Invariants
§3.7 tenancy: the OIDC-resolved binding flows through the same
check_bindingwhole-batch enforcement before the WAL append — no new tenant-scoping path. §3.4 WAL-before-ack untouched. Telemetry: rejections keep counting onourios.ingest.batches(error.type = unauthenticated) from the layer, as from the interceptor.Next: 3b (query/MCP binding — retires
enforcement_store()), then the Dex testcontainers arm (.7) and the status flip; the RFC staysreduntil the ladder completes.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes