Skip to content

feat(sidecar): add client key self-registration API#1357

Closed
hGrany wants to merge 2 commits into
larksuite:mainfrom
hGrany:feat/sidecar-client-key-self-registration
Closed

feat(sidecar): add client key self-registration API#1357
hGrany wants to merge 2 commits into
larksuite:mainfrom
hGrany:feat/sidecar-client-key-self-registration

Conversation

@hGrany

@hGrany hGrany commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds /_sidecar/keys/register management-plane endpoint so clients can dynamically register their own per-client HMAC keys without server-side pre-provisioning.

Problem

In containerized / sandboxed environments (e.g. Coder workspaces, CI runners), the sidecar's keys/ directory is typically a read-only host mount. Operators had to manually create per-client .key files for every new sandbox before it could connect — this doesn't scale and blocks fully automated provisioning.

Solution

Clients generate a random 64-char hex HMAC key locally (in their writable home directory), then register it with the sidecar via a single POST /_sidecar/keys/register call signed with the shared proxy.key. The server:

  1. Validates the client ID (safe characters, no path traversal) and key format (64 hex chars, not colliding with proxy key)
  2. Persists the key to keys/{client_id}.key on disk
  3. Hot-loads it into the in-memory key map — no server restart needed
  4. Returns "registered" on first call, "already_registered" on idempotent re-registration, or 409 Conflict if the client already has a different key

Changes

File Change
auth_bridge.go Add keyRegistrar interface + handleRegisterKey handler + /_sidecar/keys/register route
handler.go Add validateClientID, registerClientKey (persist + hot-load), hotLoadClientKey
main.go Replace validate.SafeInputPath with charcheck-based validateSidecarPath that allows absolute server paths; reorder init to resolve circular dependency (handler ↔ authBridge)
handler_test.go 4 new tests: registration + idempotency, conflict detection, input validation (empty/reserved/traversal/bad hex/proxy collision), full HTTP round-trip
README.md Document self-registration flow, add client-init.sh example script, update architecture diagram and end-to-end workflow sections

Design decisions

  • Management-plane HMAC for registration: reuses the existing proxy.key signing scheme — no new auth mechanism needed
  • Disk persistence + hot-load: keys survive sidecar restarts; existing file-scan (loadClientKeys) continues to work for pre-provisioned keys
  • Strict validation: regex-based client ID check, hex format enforcement, proxy key collision guard, path traversal prevention
  • Idempotent: safe to call on every container startup (init scripts typically run on each boot)

Testing

  • All 4 new unit tests pass
  • All existing sidecar demo tests pass (no regressions)
  • Production validated: deployed and running in a Coder workspace environment with 20+ concurrent client containers for 1+ week

Test plan

  • go test -tags authsidecar_multi_tenant_demo ./sidecar/server-multi-tenant-demo/ -v — all tests pass
  • go build -tags authsidecar_multi_tenant_demo ./sidecar/server-multi-tenant-demo/ — builds cleanly
  • Production smoke test with real containers registering keys dynamically

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Clients can self-register per-client HMAC keys via a management endpoint; idempotent registration returns clear statuses (200 success/already-registered, 409 conflict).
  • Documentation

    • README updated with client init scripts, end-to-end workflow, management endpoint details, and clarified multi-tenant dual-key design.
  • Tests

    • Added tests for registration success, idempotency, conflict handling, and invalid-input cases.
  • Bug Fixes / Validation

    • Improved input/path validation and runtime key-loading consistency.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a44230f3-54ac-4b6b-accf-9529ca543226

📥 Commits

Reviewing files that changed from the base of the PR and between f02f411 and e0084d9.

📒 Files selected for processing (2)
  • sidecar/server-multi-tenant-demo/README.md
  • sidecar/server-multi-tenant-demo/handler.go
✅ Files skipped from review due to trivial changes (1)
  • sidecar/server-multi-tenant-demo/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • sidecar/server-multi-tenant-demo/handler.go

📝 Walkthrough

Walkthrough

This PR adds per-client HMAC key self-registration to the multi-tenant demo server. Clients can register data-plane keys via a signed POST to /_sidecar/keys/register after proving identity with the shared proxy.key. Changes span handler validation/persistence and hot-load, auth-bridge routing, tests, startup wiring, and README examples.

Changes

Per-Client Key Registration System

