feat(push): add iOS APNs relay gateway (#43369)

* feat(push): add ios apns relay gateway

* fix(shared): avoid oslog string concatenation

# Conflicts:
#	apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift

* fix(push): harden relay validation and invalidation

* fix(push): persist app attest state before relay registration

* fix(push): harden relay invalidation and url handling

* feat(push): use scoped relay send grants

* feat(push): configure ios relay through gateway config

* feat(push): bind relay registration to gateway identity

* fix(push): tighten ios relay trust flow

* fix(push): bound APNs registration fields (#43369) (thanks @ngutman)
This commit is contained in:
Nimrod Gutman
2026-03-12 18:15:35 +02:00
committed by GitHub
parent 9342739d71
commit b77b7485e0
36 changed files with 3249 additions and 203 deletions

View File

@@ -2447,6 +2447,14 @@ See [Plugins](/tools/plugin).
// Remove tools from the default HTTP deny list
allow: ["gateway"],
},
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
timeoutMs: 10000,
},
},
},
},
}
```
@@ -2472,6 +2480,11 @@ See [Plugins](/tools/plugin).
- `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`.
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`: client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext.
- `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves.
- `gateway.push.apns.relay.baseUrl`: base HTTPS URL for the external APNs relay used by official/TestFlight iOS builds after they publish relay-backed registrations to the gateway. This URL must match the relay URL compiled into the iOS build.
- `gateway.push.apns.relay.timeoutMs`: gateway-to-relay send timeout in milliseconds. Defaults to `10000`.
- Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches `gateway.identity.get`, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration.
- `OPENCLAW_APNS_RELAY_BASE_URL` / `OPENCLAW_APNS_RELAY_TIMEOUT_MS`: temporary env overrides for the relay config above.
- `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true`: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS.
- Local gateway call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset.
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
- `trustedProxies`: reverse proxy IPs that terminate TLS. Only list proxies you control.

View File

@@ -225,6 +225,63 @@ When validation fails:
</Accordion>
<Accordion title="Enable relay-backed push for official iOS builds">
Relay-backed push is configured in `openclaw.json`.
Set this in gateway config:
```json5
{
gateway: {
push: {
apns: {
relay: {
baseUrl: "https://relay.example.com",
// Optional. Default: 10000
timeoutMs: 10000,
},
},
},
},
}
```
CLI equivalent:
```bash
openclaw config set gateway.push.apns.relay.baseUrl https://relay.example.com
```
What this does:
- Lets the gateway send `push.test`, wake nudges, and reconnect wakes through the external relay.
- Uses a registration-scoped send grant forwarded by the paired iOS app. The gateway does not need a deployment-wide relay token.
- Binds each relay-backed registration to the gateway identity that the iOS app paired with, so another gateway cannot reuse the stored registration.
- Keeps local/manual iOS builds on direct APNs. Relay-backed sends apply only to official distributed builds that registered through the relay.
- Must match the relay base URL baked into the official/TestFlight iOS build, so registration and send traffic reach the same relay deployment.
End-to-end flow:
1. Install an official/TestFlight iOS build that was compiled with the same relay base URL.
2. Configure `gateway.push.apns.relay.baseUrl` on the gateway.
3. Pair the iOS app to the gateway and let both node and operator sessions connect.
4. The iOS app fetches the gateway identity, registers with the relay using App Attest plus the app receipt, and then publishes the relay-backed `push.apns.register` payload to the paired gateway.
5. The gateway stores the relay handle and send grant, then uses them for `push.test`, wake nudges, and reconnect wakes.
Operational notes:
- If you switch the iOS app to a different gateway, reconnect the app so it can publish a new relay registration bound to that gateway.
- If you ship a new iOS build that points at a different relay deployment, the app refreshes its cached relay registration instead of reusing the old relay origin.
Compatibility note:
- `OPENCLAW_APNS_RELAY_BASE_URL` and `OPENCLAW_APNS_RELAY_TIMEOUT_MS` still work as temporary env overrides.
- `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true` remains a loopback-only development escape hatch; do not persist HTTP relay URLs in config.
See [iOS App](/platforms/ios#relay-backed-push-for-official-builds) for the end-to-end flow and [Authentication and trust flow](/platforms/ios#authentication-and-trust-flow) for the relay security model.
</Accordion>
<Accordion title="Set up heartbeat (periodic check-ins)">
```json5
{