feat(ingester): rfc 0026 green b2 — ingest authn + tenant binding - #398
Conversation
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>
|
Warning Review limit reached
Next review available in: 11 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 (5)
📝 WalkthroughWalkthroughThis PR adds RFC 0026 bearer-token authentication and per-batch tenant binding enforcement to the OTLP log ingest pipeline. It introduces an ChangesRFC 0026 Auth and Tenant Binding
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 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 gRPCAuthInterceptor, 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.
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.
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
📒 Files selected for processing (9)
crates/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/src/receiver/pipeline.rscrates/ourios-ingester/src/receiver/tenant.rscrates/ourios-ingester/tests/rfc0026_auth.rscrates/ourios-server/src/main.rscrates/ourios-server/src/receiver.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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
RFC 0026 green slice (b2) — ingest enforcement (Scenarios RFC0026.2, .3, and the ingest half of .5):
ourios_ingester::receiver::auth): both listeners shareauthenticate_bearer(RFC 6750 shape, case-insensitive scheme, one undifferentiated rejection — missing vs malformed vs unknown would be a probing oracle). gRPC runs it in anAuthInterceptorinstalled viaLogsServiceServer::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.IngestPipeline::ingest_bound): with a binding attached, everyResourceLogsgroup's derived tenant (RFC 0003 §6.3 derivation unchanged) must fall inside the token's set, else the whole batch rejects with the newReceiveError::TenantDenied⇒PERMISSION_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.ingestdelegates with no binding — byte-for-byte today's behavior (RFC0026.6's parity claim, asserted fully in the query slice).ReceiverConfig.auth, threaded fromServerConfig.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—.2drives 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 (itswith_interceptorplacement is the before-decode guarantee);.3asserts whole-batch denial with in-set siblings, journal unchanged (no partial acceptance), 403 over HTTP andPERMISSION_DENIEDthroughLogsReceiver::exportwith the extension the interceptor attaches;.5-ingest wildcards across arbitrary tenants..7stays a stub for the telemetry slice.Invariants / hazards
.2/.3.AuthBindingcarries 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
Bug Fixes