phase 4: connectrpc on the gateway authenticated edge

Replace the native-gRPC server bootstrap with a single
`connectrpc.com/connect` HTTP/h2c listener. Connect-Go natively
serves Connect, gRPC, and gRPC-Web on the same port, so browsers can
now reach the authenticated surface without giving up the gRPC
framing native and desktop clients may use later. The decorator
stack (envelope → session → payload-hash → signature →
freshness/replay → rate-limit → routing/push) is reused unchanged
behind a small Connect → gRPC adapter and a `grpc.ServerStream`
shim around `*connect.ServerStream`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 11:49:28 +02:00
parent 39b7b2ef29
commit 118f7c17a2
30 changed files with 1009 additions and 772 deletions
+33 -16
View File
@@ -87,7 +87,15 @@ The gateway exposes two external transport classes.
| Transport | Audience | Authentication | Payload format | Primary use |
| --- | --- | --- | --- | --- |
| REST/JSON | Public, unauthenticated traffic | No device session auth | JSON | Health checks, public auth commands, and browser/bootstrap traffic |
| gRPC over HTTP/2 | Authenticated clients only | Required | FlatBuffers payload inside protobuf control envelope | Verified commands and push delivery |
| Connect / gRPC / gRPC-Web over HTTP/2 (h2c) | Authenticated clients only | Required | FlatBuffers payload inside protobuf control envelope | Verified commands and push delivery |
The authenticated edge listener is built on
[`connectrpc.com/connect`](https://connectrpc.com/) and natively serves
the Connect, gRPC, and gRPC-Web protocols on a single HTTP/2 cleartext
(`h2c`) port. Browser clients use `@connectrpc/connect-web`; native
clients can use either Connect or raw gRPC framing against the same
listener. Production TLS termination happens upstream of the gateway,
matching the previous gRPC-only deployment posture.
### Public REST Surface
@@ -181,16 +189,21 @@ The endpoint exposes metrics in the Prometheus text exposition format described
in the official Prometheus documentation:
<https://prometheus.io/docs/instrumenting/exposition_formats/>.
### Authenticated gRPC Surface
### Authenticated Edge Surface
All authenticated client requests use HTTP/2 and gRPC.
The listener address is configured by `GATEWAY_AUTHENTICATED_GRPC_ADDR`.
Inbound authenticated gRPC connection setup is bounded by
All authenticated client requests use HTTP/2 cleartext (`h2c`) and are
served through `connectrpc.com/connect`, which natively accepts the
Connect, gRPC, and gRPC-Web protocols on the same listener.
The listener address is configured by `GATEWAY_AUTHENTICATED_GRPC_ADDR`
(the env-var name retains the historical `GRPC` infix for operational
stability — it labels the authenticated edge tier, not the wire
protocol).
Inbound authenticated edge connection setup is bounded by
`GATEWAY_AUTHENTICATED_GRPC_CONNECTION_TIMEOUT`, which defaults to `5s`.
The accepted client timestamp skew is configured by
`GATEWAY_AUTHENTICATED_GRPC_FRESHNESS_WINDOW` and defaults to `5m`.
The public gRPC service exposes two methods:
The public service exposes two methods:
- `ExecuteCommand(ExecuteCommandRequest) returns (ExecuteCommandResponse)`
- `SubscribeEvents(SubscribeEventsRequest) returns (stream GatewayEvent)`
@@ -200,9 +213,12 @@ The gateway routes the request downstream by `message_type` after transport
verification succeeds.
Downstream unary execution is bounded by
`GATEWAY_AUTHENTICATED_DOWNSTREAM_TIMEOUT`, which defaults to `5s`.
When that timeout expires, the gateway preserves the authenticated gRPC
contract and returns gRPC `UNAVAILABLE` with message
`downstream service is unavailable`.
When that timeout expires, the gateway preserves the authenticated edge
contract and returns `UNAVAILABLE` with message
`downstream service is unavailable`. Reject codes are documented using
their gRPC names (`INVALID_ARGUMENT`, `UNAUTHENTICATED`, …); the same
codes flow back to Connect clients as the corresponding `connect.Code*`
values.
`SubscribeEvents` is an authenticated server-streaming RPC.
It binds the stream to `user_id` and `device_session_id` and starts by sending
@@ -211,8 +227,9 @@ a signed service event that includes the current server time in milliseconds.
The v1 protobuf contract lives in
`proto/galaxy/gateway/v1/edge_gateway.proto` under package
`galaxy.gateway.v1` and service `EdgeGateway`.
Generated Go bindings are committed under `proto/galaxy/gateway/v1/` and are
regenerated with:
Generated Go bindings are committed under
`proto/galaxy/gateway/v1/` (gRPC stubs and `gatewayv1connect/` Connect
handlers) and are regenerated with:
```bash
buf generate
@@ -286,8 +303,8 @@ affected stream is closed with gRPC `RESOURCE_EXHAUSTED` and message
same `device_session_id` was revoked, every active `SubscribeEvents` stream
bound to that exact session is closed with gRPC `FAILED_PRECONDITION` and
message `device session is revoked`. During gateway shutdown, the in-memory
push hub is closed before gRPC graceful stop, and every active
`SubscribeEvents` stream is terminated with gRPC `UNAVAILABLE` and message
push hub is closed before HTTP graceful stop, and every active
`SubscribeEvents` stream is terminated with `UNAVAILABLE` and message
`gateway is shutting down`.
Authenticated anti-abuse budgets are configured by the
`GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_*` environment variables.
@@ -851,9 +868,9 @@ subscribers, and telemetry runtime.
`GATEWAY_SHUTDOWN_TIMEOUT` configures the per-component graceful shutdown
budget and defaults to `5s`.
During authenticated gRPC shutdown, the in-memory `PushHub` closes active
streams before gRPC graceful stop, so active `SubscribeEvents` calls terminate
with gRPC `UNAVAILABLE` and message `gateway is shutting down`.
During authenticated edge shutdown, the in-memory `PushHub` closes active
streams before HTTP graceful stop, so active `SubscribeEvents` calls terminate
with `UNAVAILABLE` and message `gateway is shutting down`.
## Recommended Package Layout