blob: 7380b6f96638a0aaa8b1b706e6245191200576c8 [file] [view]
# Proxy Admin Management Interface
> 中文版见 `docs/proxy-admin-zh.md`。
## 1. Motivation
RocketMQ 5.0 moved client access behind the stateless Proxy, but operations still
observe clients through broker-side structures (`ConsumerManager` on the broker,
Remoting-era admin commands). gRPC clients attached to a Proxy are invisible to
those tools: the control plane cannot answer "which SDK clients are online, what
do they subscribe to, are they healthy" without indirect metrics heuristics.
A control-plane dashboard needs a standard server-side interface to read complete
gRPC client data. This change implements that interface on the Proxy itself.
## 2. Contract choice: upstream `Admin` service
The gRPC contract is the **upstream `Admin` service** defined in
`apache/rocketmq/v2/admin.proto` of the `apache/rocketmq-apis` repository (main
branch, consumed as a git submodule — see §6). This implementation deliberately
does NOT introduce a bespoke `ProxyAdminService` in the protocol layer: the
control-plane RPC surface lives in the same versioned, community-reviewed proto
that the dashboard and multi-language SDKs already consume, and stays
additive-compatible with upstream evolution.
The proxy implements `AdminGrpc.AdminImplBase` (all 16 RPCs) and binds it on a
dedicated admin gRPC server.
## 3. Goals
1. A dedicated, independent gRPC Admin server on the Proxy, isolated from the
data-plane `MessagingService`.
2. Stable, backward-compatible contract from rocketmq-apis main (`Admin`
service, `apache/rocketmq/v2/admin.proto`).
3. First-class authorization under dedicated `proxy.admin.*` ACL 2.0 resources
with read-only / high-privilege action separation.
4. Cluster-wide semantics without leaking topology into the protocol: every
proxy exposes a cluster-visible view of connection/subscription data and
forwards client-directed RPCs to the proxy that owns the client.
## 4. Design Decisions
### D1 — Service placement
The upstream `Admin` service is bound on its own gRPC server/port
(`grpcAdminServerPort`, default 8088) with its own interceptor chain, separate
from the data plane (8081). The whole surface is gated by a global kill switch
`grpcAdminServerEnable`, which **defaults to false**: the admin server is opt-in
and is not started unless explicitly enabled.
Rationale: control-plane traffic must never contend with the data plane, and the
admin port can be firewalled to the operations network only. The admin server
intentionally does NOT expose channelz or proto reflection. It reuses the data
plane's `GrpcChannelManager` / `GrpcClientSettingsManager` so online clients are
visible to admin queries. Runtime executors introduced by this feature are
instance-owned and registered in the Proxy lifecycle: the admin timeout scheduler
stops with `ProxyAdminGrpcService`, and the blocking gateway executor stops with
`AdminService`.
### D2 — Authorization: dedicated `proxy.admin.*` resources
Credentials arrive in the standard gRPC `Authorization` metadata (same scheme as
the data plane, ACL 2.0 signature). Authentication runs as an interceptor, ordered
**after** the standard pipeline's `HeaderInterceptor` (registered before it, since
gRPC invokes interceptors in reverse registration order). That ordering is
load-bearing: `HeaderInterceptor` replaces the inbound `x-mq-channel-id` with the
channel id derived from the transport, and authentication results are cached per
channel id, so authenticating on the client-supplied value would let a request on
one connection reuse another connection's cached success and skip signature
verification.
Every RPC maps to one resource + one action:
| Resource | RPCs | Actions |
|---|---|---|
| `proxy.admin.client` | ListSubscription, ListConsumerConnection (List); DescribeSubscription, DescribeGroupAccumulation, GetConsumerRunningInfo, QueryTimeSpan (Get) | List / Get |
| `proxy.admin.config` | ChangeLogLevel | Update |
| `proxy.admin.connection` | PrintThreadStackTrace, VerifyMessage | Update (high privilege) |
| `proxy.admin.route` | GetTopicRoute | Get |
| `proxy.admin.ops` | GetProxyRuntimeStats, DescribeTopicStatus, QueryMessage (Get); ResetGroupOffset (Update); DeleteSubscription (Delete); AdminSendMessage (Pub) | Get / Update / Delete / Pub |
Modeling note: ACL 2.0 resource types are cluster/namespace/topic/group. The
admin resources are modeled as CLUSTER-typed literals with reserved names
(resource keys `cluster:proxy.admin.<module>`), which yields exact
least-privilege matching without colliding with real cluster names and without
changing the auth core.
Modes:
- cluster auth disabled, `grpcAdminServerAuthEnable=false` → open surface (same
semantics as the data plane);
- cluster auth enabled → standard authenticate + authorize pipeline against the
per-method `proxy.admin.*` resource;
- `grpcAdminServerAuthEnable=true` → fail-closed. The admin surface must be able
to both authenticate the caller **and** enforce the per-method ACL, so this
mode requires the cluster-wide authentication **and** authorization switches to
be on. If either is off every request is refused (authentication off →
`UNAUTHENTICATED`; authorization off → `FAILED_PRECONDITION`) rather than served
without a real permission check. This is deliberate: the ACL 2.0 evaluator is
itself gated by the cluster authorization switch, so serving requests with
authorization off would silently pass every call, including the destructive
ones.
Audit: every served RPC writes `[PROXY-ADMIN-AUDIT] subject/method/resource/
action/sourceIp` to the auth audit logger (satisfies the Console-user + AK +
resource + operation audit tuple requirement together with the ACL 2.0 engine's
own audit log).
### D3 — Multi-proxy semantics: cluster-visible, no topology in the protocol
A gRPC client is attached to exactly one proxy at a time, but a dashboard must
still see the whole cluster from any single admin endpoint. Two mechanisms make
that work without adding scope/peering fields to the protocol:
- **Connection / subscription data is cluster-visible.** The listing RPCs
(`ListConsumerConnection`, `ListSubscription`, `DescribeSubscription`) merge
the broker-side `ConsumerConnection` view (which covers remoting clients, and
gRPC clients replicated between proxies by `HeartbeatSyncer`) with this proxy's
own `ConsumerManager` (which covers the gRPC v2 clients attached locally). The
result is deduplicated by group + topic, so the answer does not scale with the
number of online consumers and does not depend on which proxy is queried.
- **Client-directed RPCs are forwarded to the owning proxy.**
`PrintThreadStackTrace`, `VerifyMessage` and `GetConsumerRunningInfo` need the
live telemetry stream that only the owning proxy holds. `ProxyAdminForwarder`
detects a client owned by a peer (its `ConsumerManager` entry is a
`RemoteChannel` whose `getRemoteProxyIp()` is the peer's `localServeAddr`) and
forwards the whole RPC — original metadata included — to the peer's admin port,
guarding against loops with the `x-mq-admin-forwarded` header. The peer admin
port is assumed uniform across the cluster (the heartbeat sync payload does not
carry it); a cluster running heterogeneous admin ports would need that added to
the heartbeat record first.
`GetProxyRuntimeStats` intentionally reports the **local** process (its own
connection/producer/consumer counts), so a dashboard that wants per-node stats
queries each proxy directly.
### D4 — Data sources per RPC
| Source | RPCs | Mechanism |
|---|---|---|
| Cluster-visible connections | ListConsumerConnection, ListSubscription, DescribeSubscription | broker-side `ConsumerConnection` (remoting + peer-synced clients) merged with this proxy's `ConsumerManager` (local gRPC v2 clients) |
| Client-directed telemetry relay | PrintThreadStackTrace, VerifyMessage, GetConsumerRunningInfo | telemetry command relayed to the owning `GrpcClientChannel`; forwarded to the peer proxy when the client is remote (D3) |
| Broker gateway (async, multi-broker fan-out) | DescribeTopicStatus, DescribeGroupAccumulation, ResetGroupOffset, QueryMessage, QueryTimeSpan, GetTopicRoute, DeleteSubscription | the proxy's own async `AdminService` remoting gateway; each broker hop is bounded by its own deadline so a slow/unresponsive broker cannot hang the RPC |
| Proxy producer path | AdminSendMessage | `MessagingProcessor.sendMessage` (system properties honoured for timer / FIFO messages) |
| Proxy runtime | ChangeLogLevel | relocated logback API on the root logger |
Every broker-facing call goes through the asynchronous `AdminService` gateway and
is fanned out to all relevant brokers concurrently, so the gRPC executor thread is
never blocked.
### D5 — Protocol coverage
The connection/subscription listings are cluster-visible and include both
remoting clients (via the broker-side `ConsumerConnection`) and gRPC v2 clients
(via the proxy's `ConsumerManager`). The client-directed telemetry RPCs
(`PrintThreadStackTrace`, `VerifyMessage`, `GetConsumerRunningInfo`) operate on the
gRPC v2 client that holds a proxy telemetry stream, with cross-proxy forwarding;
their full running-info payload is bounded by what the telemetry protocol can
carry (see §8, honest boundaries).
## 5. Capability mapping
| Requirement | Delivered as | Notes |
|---|---|---|
| ListClients (filter by group/topic/clientId prefix, paged) | `ListConsumerConnection(group[, topic])` | clientId-prefix filter and pagination are not expressible in the upstream contract; documented boundary of this partial delivery |
| DescribeClient (SDK version, subscriptions, heartbeat, auth, Pop progress) | `DescribeSubscription` (per-client subscriptions) + `GetConsumerRunningInfo` (subscriptions + running info) | heartbeat/auth-status/Pop-progress fields do not exist in the upstream contract |
| ListClientsByGroup / ListClientsByTopic | `ListConsumerConnection(group[, topic])` | fully covered |
| Multi-proxy cluster aggregation | cluster-visible connection data + client-directed RPC forwarding | see D3 |
M2+ items (config hot update, quotas, connection control, Pop/batch diagnostics,
route observation streaming) are not part of the upstream `Admin` contract and
are out of scope for this delivery.
## 6. Building the proto sources
The `apache/rocketmq-apis` repository (main branch, containing
`apache/rocketmq/v2/admin.proto`) is consumed as a **git submodule**
(`rocketmq-apis/`, see `.gitmodules`); CI workflows check it out with
`submodules: true`.
- **Maven**: the `rocketmq-proto` module generates the Java + gRPC stubs for
`apache/rocketmq/v2/{definition,service,admin}.proto` at build time via
`protobuf-maven-plugin`; its version follows the reactor (`${revision}`), so
no published proto artifact is required to build this repository.
- **Bazel**: `//rocketmq-proto:rocketmq-proto` builds the same classes through a
`genrule` over the submodule (surfaced as the `@rocketmq_apis` external
repository), using pinned `protoc` / `protoc-gen-grpc-java` binaries and the
well-known-type protos.
- Other consumers (dashboard, SDKs) continue to use the artifact published by
rocketmq-apis; only this repository builds the proto from source.
## 7. Configuration Reference
| Key | Default | Meaning |
|---|---|---|
| `grpcAdminServerEnable` | false | kill switch; the admin surface is opt-in. Set `true` to start the admin gRPC server; `false` (default) = not started |
| `grpcAdminServerPort` | 8088 | dedicated admin gRPC port (<=0 disables) |
| `grpcAdminServerAuthEnable` | false | fail-closed mode; requires cluster authentication **and** authorization to be enabled (see D2) |
| `grpcAdminServerRequestTimeoutMillis` | 3000 | per-broker-hop deadline for the broker calls an admin RPC fans out to |
## 8. Honest boundaries
The admin surface never fills a field it cannot truthfully supply; the following
are answered with an explicit status or left unset rather than faked:
- **DeleteSubscription is effectively a no-op in open source.** Nothing in the
open-source codebase writes `SubscriptionGroupConfig.subscriptionDataSet` (the
broker only reads it), so there is no persisted per-topic subscription to
remove. The RPC returns `NOT_FOUND` with an explanation and **leaves the
consumer group and its offsets untouched**. Distributions that need this RPC
back it with an external subscription store that populates the same field.
- **QueryMessage** supports lookup by `message_id` (unique-key index) and
`message_key`. The `subscription` / `lite_topic` / pure time-range scan variants
need a paging cursor the open-source broker does not provide and return
`BAD_REQUEST`.
- **GetProxyRuntimeStats** leaves `in_tps` / `out_tps` unset: the open-source
proxy keeps no per-process throughput counter, and reporting 0 would be
indistinguishable from an idle proxy.
- **GetConsumerRunningInfo** over a gRPC v2 client returns only `subscriptions`:
the v2 telemetry protocol has no reply message carrying `properties`,
`message_queue_table` or `consume_status_table`. The RPC returns `OK` with an
explanatory status message rather than an empty shell.
- **DescribeTopicStatus** leaves `create_timestamp` / `tags` unset because the
broker does not record a topic creation time.
## 9. Milestones
- M1 (this delivery): online client query over the upstream `Admin` service —
ListConsumerConnection / DescribeSubscription / GetConsumerRunningInfo /
DescribeGroupAccumulation, served cluster-visibly with cross-proxy forwarding,
plus the full 16-RPC `Admin` surface for dashboard integration.
- Future (needs upstream proto evolution): clientId-prefix filter / pagination
for client listings, heartbeat & auth-status fields, and the M2+ surfaces
(config/quota/connection/route observation).
## 10. Acceptance Criteria Mapping
| Criterion | Status |
|---|---|
| Design document + stable backward-compatible proto contract | this document + upstream `admin.proto` (rocketmq-apis main) |
| Client query RPCs merged into the server repo | `ProxyAdminGrpcService` implements all 16 `Admin` RPCs on the proxy |
| Independent ACL control, read-only/high-risk separation, least-privilege doc | D2 resources/actions + §11 (Least-Privilege Configuration Guide) below |
| E2E with the dashboard | contract = upstream `Admin` service, frozen for dashboard integration (cross-repo) |
## 11. Least-Privilege Configuration Guide
The admin surface authorizes every RPC against dedicated `proxy.admin.*`
ACL 2.0 resources (see §4 decision D2 above). This section shows the
minimum-permission policy for each operational role.
### 11.1 Resource & Action Model
Resources (ACL 2.0 keys; modeled as cluster-typed literals with reserved names):
| Resource key | Protects |
|---|---|
| `cluster:proxy.admin.client` | online client query & client diagnostics |
| `cluster:proxy.admin.config` | proxy runtime log-level change |
| `cluster:proxy.admin.connection` | client-directed telemetry commands: thread stack, verify message (HIGH) |
| `cluster:proxy.admin.route` | topic route view |
| `cluster:proxy.admin.ops` | broker-facing ops: stats/topic status/message query (read) and reset offset / delete subscription / admin send (HIGH) |
Action classes:
- Read-only: `Get`, `List`
- High privilege (mutating / disruptive): `Update`, `Delete`, `Pub`
The server maps every RPC to exactly one (resource, action) pair; granting a
read-only action can never authorize a high-privilege RPC.
### 11.2 Role Templates
All commands run against any broker/namesrv of the cluster (ACL 2.0 storage).
Create users first:
```bash
sh mqadmin createUser -n <namesrv-addr> -u <username> -p <password>
```
#### Role A — Read-only observer (dashboard service account)
Online clients, subscriptions, accumulation, diagnostics, config/route views.
```bash
sh mqadmin updateAcl -n <namesrv-addr> \
-s user:rip2-ro \
-r cluster:proxy.admin.client,cluster:proxy.admin.config,cluster:proxy.admin.route,cluster:proxy.admin.ops \
-a Get,List \
-d Allow
```
Note: `Get,List` on `proxy.admin.ops` covers the read-only broker-facing RPCs;
the mutating ops RPCs require `Update`/`Delete`/`Pub` and stay denied.
#### Role B — On-call operator (observer + client diagnostics commands)
Role A plus the ability to direct telemetry commands (thread stack / verify
message) at a connected client.
```bash
sh mqadmin updateAcl -n <namesrv-addr> \
-s user:rip2-oncall \
-r cluster:proxy.admin.connection \
-a Update \
-d Allow
# plus the Role A grant above
```
#### Role C — Admin (full control, break-glass)
Offset reset, subscription deletion, admin send, client diagnostics commands.
```bash
sh mqadmin updateAcl -n <namesrv-addr> \
-s user:rip2-admin \
-r cluster:proxy.admin.client,cluster:proxy.admin.config,cluster:proxy.admin.connection,cluster:proxy.admin.route,cluster:proxy.admin.ops \
-a Get,List,Update,Delete,Pub \
-d Allow
```
(Keep Role C accounts to a minimum; every use is recorded in the auth audit
log with the `[PROXY-ADMIN-AUDIT]` prefix.)
#### Environment restriction (recommended)
Restrict admin access to the operations network via the `-i` sourceIp option:
```bash
sh mqadmin updateAcl -n <namesrv-addr> \
-s user:rip2-ro \
-r cluster:proxy.admin.client \
-a Get,List \
-d Allow \
-i 10.0.0.0/8
```
### 11.3 Fail-Closed Mode
By default the admin server follows the cluster-wide authentication/authorization
switches (same behavior as the data plane). Fail-closed mode makes the admin
surface refuse to serve anything it cannot fully authorize:
```properties
# proxy.json / -D grpcAdminServerAuthEnable=true
grpcAdminServerAuthEnable: true
```
With `grpcAdminServerAuthEnable=true`, the admin surface requires the cluster
authentication **and** authorization switches to be enabled. If authentication is
off, requests are rejected with `UNAUTHENTICATED`; if authorization is off, they
are rejected with `FAILED_PRECONDITION`. This guarantees the `proxy.admin.*` ACL
is genuinely enforced — use it when the admin port cannot be network-isolated.
### 11.4 Enabling / Disabling the Surface
The surface is **off by default** (`grpcAdminServerEnable=false`). To enable it:
```properties
grpcAdminServerEnable: true # start the admin gRPC server on grpcAdminServerPort
```
To keep it disabled (the default), leave `grpcAdminServerEnable: false`, or set
`grpcAdminServerPort` to 0 / negative — the admin gRPC server is then not started
at all.
### 11.5 Audit
Every served admin RPC logs subject (Console login user / AK), method, resource,
action and source IP to the auth audit logger; denied requests are logged by the
ACL 2.0 engine itself. This satisfies the four-tuple audit requirement
(Console user + AK + resource + operation).