Skip to content

feat(rbac-proxy): introducing rbac proxy worker - #353

Merged
sergiofilhowz merged 4 commits into
mainfrom
feat/rbac-proxy
Jun 29, 2026
Merged

feat(rbac-proxy): introducing rbac proxy worker#353
sergiofilhowz merged 4 commits into
mainfrom
feat/rbac-proxy

Conversation

@sergiofilhowz

@sergiofilhowz sergiofilhowz commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added the rbac-proxy worker, including a public RBAC-protected proxy endpoint, channel bridging, and live configuration updates.
    • Added a status probe showing host, port, connection count, RBAC status, and version.
    • Expanded tag/release support so rbac-proxy builds can be published and released.
  • Documentation

    • Added end-user documentation and setup guidance for the new worker and its end-to-end test workflow.
  • Tests

    • Added end-to-end and integration coverage for access control, discovery filtering, registrations, and websocket relay behavior.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jun 26, 2026 9:56pm
workers-tech-spec Ready Ready Preview, Comment Jun 26, 2026 9:56pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sergiofilhowz, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 33 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4bc86d0c-bd4c-4cd0-b811-c91f2cf08ea7

📥 Commits

Reviewing files that changed from the base of the PR and between dc55074 and 0324bfe.

⛔ Files ignored due to path filters (1)
  • rbac-proxy/tests/e2e/workers/harness/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • .gitignore
  • rbac-proxy/iii.worker.yaml
  • rbac-proxy/src/configuration.rs
  • rbac-proxy/src/engine_overrides.rs
  • rbac-proxy/src/interceptor.rs
  • rbac-proxy/src/manifest.rs
  • rbac-proxy/src/rbac.rs
  • rbac-proxy/tests/integration.rs
  • rbac-proxy/tests/manifest.rs
📝 Walkthrough

Walkthrough

Adds the rbac-proxy worker, its config and RBAC model, the websocket proxy and discovery-rewrite paths, a rebindable listener/startup flow, and new integration, E2E, CI, and release wiring.

Changes

rbac-proxy worker

