mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 00:55:53 +08:00
Relax the auth-sidecar proxy address policy so a remote central sidecar reachable over TLS can be used, while keeping existing same-host plaintext behavior unchanged. - ValidateProxyAddr: allow https:// to any host (cross-machine); http:// and bare host:port stay same-host only; userinfo/path/query/fragment remain rejected. - Add ProxyScheme and route the interceptor URL rewrite through the configured scheme (https for remote, http for same-host). ProxyScheme parses the address so a mixed-case HTTPS:// cannot silently downgrade to plaintext HTTP. - Update LARKSUITE_CLI_AUTH_PROXY doc and server-demo README for the new policy; refresh the package comment. - Tests: case-insensitive scheme, IPv6 https, https userinfo rejection, query/fragment rejection, ProxyHost https forms, and end-to-end interceptor scheme selection.
209 lines
8.9 KiB
Markdown
209 lines
8.9 KiB
Markdown
# Sidecar Server Reference Implementation
|
|
|
|
> ⚠️ **This is a demo.** For production deployment, implement your own sidecar
|
|
> server conforming to the wire protocol in `github.com/larksuite/cli/sidecar`.
|
|
|
|
This example shows how to implement a sidecar auth proxy server that receives
|
|
HMAC-signed requests from lark-cli sandbox clients and forwards them to the
|
|
Lark/Feishu API with real credentials injected.
|
|
|
|
## What this demo shows
|
|
|
|
- HMAC-SHA256 request verification (timestamp drift, body digest, signature)
|
|
- Target host allowlist + https-only target validation (anti-SSRF / anti-downgrade)
|
|
- Identity-based token resolution (UAT for user, TAT for bot)
|
|
- Auth-header allowlist: real token may only be injected into `Authorization`
|
|
/ `X-Lark-MCP-UAT` / `X-Lark-MCP-TAT`, rejecting attempts to smuggle it into
|
|
`Cookie`, `User-Agent`, or other intermediate-logged headers
|
|
- Audit logging with path ID-segment sanitization and upstream error truncation
|
|
- Safe request forwarding (strips client-supplied auth headers)
|
|
|
|
## What this demo does NOT handle
|
|
|
|
- **TAT refresh** — the shared `DefaultTokenProvider` caches the TAT via
|
|
`sync.Once`, which never refreshes. A long-running server will return an
|
|
expired TAT after 2 hours. Production implementations should maintain a
|
|
TTL-based cache with early renewal.
|
|
- High availability / load balancing / hot key rotation
|
|
- TLS termination
|
|
- Rate limiting / per-identity quotas
|
|
|
|
## Both sides need the right build tags
|
|
|
|
Sidecar is split into **two separate binaries** with **different build tags**:
|
|
|
|
| Side | Binary | Build tag | How to build |
|
|
| --- | --- | --- | --- |
|
|
| Sandbox (client) | `lark-cli` | `authsidecar` | `go build -tags authsidecar -o lark-cli .` |
|
|
| Trusted (server) | `sidecar-server-demo` | `authsidecar_demo` | `go build -tags authsidecar_demo -o sidecar-server-demo ./sidecar/server-demo/` |
|
|
|
|
If the sandbox runs a standard `lark-cli` **without** `-tags authsidecar`, the
|
|
`LARKSUITE_CLI_AUTH_PROXY` env var is ignored and requests bypass the sidecar
|
|
entirely — real credentials (if any) leak to the sandbox.
|
|
|
|
## Prerequisites
|
|
|
|
The demo reuses the lark-cli credential pipeline, so the trusted machine must
|
|
have an app configured:
|
|
|
|
```bash
|
|
lark-cli config init --new # configure app_id / app_secret (required)
|
|
lark-cli auth login # store user refresh_token in keychain
|
|
# (only required if sandbox will use --as user)
|
|
```
|
|
|
|
`auth login` is **only required for user identity**. If the server will only
|
|
serve bot requests (TAT), `config init` alone is enough because the TAT is
|
|
minted from `app_id + app_secret`.
|
|
|
|
Also, the server process **must not** inherit `LARKSUITE_CLI_AUTH_PROXY` — if
|
|
it does, the sidecar credential provider would activate inside the server and
|
|
return sentinel tokens instead of real ones. The demo rejects this at startup
|
|
with a clear error, but you should make sure to `unset LARKSUITE_CLI_AUTH_PROXY`
|
|
in the server shell before launching.
|
|
|
|
## Run
|
|
|
|
```bash
|
|
./sidecar-server-demo \
|
|
--listen 127.0.0.1:16384 \
|
|
--key-file <HOME>/.lark-sidecar/proxy.key \
|
|
--log-file <HOME>/.lark-sidecar/audit.log
|
|
```
|
|
|
|
### Flags
|
|
|
|
| Flag | Default | Purpose |
|
|
| --- | --- | --- |
|
|
| `--listen` | `127.0.0.1:16384` | Address to bind the HTTP listener |
|
|
| `--key-file` | `<HOME>/.lark-sidecar/proxy.key` | Path to write the generated HMAC key (mode 0600) |
|
|
| `--log-file` | *(empty, stderr)* | Audit log output path |
|
|
| `--profile` | *(empty, active profile)* | lark-cli profile name for credential lookup |
|
|
|
|
### Startup output
|
|
|
|
```
|
|
Auth sidecar listening on http://127.0.0.1:16384
|
|
HMAC key prefix: a3b2c1d4
|
|
Full key written to /Users/alice/.lark-sidecar/proxy.key (mode 0600)
|
|
|
|
Set in sandbox:
|
|
export LARKSUITE_CLI_AUTH_PROXY="http://127.0.0.1:16384"
|
|
export LARKSUITE_CLI_PROXY_KEY="<read from /Users/alice/.lark-sidecar/proxy.key>"
|
|
export LARKSUITE_CLI_APP_ID="cli_xxx"
|
|
export LARKSUITE_CLI_BRAND="feishu"
|
|
```
|
|
|
|
The `key-file` path is printed exactly as passed on the command line (relative
|
|
paths stay relative). The `HMAC key prefix` is the first 8 characters for
|
|
identification without revealing the full key.
|
|
|
|
### Sandbox env vars (complete list)
|
|
|
|
The startup banner only prints the *required* variables. Two more are
|
|
optional:
|
|
|
|
```bash
|
|
export LARKSUITE_CLI_AUTH_PROXY="http://..." # required (see constraints below)
|
|
export LARKSUITE_CLI_PROXY_KEY="..." # required
|
|
export LARKSUITE_CLI_APP_ID="cli_xxx" # required
|
|
export LARKSUITE_CLI_BRAND="feishu" # required (feishu | lark)
|
|
export LARKSUITE_CLI_DEFAULT_AS="user" # optional: force default identity
|
|
export LARKSUITE_CLI_STRICT_MODE="user" # optional: lock sandbox to one identity
|
|
```
|
|
|
|
**`LARKSUITE_CLI_AUTH_PROXY` constraints** — validated by the CLI on startup:
|
|
|
|
- Scheme must be `http://` / `https://` (or bare `host:port`, treated as
|
|
plaintext http).
|
|
- `https://<any-host>` is allowed, **including a remote sidecar on another
|
|
machine**: TLS provides confidentiality over the network and the
|
|
per-request HMAC signature provides integrity/authentication.
|
|
- Plaintext `http://` (and bare `host:port`) is allowed **only same-host**:
|
|
loopback (`127.0.0.1`, `::1`) or a recognized same-host alias
|
|
(`localhost`, `host.docker.internal`, `host.containers.internal`,
|
|
`host.lima.internal`, `gateway.docker.internal`). For a remote sidecar,
|
|
use an `https://` address.
|
|
- No path, query, fragment, or `user:pass@` in the URL.
|
|
|
|
> Note: this demo server itself terminates plain HTTP and is meant to run
|
|
> locally. A production **remote** sidecar must terminate TLS (its own
|
|
> `https://` endpoint, e.g. behind a load balancer or with a real
|
|
> certificate); the CLI-side policy above is what enables pointing at it.
|
|
|
|
**How auto identity detection works in sidecar mode**: on every invocation the
|
|
CLI asks the sidecar to look up the logged-in user's `open_id` via
|
|
`/open-apis/authen/v1/user_info`. If that succeeds, `--as` defaults to `user`;
|
|
if it fails (trusted side has no valid user login, or the call errors out),
|
|
it falls back to `bot`. Setting `LARKSUITE_CLI_DEFAULT_AS=user` lets you
|
|
short-circuit this and always default to user regardless of the lookup
|
|
result; set it to `bot` for the opposite.
|
|
|
|
**Note**: `LARKSUITE_CLI_STRICT_MODE` and the server's identity allowlist are
|
|
two separate enforcement points:
|
|
- `STRICT_MODE` is interpreted locally by the sandbox CLI — it rejects
|
|
`--as` values the sandbox itself disallows, before any request goes out.
|
|
- The server's allowlist is built from the **trusted-side** config's
|
|
`SupportedIdentities` (`sidecar/server-demo/allowlist.go`). The sandbox
|
|
cannot override it.
|
|
|
|
A well-configured deployment aligns both (e.g. both set to `user` when the
|
|
app only supports user tokens), but they are computed independently.
|
|
|
|
### Graceful shutdown
|
|
|
|
Send `SIGINT` (`Ctrl+C`) or `SIGTERM` to stop the server. The demo drains
|
|
in-flight requests with a 5-second timeout before exiting.
|
|
|
|
## Wire protocol
|
|
|
|
See the [`sidecar` package on pkg.go.dev](https://pkg.go.dev/github.com/larksuite/cli/sidecar)
|
|
for protocol constants, HMAC signing/verification, and address validation utilities.
|
|
|
|
Headers (client → server):
|
|
|
|
| Header | Purpose |
|
|
| --- | --- |
|
|
| `X-Lark-Proxy-Version` | Wire-protocol version (currently `"v1"`). Server rejects unknown values with 400. |
|
|
| `X-Lark-Proxy-Target` | Original target **scheme + host only** (e.g. `https://open.feishu.cn`). Must be `https://`; any path/query/fragment/userinfo in this header is rejected. The path and query come from the request line itself; the server reconstructs the upstream URL as `https://<host> + requestURI`. |
|
|
| `X-Lark-Proxy-Identity` | `"user"` or `"bot"`. Covered by the signature. |
|
|
| `X-Lark-Proxy-Auth-Header` | Which header the server should inject real token into. Covered by the signature. |
|
|
| `X-Lark-Proxy-Signature` | hex-encoded HMAC-SHA256 |
|
|
| `X-Lark-Proxy-Timestamp` | Unix seconds (drift ≤ 60s) |
|
|
| `X-Lark-Body-SHA256` | hex-encoded SHA-256 of the request body |
|
|
|
|
Signing material (newline-separated, in order):
|
|
|
|
```text
|
|
version
|
|
method
|
|
host
|
|
pathAndQuery
|
|
bodySHA256
|
|
timestamp
|
|
identity
|
|
authHeader
|
|
```
|
|
|
|
Every field above is part of the canonical string. In particular, `identity`
|
|
and `authHeader` are covered so a captured request cannot be replayed with
|
|
its identity flipped (bot↔user) or its auth-header redirected (e.g. into
|
|
`Cookie`) inside the 60s drift window.
|
|
|
|
## Source layout
|
|
|
|
| File | Purpose |
|
|
| --- | --- |
|
|
| `main.go` | Entry point: flag parsing, server lifecycle |
|
|
| `handler.go` | `proxyHandler.ServeHTTP` — main request flow |
|
|
| `forward.go` | Forwarding HTTP client + proxy-header filter |
|
|
| `allowlist.go` | Target host / identity allowlists |
|
|
| `audit.go` | Log path/error sanitization |
|
|
| `handler_test.go` | Unit tests for all of the above |
|
|
|
|
## See also
|
|
|
|
- [server-multi-tenant-demo](../server-multi-tenant-demo/) — extends this demo
|
|
with per-client HMAC key isolation, OAuth device-flow login, and persistent
|
|
client → user mapping for multi-tenant deployments
|