Layer / File(s) Summary
Documentation and end-to-end examples
sidecar/server-multi-tenant-demo/README.md
Documents client self-registration, dual-key design (management vs data plane), server setup requiring only proxy.key, idempotent client init script with signed /_sidecar/keys/register, example workflows, management endpoints table, and key-registration HTTP semantics (200/409).
Handler: client key validation, persistence, hot-load
sidecar/server-multi-tenant-demo/handler.go
Adds validClientID regex and validateClientID, registerClientKey to validate 64-char hex keys, prevent reuse of proxy.key, persist <client_id>.key with 0600, return registered/already_registered/conflict, and hotLoadClientKey to atomically update clientKeys with consistency checks and stale-entry removal.
Management endpoint for key registration
sidecar/server-multi-tenant-demo/auth_bridge.go
Adds keyRegistrar interface and optional authBridge.keyRegistrar, routes /_sidecar/keys/register to handleRegisterKey, parses JSON (client_id,key_hex), requires registrar, delegates registration, maps key_conflict errors to HTTP 409 (others to 400), and returns {ok:true,status} on success.
Tests: request signing, unit and HTTP integration
sidecar/server-multi-tenant-demo/handler_test.go
Adds crypto imports and signManagementRequest helper for canonical signed management requests; unit tests for registerClientKey (success, idempotency, conflict, invalid inputs); HTTP-style integration test signs and posts to /_sidecar/keys/register, asserting 200 then 409 on conflict.
Server CLI validation and startup wiring
sidecar/server-multi-tenant-demo/main.go
Replaces validate.SafeInputPath with validateSidecarPath (reject empty, control chars, .. traversal), applies it to CLI args, refactors startup to load client keys before creating authBridge, then wires ab.keyRegistrar = handler and handler.authBridge = ab.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • larksuite/cli#934: Prior PR modifying auth bridge and proxy handler for multi-tenant per-client HMAC key isolation; this PR extends that work with the /_sidecar/keys/register registration flow and expanded documentation.

Suggested labels

size/XL

Suggested reviewers

  • sang-neo03

Poem

🐰 I dig a hole for keys so neat,
HMAC whispers make auth complete.
Clients sign, register, then depart,
Sidecar holds each secret heart.
Hop! The demo hums — secure and sweet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a client key self-registration API to the sidecar.
Description check ✅ Passed The description is comprehensive and well-structured, including summary, problem statement, solution, detailed changes table, design decisions, testing approach, and test plan with completed checkmarks.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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 and usage tips.

@CLAassistant

CLAassistant commented Jun 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jun 9, 2026

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

🤖 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 `@sidecar/server-multi-tenant-demo/handler.go`:
- Around line 149-162: Replace the read-then-write race with an atomic
create-or-check pattern: attempt to create the key file with exclusive create
semantics (O_CREATE|O_EXCL) via the VFS open API (use vfs.OpenFile with
os.O_CREATE|os.O_EXCL and mode 0600) and write keyHex; if the create succeeds,
hot-load the key (h.hotLoadClientKey) and return "registered"; if the create
fails with EEXIST, read the existing file (vfs.ReadFile) and compare to
keyHex—if equal hot-load and return "already_registered", otherwise return the
key_conflict error; ensure partial writes are cleaned up on failure and wrap
errors with context.

In `@sidecar/server-multi-tenant-demo/README.md`:
- Around line 261-264: The loop attempts to register with two sidecar instances
but call_sidecar reads only global SIDECAR_HOST/SIDECAR_PORT, so it ignores the
extra host/port args; update call_sidecar (the function named call_sidecar in
the README script) to accept and use positional parameters for host and port
when provided (falling back to global SIDECAR_HOST/SIDECAR_PORT if not passed),
or alternatively change the loop to export/override SIDECAR_HOST and
SIDECAR_PORT per iteration before calling call_sidecar; ensure the
implementation uses the passed host/port variables when constructing the request
so the POST targets both ports 16384 and 16385 as intended.
🪄 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

Run ID: ba596bab-733e-4eb1-80bc-29a9d0e573fd

📥 Commits

Reviewing files that changed from the base of the PR and between 4f4c0b5 and 01fff73.

📒 Files selected for processing (5)
  • sidecar/server-multi-tenant-demo/README.md
  • sidecar/server-multi-tenant-demo/auth_bridge.go
  • sidecar/server-multi-tenant-demo/handler.go
  • sidecar/server-multi-tenant-demo/handler_test.go
  • sidecar/server-multi-tenant-demo/main.go