Layer / File(s) Summary
Surface and manifest
rbac-proxy/Cargo.toml, rbac-proxy/build.rs, rbac-proxy/src/lib.rs, rbac-proxy/src/manifest.rs, rbac-proxy/src/functions/*, rbac-proxy/iii.worker.yaml, README.md, rbac-proxy/README.md, rbac-proxy/tests/support/mod.rs, rbac-proxy/tests/manifest.rs, rbac-proxy/tests/schemas.rs, rbac-proxy/tests/golden/schemas/rbac-proxy.status.json, tech-specs/2026-06-rbac-proxy-worker/presentation/src/*
Declares the crate, worker manifest, public status surface, and the manifest/schema snapshots that pin the published shape.
RBAC policy
rbac-proxy/src/rbac.rs, iii-permissions.yaml, rbac-proxy/README.md
Defines RBAC matching, session auth, remediation strings, and deny rules for the proxy’s internal reload hooks.
Config and startup
rbac-proxy/src/config.rs, rbac-proxy/src/configuration.rs, rbac-proxy/src/server.rs, rbac-proxy/src/main.rs, rbac-proxy/README.md
Defines runtime config defaults, config registration and reload, rebindable listener state, startup/shutdown wiring, and matching config docs.
Discovery rewrites
rbac-proxy/src/configuration.rs, rbac-proxy/src/engine_overrides.rs
Caches engine discovery data, invalidates the cache on function-set changes, and rewrites discovery responses for functions, workers, triggers, and registered triggers.
WebSocket proxy
rbac-proxy/src/proxy.rs, rbac-proxy/src/interceptor.rs, rbac-proxy/src/channels.rs
Implements the authenticated websocket proxy path, per-frame RBAC and middleware handling, and the /ws/channels/{channel_id} bridge.
Tests, E2E harness, and release wiring
rbac-proxy/tests/integration.rs, rbac-proxy/tests/e2e/..., rbac-proxy/tests/e2e/workers/harness/..., .github/workflows/create-tag.yml, .github/workflows/rbac-proxy-e2e.yml, .github/workflows/release.yml, rbac-proxy/README.md
Adds the integration test, E2E harness, CI workflows, release/tag trigger updates, and local test instructions.

Sequence Diagram(s)

sequenceDiagram
  participant DownstreamWorker
  participant Proxy as rbac-proxy::proxy
  participant Rbac as rbac-proxy::rbac
  participant IIIClient
  participant Engine as iii engine
  participant Interceptor

  DownstreamWorker->>Proxy: websocket upgrade request
  Proxy->>Rbac: resolve_session(headers, query, ip_address)
  Rbac->>IIIClient: trigger auth_function_id
  IIIClient->>Engine: auth trigger
  Engine-->>IIIClient: AuthResult
  IIIClient-->>Rbac: AuthResult
  Proxy->>Engine: open upstream websocket
  Proxy->>Interceptor: handle_downstream(text frame)
  Interceptor->>Engine: forward rewritten frame
  Engine-->>Interceptor: invocationresult
  Interceptor-->>DownstreamWorker: filtered response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • iii-hq/workers#110: Both PRs modify .github/workflows/create-tag.yml by changing the workflow_dispatch.inputs.worker option set.
  • iii-hq/workers#299: Both PRs touch the rbac-proxy E2E harness manifest at rbac-proxy/tests/e2e/workers/harness/iii.worker.yaml.

Suggested reviewers

  • andersonleal

Poem

I hopped through webs of code and light,
With RBAC gates and channels bright.
I sniffed the tags, then ran the test,
And rbac-proxy passed the quest. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding the new rbac-proxy worker.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 feat/rbac-proxy

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.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 28 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

The `description` in iii.worker.yaml contained an unquoted `engine:: `
which YAML parsed as a mapping value, breaking validate_worker.py and
the interface-smoke opt-out check. Quote the string.

Also apply `cargo fmt --all` to satisfy the rust formatting check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The harness package-lock.json is gitignored and generated at first
`npm install`, so `setup-node@v4` with `cache: 'npm'` against the
missing lockfile is a hard failure. Mirror the storage-e2e workflow
and omit the cache. run-tests.sh uses `npm install`, not `npm ci`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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: 18

🤖 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 @.github/workflows/rbac-proxy-e2e.yml:
- Around line 43-47: The installer step in the workflow pipes a mutable remote
script from the iii install endpoint directly into sh, which should be removed.
Update the “Install iii engine (next)” step to use a pinned, reproducible
installer source or a downloaded, verified artifact instead of executing the
live main branch script. Keep the change localized to the workflow job that
contains the iii installation step and preserve the PATH update after the
install completes.
- Line 23: The checkout step is persisting the GitHub token before executing
PR-controlled code, which can expose credentials to the test script. Update the
actions/checkout usage in the workflow to disable credential persistence, and
ensure the later ./run-tests.sh execution in the same job cannot read the token
from Git config. Keep the fix scoped to the checkout step and the job that runs
the PR branch code.

In `@rbac-proxy/iii.worker.yaml`:
- Line 7: The manifest description in the iii worker YAML is an unquoted scalar
that includes the text engine:: result, which can be parsed as YAML syntax and
break validation. Update the description field in the worker manifest so the
full value is quoted, keeping the existing wording intact, and verify the same
change is applied anywhere the iii worker description is defined to ensure the
manifest stays discoverable and valid.

In `@rbac-proxy/src/configuration.rs`:
- Line 71: This change is a formatting-only fix needed to satisfy cargo fmt
--all -- --check. Reformat the fetch_config function signature and the
tracing::warn! match arm in configuration.rs to match rustfmt output, and ensure
the related lines around the referenced block are updated consistently so CI can
pass without manual formatting diffs.

In `@rbac-proxy/src/engine_overrides.rs`:
- Line 68: The CI failure is due to formatting-only issues in
engine_overrides.rs, so run rustfmt over the affected expressions/macros to
normalize them. Update the code around the message formatting in the relevant
functions and blocks referenced by the diff, then reformat the rest of the
impacted spots consistently so cargo fmt --all passes cleanly.

In `@rbac-proxy/src/interceptor.rs`:
- Around line 218-236: The failure is formatting-only in the interceptor logic
around bare_id, call_hook, and apply_function_hook_mapping, so run rustfmt/cargo
fmt --all to normalize the affected expressions and macros in this area and the
other referenced spots, without changing behavior.
- Around line 456-460: The invocation result currently preserves the
engine-prefixed target, which can leak a self-invoke prefix like tenant::foo
back to callers expecting bare function names. Update the invocation result
construction in interceptor.rs, including the tokio::spawn block and the related
result handling around the other referenced section, so the function_id/value is
normalized by stripping any session/engine prefix before emitting the
invocationresult frame. Use the existing target/inv_id result-building logic as
the place to apply the prefix removal consistently for both regular and
middleware paths.
- Around line 270-321: The on_register_trigger flow in interceptor.rs is
forwarding trigger configs with an api_path that still starts with a leading
slash, which can create double-slash routes downstream. Update
on_register_trigger and/or apply_trigger_hook_mapping to validate the config
after hook remapping and reject registrations whose config.api_path begins with
a slash, using deny_trigger_registration with a clear message before forwarding.
Make sure the check applies to the RegisterTriggerInput path regardless of
whether the value came from the original payload or from call_hook mapping.

In `@rbac-proxy/src/lib.rs`:
- Around line 33-40: The fallback in redact_url currently returns the original
input when url::Url::parse fails, which can leak credentials in logs and status
output. Update redact_url in rbac-proxy::lib::redact_url so the Err(_) branch
never returns the raw URL; instead use a best-effort redaction approach or a
generic redacted placeholder, while keeping the successful parse path unchanged.

In `@rbac-proxy/src/manifest.rs`:
- Around line 37-50: The test block in manifest.rs is not formatted according to
rustfmt, causing the format check to fail. Run cargo fmt on the file and keep
the existing json_roundtrip_has_required_fields test logic unchanged, with
special attention to the wrapped assert on supported_targets so it matches
rustfmt’s preferred layout.

In `@rbac-proxy/src/rbac.rs`:
- Around line 291-310: The FunctionFilter serialization blocks are failing
rustfmt, so update the affected Serialize for FunctionFilter implementation to
match standard Rust formatting. Run cargo fmt on the file and ensure the
FunctionFilter::Match and FunctionFilter::Metadata arms, along with the
surrounding impl Serialize and serialize_map usage, are formatted consistently
with rustfmt so CI passes.
- Around line 553-567: The AuthRejection handling in rbac.rs is leaking internal
backend and deserialization details through the message field, so replace the
raw e.to_string() responses in the match over result and
serde_json::from_value::<AuthResult>(v) with a stable generic client-facing
rejection message. Keep the detailed error text server-side by logging it near
the existing auth/session flow in the relevant rbac:: function, while preserving
the AUTH_ERROR code and using a consistent non-sensitive message for all failure
branches.

In `@rbac-proxy/src/server.rs`:
- Around line 118-154: The server shutdown path in `spawn_server` and `shutdown`
only signals the watch channel and never waits for the `axum::serve` task to
finish, so in-flight requests may be dropped. Change `spawn_server` to retain
the spawned `JoinHandle` alongside the shutdown sender in the proxy
state/handle, then update `shutdown()` to send the signal and await the server
task’s completion before returning. Use the existing `spawn_server`, `shutdown`,
and `current`/proxy handle plumbing to locate the async task ownership and make
graceful draining actually complete.

In `@rbac-proxy/tests/e2e/run-tests.sh`:
- Around line 88-92: The E2E harness currently reuses the existing data
directory, so persisted engine state can leak between runs and make fixed-id
tests in run-tests.sh non-hermetic. Update the test setup in the run-tests.sh
flow around the ROOT_DIR preparation and cd step to start from a clean state by
removing or recreating the persisted data used by the engine before launching
the suite, while keeping the reports directory behavior intact.

In `@rbac-proxy/tests/golden/schemas/rbac-proxy.status.json`:
- Around line 32-34: The status schema for rbac_enabled does not match the
worker’s actual RBAC enforcement, since the proxy can still block calls when
expose_functions is set even if auth_function_id is unset. Update the schema
contract in rbac-proxy.status.json so the rbac_enabled field name/description
reflects the full enforcement behavior, or rename the field to match the current
meaning used by the proxy status output.

In `@rbac-proxy/tests/integration.rs`:
- Around line 209-211: The `assert_eq!` in the integration test needs to be
reformatted to satisfy rustfmt and unblock CI. Adjust the call in the echoed
payload assertion so it matches standard formatting conventions, keeping the
same check and message while letting `cargo fmt --check` pass.
- Around line 109-115: The early-exit branch in the harness setup still returns
a Harness even when wait_for_listen fails, so update the integration test flow
to self-skip by returning None from the branch in the setup path around
wait_for_listen and Harness construction; keep the skipping message, but do not
spawn the placeholder proxy or call register_support() when the engine never
binds.

In `@rbac-proxy/tests/manifest.rs`:
- Around line 40-44: The manifest test block in manifest.rs needs formatting to
satisfy cargo fmt --all -- --check; run cargo fmt on this file and let it reflow
the default_config assertions around the cfg loop and port check without
changing behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abc028c0-f43a-496a-bd63-d78f7c46f57c

📥 Commits

Reviewing files that changed from the base of the PR and between a15475a and dc55074.

⛔ Files ignored due to path filters (1)
  • rbac-proxy/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • .github/workflows/create-tag.yml
  • .github/workflows/rbac-proxy-e2e.yml
  • .github/workflows/release.yml
  • README.md
  • iii-permissions.yaml
  • rbac-proxy/Cargo.toml
  • rbac-proxy/README.md
  • rbac-proxy/build.rs
  • rbac-proxy/iii.worker.yaml
  • rbac-proxy/src/channels.rs
  • rbac-proxy/src/config.rs
  • rbac-proxy/src/configuration.rs
  • rbac-proxy/src/engine_overrides.rs
  • rbac-proxy/src/functions/mod.rs
  • rbac-proxy/src/functions/status.rs
  • rbac-proxy/src/interceptor.rs
  • rbac-proxy/src/lib.rs
  • rbac-proxy/src/main.rs
  • rbac-proxy/src/manifest.rs
  • rbac-proxy/src/proxy.rs
  • rbac-proxy/src/rbac.rs
  • rbac-proxy/src/server.rs
  • rbac-proxy/tests/e2e/.gitignore
  • rbac-proxy/tests/e2e/README.md
  • rbac-proxy/tests/e2e/config.yaml
  • rbac-proxy/tests/e2e/reports/.gitkeep
  • rbac-proxy/tests/e2e/run-tests.sh
  • rbac-proxy/tests/e2e/workers/harness/iii.worker.yaml
  • rbac-proxy/tests/e2e/workers/harness/package.json
  • rbac-proxy/tests/e2e/workers/harness/src/cases.ts
  • rbac-proxy/tests/e2e/workers/harness/src/runner.ts
  • rbac-proxy/tests/e2e/workers/harness/src/support.ts
  • rbac-proxy/tests/e2e/workers/harness/src/worker.ts
  • rbac-proxy/tests/e2e/workers/harness/tsconfig.json
  • rbac-proxy/tests/golden/schemas/rbac-proxy.status.json
  • rbac-proxy/tests/integration.rs
  • rbac-proxy/tests/manifest.rs
  • rbac-proxy/tests/schemas.rs
  • rbac-proxy/tests/support/mod.rs
  • tech-specs/2026-06-rbac-proxy-worker/presentation/src/content/deck.ts
  • tech-specs/2026-06-rbac-proxy-worker/presentation/src/sections/Hero.tsx

Comment thread .github/workflows/rbac-proxy-e2e.yml
Comment thread .github/workflows/rbac-proxy-e2e.yml
Comment thread rbac-proxy/iii.worker.yaml Outdated
Comment thread rbac-proxy/src/configuration.rs Outdated
Comment thread rbac-proxy/src/engine_overrides.rs Outdated
Comment thread rbac-proxy/tests/e2e/run-tests.sh
Comment on lines +32 to +34
"rbac_enabled": {
"description": "Whether an `auth_function_id` is configured.",
"type": "boolean"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

rbac_enabled doesn't match the worker's actual enforcement.

This defines the field as "Whether an auth_function_id is configured", but the proxy still denies calls when expose_functions is populated and no auth function is set. Publishing this schema will tell downstream tooling "rbac off" for a proxy that is still filtering traffic. Rename the field or broaden its semantics before this becomes the public contract.

🤖 Prompt for 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.

In `@rbac-proxy/tests/golden/schemas/rbac-proxy.status.json` around lines 32 - 34,
The status schema for rbac_enabled does not match the worker’s actual RBAC
enforcement, since the proxy can still block calls when expose_functions is set
even if auth_function_id is unset. Update the schema contract in
rbac-proxy.status.json so the rbac_enabled field name/description reflects the
full enforcement behavior, or rename the field to match the current meaning used
by the proxy status output.

Comment on lines +109 to +115
if !wait_for_listen(49134, Duration::from_secs(8)).await {
eprintln!("skipping: engine did not come up on :49134");
return Some(Harness {
proxy: Command::new("true").spawn().ok()?,
iii,
_support: register_support(),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return None when the engine never binds.

Line 110 says this branch is skipping, but Lines 111-115 still return Some(Harness) and even call register_support(). That makes the test continue against a dead engine instead of self-skipping.

Proposed fix
     if !wait_for_listen(49134, Duration::from_secs(8)).await {
         eprintln!("skipping: engine did not come up on :49134");
-        return Some(Harness {
-            proxy: Command::new("true").spawn().ok()?,
-            iii,
-            _support: register_support(),
-        });
+        let mut iii = iii;
+        let _ = iii.kill();
+        let _ = iii.wait();
+        return None;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !wait_for_listen(49134, Duration::from_secs(8)).await {
eprintln!("skipping: engine did not come up on :49134");
return Some(Harness {
proxy: Command::new("true").spawn().ok()?,
iii,
_support: register_support(),
});
if !wait_for_listen(49134, Duration::from_secs(8)).await {
eprintln!("skipping: engine did not come up on :49134");
let mut iii = iii;
let _ = iii.kill();
let _ = iii.wait();
return None;
}
🤖 Prompt for 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.

In `@rbac-proxy/tests/integration.rs` around lines 109 - 115, The early-exit
branch in the harness setup still returns a Harness even when wait_for_listen
fails, so update the integration test flow to self-skip by returning None from
the branch in the setup path around wait_for_listen and Harness construction;
keep the skipping message, but do not spawn the placeholder proxy or call
register_support() when the engine never binds.

Comment thread rbac-proxy/tests/integration.rs Outdated
Comment thread rbac-proxy/tests/manifest.rs
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.

1 participant