Skip to content

feat(ingester): rfc 0026 green b2 — ingest authn + tenant binding - #398

Merged
jensholdgaard merged 6 commits into
mainfrom
rfc0026-green-b2-ingest-enforcement
Jul 6, 2026
Merged

feat(ingester): rfc 0026 green b2 — ingest authn + tenant binding#398
jensholdgaard merged 6 commits into
mainfrom
rfc0026-green-b2-ingest-enforcement

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jul 6, 2026

Copy link
Copy Markdown
Owner

What

RFC 0026 green slice (b2) — ingest enforcement (Scenarios RFC0026.2, .3, and the ingest half of .5):

  • Authentication before wire decode (ourios_ingester::receiver::auth): both listeners share authenticate_bearer (RFC 6750 shape, case-insensitive scheme, one undifferentiated rejection — missing vs malformed vs unknown would be a probing oracle). gRPC runs it in an AuthInterceptor installed via LogsServiceServer::with_interceptor — interceptors see only metadata, so rejection precedes the protobuf decode; HTTP runs it as the handler's first step, before media-type dispatch, decompression, and decode. Missing/unknown bearer ⇒ UNAUTHENTICATED / 401, and the WAL is never touched.
  • Tenant binding before the WAL append (IngestPipeline::ingest_bound): with a binding attached, every ResourceLogs group's derived tenant (RFC 0003 §6.3 derivation unchanged) must fall inside the token's set, else the whole batch rejects with the new ReceiveError::TenantDeniedPERMISSION_DENIED / 403 — before encode, fan-out, and any WAL work, so there is no partial success and nothing durable. Zero-record groups still have their claimed tenant checked. The error carries the token name (for the slice-d audit event) but renders only the tenant — no token value on any surface.
  • Open mode is untouched: no store ⇒ the interceptor and handler pass through unbound and ingest delegates with no binding — byte-for-byte today's behavior (RFC0026.6's parity claim, asserted fully in the query slice).
  • Server wiring: ReceiverConfig.auth, threaded from ServerConfig.auth (feat(server): rfc 0026 green a — the token store (RFC0026.1) #390) into both listeners.

Scenario mapping

crates/ourios-ingester/tests/rfc0026_auth.rs.2 drives the real router in-process over a capturing journal (401s, journal empty, then 200 + append with the right token) and the interceptor directly for gRPC (its with_interceptor placement is the before-decode guarantee); .3 asserts whole-batch denial with in-set siblings, journal unchanged (no partial acceptance), 403 over HTTP and PERMISSION_DENIED through LogsReceiver::export with the extension the interceptor attaches; .5-ingest wildcards across arbitrary tenants. .7 stays a stub for the telemetry slice.

Invariants / hazards

  • §3.4 WAL-before-ack: rejection paths are strictly before the WAL append; nothing is acked that isn't durable, and nothing durable is created for a denied batch — asserted on the journal in .2/.3.
  • §3.7 multi-tenancy: this is the enforcement half the invariant has been waiting for — identity now constrains attribute-derived tenancy (RFC 0003 §9 closes as specified: derivation unchanged, bounded by the token's set).
  • Secret hygiene: AuthBinding carries name + tenant set only; TenantDenied's Display renders the tenant, never the token, with a test asserting no token value on the error surface.

Checks run locally

cargo fmt --all --check, cargo clippy -p ourios-ingester -p ourios-server --all-targets --all-features -- -D warnings, cargo test -p ourios-ingester -p ourios-server --all-features — all green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added bearer-token authentication for OTLP log ingestion over HTTP and gRPC.
    • Added tenant-bound access checks so authenticated requests can be limited to approved tenants.
    • Wired authentication support into the server startup and receiver configuration.
  • Bug Fixes

    • Unauthorized requests now return clearer auth errors.
    • Tenant-denied writes now map to forbidden/permitted-denied responses instead of reaching ingestion.

Bearer authentication on both OTLP listeners before wire decode (a
tonic interceptor and the HTTP handler's first step; one
undifferentiated UNAUTHENTICATED/401), and the §3.2 per-batch tenant
binding in the pipeline: every ResourceLogs group's derived tenant
must fall inside the authenticated token's set or the whole batch is
rejected (PERMISSION_DENIED/403) before any WAL work — no partial
success. Open mode (no store) is byte-for-byte today's behavior.
RFC0026.2/.3/.5-ingest go green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jensholdgaard
jensholdgaard requested a review from Copilot July 6, 2026 06:02
@coderabbitai

coderabbitai Bot commented Jul 6, 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: 11 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: 193239ce-82b9-4533-92fb-cf4739d12f5f

📥 Commits

Reviewing files that changed from the base of the PR and between 351d1e8 and e7e83c3.

📒 Files selected for processing (5)
  • crates/ourios-ingester/Cargo.toml
  • crates/ourios-ingester/src/receiver/auth.rs
  • crates/ourios-ingester/src/receiver/grpc.rs
  • crates/ourios-ingester/src/receiver/tenant.rs
  • crates/ourios-ingester/tests/rfc0026_auth.rs
📝 Walkthrough

Walkthrough

This PR adds RFC 0026 bearer-token authentication and per-batch tenant binding enforcement to the OTLP log ingest pipeline. It introduces an auth module with AuthBinding/authenticate_bearer/check_binding, wires authentication into gRPC and HTTP receivers, adds a TenantDenied error mapped to permission-denied/403 responses, and updates server configuration and tests accordingly.

Changes

RFC 0026 Auth and Tenant Binding

Layer / File(s) Summary
Auth module: bearer parsing and tenant binding check
crates/ourios-ingester/src/receiver.rs, crates/ourios-ingester/src/receiver/auth.rs, crates/ourios-ingester/src/receiver/tenant.rs
Adds AuthBinding, Unauthenticated, authenticate_bearer, parse_bearer, and check_binding; re-exports these from receiver.rs; makes TenantResolutionError::at_resource crate-visible for reuse by check_binding.
Pipeline ingest_bound and TenantDenied error
crates/ourios-ingester/src/receiver/pipeline.rs
Adds IngestPipeline::ingest_bound performing tenant authorization via check_binding before WAL writes; ingest delegates to it with no binding; adds ReceiveError::TenantDenied with Display/source handling.
gRPC AuthInterceptor and error mapping
crates/ourios-ingester/src/receiver/grpc.rs
Adds AuthInterceptor gating LogsService requests via authenticate_bearer, attaching AuthBinding to extensions; export now calls ingest_bound with the extracted binding; maps TenantDenied to PermissionDenied.
HTTP handler auth and error mapping
crates/ourios-ingester/src/receiver/http.rs
Extends HttpConfig/AppState with optional auth store; handle_logs authenticates before decoding, returning 401 on failure; ingest now uses ingest_bound; maps TenantDenied to 403 Forbidden.
Server config and startup wiring
crates/ourios-server/src/receiver.rs, crates/ourios-server/src/main.rs
Adds ReceiverConfig.auth, wires it into gRPC with_interceptor and HTTP router config, adds bind_listeners helper, and a warn_if_open_mode startup warning when auth is unset.
RFC0026 integration tests
crates/ourios-ingester/tests/rfc0026_auth.rs
Implements previously-ignored RFC0026.2/.3/.5 scenarios covering authentication rejection/acceptance, tenant-denied batch rejection, and wildcard tenant binding across HTTP and gRPC transports.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthInterceptor
  participant LogsService
  participant IngestPipeline
  participant TokenStore

  Client->>AuthInterceptor: request with authorization metadata
  AuthInterceptor->>TokenStore: authenticate_bearer(token)
  TokenStore-->>AuthInterceptor: AuthBinding or Unauthenticated
  AuthInterceptor-->>LogsService: forward request (with binding) or reject
  LogsService->>IngestPipeline: ingest_bound(request, binding)
  IngestPipeline->>IngestPipeline: check_binding(resource_logs, tenant set)
  IngestPipeline-->>LogsService: Ok(count) or TenantDenied
  LogsService-->>Client: Response / PermissionDenied / Unauthenticated
Loading

Possibly related PRs

  • jensholdgaard/ourios#134: Extends the IngestPipeline/ReceiveError from this retrieved PR by adding ingest_bound and the TenantDenied error path.
  • jensholdgaard/ourios#136: Builds on the same gRPC LogsService::export path by adding AuthInterceptor and switching to pipeline.ingest_bound.
  • jensholdgaard/ourios#378: Implements the concrete RFC0026 .2/.3/.5 test scenarios that were previously left as #[ignore] stubs in that PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: RFC 0026 ingest authentication and tenant binding.
Description check ✅ Passed The description is detailed and mostly matches the template, though it omits explicit Related and Checklist sections.
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 rfc0026-green-b2-ingest-enforcement

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 0026 ingest-side enforcement by adding bearer authentication gates before OTLP wire decode (HTTP + gRPC) and enforcing tenant binding on the ingest pipeline before any WAL append, while preserving open-mode behavior when no auth store is configured.

Changes:

  • Add shared bearer authentication (authenticate_bearer) plus a gRPC AuthInterceptor, ensuring rejection happens before protobuf decode.
  • Add pipeline-level tenant binding enforcement (ingest_bound + ReceiveError::TenantDenied) and map it to 403 / PERMISSION_DENIED.
  • Extend server wiring to thread the optional token store through receiver config and warn once on startup when running in open mode.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/ourios-server/src/receiver.rs Thread optional auth store into both listeners; install gRPC interceptor; pass auth into HTTP router.
crates/ourios-server/src/main.rs Extract open-mode startup warning into a helper; pass auth store into receiver config.
crates/ourios-ingester/tests/rfc0026_auth.rs Implement RFC0026 ingest authn/binding/wildcard scenario tests across HTTP + gRPC paths.
crates/ourios-ingester/src/receiver/tenant.rs Expose TenantResolutionError::at_resource within the crate for binding checks.
crates/ourios-ingester/src/receiver/pipeline.rs Add ingest_bound and enforce binding before WAL work; introduce ReceiveError::TenantDenied.
crates/ourios-ingester/src/receiver/http.rs Authenticate before dispatch/decompression/decode; pass binding into pipeline; map tenant denial to 403.
crates/ourios-ingester/src/receiver/grpc.rs Add AuthInterceptor; plumb binding via request extensions; map tenant denial to PERMISSION_DENIED.
crates/ourios-ingester/src/receiver/auth.rs New shared authn + tenant-binding enforcement module (no token-value surfaces).
crates/ourios-ingester/src/receiver.rs Export new auth module + helpers from the receiver facade.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ourios-ingester/src/receiver/http.rs
Comment thread crates/ourios-ingester/src/receiver/grpc.rs
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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/receiver/pipeline.rs
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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/receiver/auth.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: 3

🤖 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-ingester/src/receiver/grpc.rs`:
- Around line 53-70: The auth rejection path in AuthInterceptor::call is
currently silent, so rejected credentials are not observable. Add a
low-cardinality counter or structured log/event when authenticate_bearer returns
an Unauthenticated error, and make sure the same signal is emitted in the HTTP
path for ReceiveError::TenantDenied as well. Keep the signal generic and
non-sensitive, and wire it through the existing AuthInterceptor and rejection
handling code so operators can monitor spikes in denied requests.

In `@crates/ourios-ingester/src/receiver/pipeline.rs`:
- Around line 210-219: The `TenantDenied` authz failure path in `ingest_bound`
still exits before any observability is emitted, so add receiver-level handling
for denied bindings in the `record_batch(...)`/`ingest_bound` flow. Use the
existing `super::auth::check_binding` result to detect `TenantDenied`, then
record a denial counter/metric and emit a structured log before returning the
error, keeping the success path unchanged for accepted batches.

In `@crates/ourios-ingester/tests/rfc0026_auth.rs`:
- Around line 222-231: The current auth test bypasses the real gRPC server path
by inserting AuthBinding directly into Request::extensions_mut(), so it does not
verify metadata-to-interceptor propagation. Update the test around
LogsReceiver::export to exercise LogsServiceServer::with_interceptor(...)
end-to-end, sending request metadata that the interceptor converts into the
AuthBinding extension before the handler runs. Keep the existing
permission-denied assertion, but route the request through the served server
path so the full auth handoff is covered.
🪄 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: bbfd3d8f-fb94-47da-afde-124b39c1fbfa

📥 Commits

Reviewing files that changed from the base of the PR and between a000daa and 351d1e8.

📒 Files selected for processing (9)
  • 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/src/receiver/pipeline.rs
  • crates/ourios-ingester/src/receiver/tenant.rs
  • crates/ourios-ingester/tests/rfc0026_auth.rs
  • crates/ourios-server/src/main.rs
  • crates/ourios-server/src/receiver.rs

Comment thread crates/ourios-ingester/src/receiver/grpc.rs
Comment thread crates/ourios-ingester/src/receiver/pipeline.rs
Comment thread crates/ourios-ingester/tests/rfc0026_auth.rs
derive_for_group is the single tenant-derivation source shared by
fan_out and the binding check (no drift), and RFC0026.2 gains a
served LogsServiceServer::with_interceptor arm over a real socket so
the metadata -> interceptor -> extension -> handler handoff is covered.

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 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/receiver/grpc.rs
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 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/receiver/tenant.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 10 out of 10 changed files in this pull request and generated no new comments.

@jensholdgaard
jensholdgaard merged commit 7cabed1 into main Jul 6, 2026
22 checks passed
@jensholdgaard
jensholdgaard deleted the rfc0026-green-b2-ingest-enforcement branch July 6, 2026 07:53
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