Comment thread sidecar/server-multi-tenant-demo/handler.go
Comment thread sidecar/server-multi-tenant-demo/README.md Outdated
When the sidecar's keys/ directory is a read-only host mount (common in
container orchestrators and CI environments), operators cannot pre-create
per-client key files for every new sandbox. This blocks fully automated
provisioning and elastic scaling.

Add a `/_sidecar/keys/register` management-plane endpoint so clients can
dynamically register their own per-client HMAC keys. The client generates
a random 64-char hex key locally (in a writable path such as
~/.lark-sidecar/client.key), then registers it with the sidecar via a
single POST signed with the shared proxy.key. The server persists the
key to disk and hot-loads it into memory — no restart needed.

Changes:
- auth_bridge.go: add keyRegistrar interface + handleRegisterKey handler
  + /_sidecar/keys/register route
- handler.go: add validateClientID, registerClientKey (persist + hot-
  load), hotLoadClientKey; add regexp and encoding/hex imports
- main.go: replace validate.SafeInputPath with charcheck-based
  validateSidecarPath that allows absolute server paths; reorder init
  so handler is created before authBridge (circular dep: authBridge
  needs keyRegistrar which is the handler)
- handler_test.go: 4 new tests covering registration, idempotency,
  conflict detection, input validation, and full HTTP round-trip
- README.md: document self-registration flow, add client-init.sh
  reference script, update architecture and end-to-end workflow sections;
  clarify multi-organization use case

Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
@hGrany
hGrany force-pushed the feat/sidecar-client-key-self-registration branch from 01fff73 to f02f411 Compare June 9, 2026 12:57
- handler.go: replace read-then-write with atomic O_CREATE|O_EXCL to
  prevent TOCTOU race between concurrent key registrations; clean up
  partial writes on failure
- README.md: fix multi-organization registration loop — call_sidecar
  reads SIDECAR_PORT from env, so override it per iteration instead of
  passing extra positional args that were silently ignored

Signed-off-by: Gao Yang <gaoyang@topwin.tech> (topwin.tech)
@MaxHuang22 MaxHuang22 added the domain/core CLI framework and core libraries label Jun 10, 2026
@sang-neo03

sang-neo03 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@hGrany
This is a real deployment need, especially in containerized, sandboxed, and CI
environments where pre-provisioning keys for every client adds operational
complexity.

That said, this directory is currently a demo. It is better kept focused on
the basic sidecar mechanism, such as HMAC verification, per-client isolation,
OAuth login bridging, and request forwarding. Adding /_sidecar/keys/register
introduces a new management endpoint, key lifecycle handling, disk
persistence, hot loading, concurrent creation handling, and conflict
semantics. These are closer to production deployment concerns and would
noticeably increase the complexity of the demo.

I would prefer to keep the demo minimal and limited to the core example logic.
Key distribution, automated registration, and integration with secret management systems should be handled by users according to their own deployment environment.

So my suggestion is to close this PR and keep this capability out of the current demo.

@hGrany

hGrany commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @sang-neo03 — I understand the concern about keeping the demo minimal and focused.

That said, I'd like to point out that this demo directory is, in practice, the only implementation of the multi-tenant sidecar. There's no separate "production" version — everyone deploying multi-tenant setups builds from server-multi-tenant-demo/. So the bar for "demo completeness" is effectively the bar for "can I actually use this."

I'd also appreciate your thoughts on #1367, which addresses a more fundamental issue: the demo silently falls back to offline_access when no scope cache exists, resulting in scope_count=1. Users get a "login successful" response but effectively zero useful permissions — document creation, drive access, search, etc. all return 99991679: Permission denied. This is a silent failure that's very hard to diagnose.

Both PRs came from running the demo in production (Coder workspaces, 90+ containers). The demo's current state requires significant setup knowledge and manual pre-configuration to work correctly. These patches aim to make it more self-contained — closer to "clone, configure, run" rather than "clone, read source code, understand scope caching internals, manually create cache files, then run."

If you'd prefer to keep #1357 out, I can accept that — key distribution is indeed deployment-specific. But #1367 is fixing a correctness issue in the demo itself: the offline_access fallback masks a misconfiguration that should be surfaced as an explicit error.

@sang-neo03

Copy link
Copy Markdown
Collaborator

@hGrany Okay, I will review #1367.

@hGrany hGrany closed this Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/core CLI framework and core libraries size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants