Skip to content

feat(auth): rfc 0029 green (ingest binding) — async auth layer on both listeners - #424

Merged
jensholdgaard merged 3 commits into
mainfrom
rfc0029-green-ingest-binding
Jul 7, 2026
Merged

feat(auth): rfc 0029 green (ingest binding) — async auth layer on both listeners#424
jensholdgaard merged 3 commits into
mainfrom
rfc0029-green-ingest-binding

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jul 7, 2026

Copy link
Copy Markdown
Owner

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 (oidc feature on ourios-ingester): static store first (constant-time, never awaits), then OIDC verification; open mode only when nothing is configured.
  • gRPC — tower layer replaces the sync interceptor: AuthLayer/AuthService on the tonic stack can await a JWKS refetch while still gating before message decode; rejection is a trailers-only UNAUTHENTICATED (grpc-status 16); the AuthBinding rides request extensions into the handler exactly as before.
  • HTTP: the handler awaits the same resolver (one-line semantic change).
  • Server: ingest_resolver at startup — OIDC discovery failure is a startup error naming auth.oidc (§3.2: with no cached keys nothing could ever verify). The binary ships the oidc features unconditionally; config decides at runtime.
  • RFC 0026 suites migrated mechanism, not contract: same assertions (undifferentiated rejection, binding attachment, wire-level UNAUTHENTICATED); the served arm now installs the layer exactly as production does (Server::builder().layer(...)).
  • Supply chain: oidc-on-the-binary pulls webpki-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)

Arm Result
Startup discovery against a loopback fixture issuer ✅ receiver serves
Bearer-less gRPC export UNAUTHENTICATED pre-decode (the layer answers; the handler never runs)
Verified JWT (ourios_tenants: ["acme"]), in-set batch ✅ acks
Same JWT, out-of-set batch ✅ whole-batch PERMISSION_DENIED, no WAL append
Bearer-less HTTP POST ✅ 401 through the same resolver
Unreachable issuer (closed loopback port) ✅ startup exits nonzero naming auth.oidc

Full 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_binding whole-batch enforcement before the WAL append — no new tenant-scoping path. §3.4 WAL-before-ack untouched. Telemetry: rejections keep counting on ourios.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 stays red until the ladder completes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for configurable OIDC-based authentication alongside existing static tokens.
    • Unified request authentication across HTTP and gRPC ingestion.
    • Receiver startup now validates OIDC settings early and fails fast if the issuer cannot be reached.
  • Bug Fixes

    • Improved handling of missing or invalid credentials with consistent unauthorized responses.
    • Tightened tenant enforcement so requests are accepted or rejected based on resolved access claims.

…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>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jensholdgaard, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e13527f5-bc86-45f8-bb16-9887a893af9a

📥 Commits

Reviewing files that changed from the base of the PR and between 96d07c9 and a57e03e.

📒 Files selected for processing (2)
  • crates/ourios-ingester/tests/rfc0026_telemetry.rs
  • crates/ourios-server/tests/it/rfc0029_oidc.rs
📝 Walkthrough

Walkthrough

Introduces AuthResolver, unifying static-token and OIDC-based bearer authentication for the ingester's gRPC and HTTP receivers. Replaces the tonic AuthInterceptor with a Tower AuthLayer/AuthService. Updates server startup to build resolvers with OIDC discovery, adjusts configs, dependencies, tests, and license exceptions.

Changes

AuthResolver and layer-based authentication

Layer / File(s) Summary
Dependencies and feature flags
crates/ourios-ingester/Cargo.toml, crates/ourios-server/Cargo.toml, deny.toml
Adds tower/http dependencies and an oidc feature; enables oidc feature on ourios-ingester/ourios-core in ourios-server; adds jsonwebtoken/p256/rand/base64 dev-dependencies; adds a webpki-roots license exception.
AuthResolver core implementation
crates/ourios-ingester/src/receiver.rs, crates/ourios-ingester/src/receiver/auth.rs
Adds AuthResolver struct with optional static TokenStore and OIDC verifier, constructors (static_only, with_oidc), is_open(), and async authenticate() trying static then OIDC verification; re-exports AuthResolver.
gRPC AuthLayer/AuthService
crates/ourios-ingester/src/receiver/grpc.rs
Replaces AuthInterceptor with Tower AuthLayer/AuthService that reads the Authorization header, resolves auth via AuthResolver, inserts AuthBinding into extensions or returns UNAUTHENTICATED with rejection telemetry.
HTTP receiver wiring
crates/ourios-ingester/src/receiver/http.rs
Changes HttpConfig.auth and AppState to use AuthResolver; handler now calls state.auth.authenticate(...); default config uses open-mode AuthResolver::static_only(None).
Server startup and config wiring
crates/ourios-server/src/main.rs, crates/ourios-server/src/receiver.rs
Adds ingest_resolver performing OIDC discovery at startup; ReceiverConfig.auth becomes AuthResolver; gRPC server uses AuthLayer instead of interceptor; updates related tests.
Ingester test updates
crates/ourios-ingester/tests/it/rfc0026_auth.rs, crates/ourios-ingester/tests/rfc0026_telemetry.rs
Updates RFC0026 tests to use AuthResolver/AuthLayer, replacing AuthInterceptor-based assertions.
RFC0029 OIDC end-to-end tests
crates/ourios-server/tests/it/rfc0029_oidc.rs
Adds ingest_binding module with a loopback OIDC issuer, JWT minting helpers, and end-to-end tests for gRPC/HTTP enforcement and startup failure on unreachable issuer.

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
Loading

Possibly related PRs

  • jensholdgaard/ourios#398: Same RFC 0026 ingest auth flow, replacing AuthInterceptor/authenticate_bearer wiring with AuthResolver/AuthLayer.
  • jensholdgaard/ourios#422: Shares the same OIDC auth model (AuthConfig/OidcConfig) that AuthResolver's OIDC verifier and closed-mode gating depend on.
  • jensholdgaard/ourios#390: AuthResolver's static-token check builds directly on the TokenStore/authenticate work introduced there.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits the Summary, Related, and Checklist sections. Add the required Summary and Related sections, then include the Checklist items for fmt, clippy, tests, docs, and RFC links.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: async auth layers for RFC 0029 ingest binding on both listeners.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc0029-green-ingest-binding

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AuthInterceptor with a tower AuthLayer/AuthService that can await auth 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.

Comment thread crates/ourios-ingester/tests/rfc0026_telemetry.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3c8a715 and 96d07c9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • crates/ourios-ingester/Cargo.toml
  • crates/ourios-ingester/src/receiver.rs
  • crates/ourios-ingester/src/receiver/auth.rs
  • crates/ourios-ingester/src/receiver/grpc.rs
  • crates/ourios-ingester/src/receiver/http.rs
  • crates/ourios-ingester/tests/it/rfc0026_auth.rs
  • crates/ourios-ingester/tests/rfc0026_telemetry.rs
  • crates/ourios-server/Cargo.toml
  • crates/ourios-server/src/main.rs
  • crates/ourios-server/src/receiver.rs
  • crates/ourios-server/tests/it/rfc0029_oidc.rs
  • deny.toml

Comment thread crates/ourios-server/tests/it/rfc0029_oidc.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Comment thread crates/ourios-server/tests/it/rfc0029_oidc.rs
Comment thread crates/ourios-server/tests/it/rfc0029_oidc.rs
Comment thread crates/ourios-ingester/tests/rfc0026_telemetry.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 tower AuthLayer installed via Server::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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants