Repository navigation
[SSH fleets] Support in-place updates of SSH keys - #4458
Conversation
| @router.post("/replicas/proxy_jump_keys") | ||
| async def update_replica_proxy_jump_keys( | ||
| body: UpdateReplicaProxyJumpKeysRequest, | ||
| repo: Annotated[GatewayProxyRepo, Depends(get_gateway_proxy_repo)], | ||
| service_conn_pool: Annotated[ServiceConnectionPool, Depends(get_service_connection_pool)], | ||
| ) -> UpdateReplicaProxyJumpKeysResponse: | ||
| failures = await registry_services.update_replica_proxy_jump_keys( | ||
| updates=body.replicas, repo=repo, service_conn_pool=service_conn_pool | ||
| ) | ||
| return UpdateReplicaProxyJumpKeysResponse(failures=failures) |
There was a problem hiding this comment.
I'm not sure about updating all replicas' keys in a single request.
- I assume the request may take a long time, since replicas' SSH tunnels are reopened sequentially.
- This makes the request fragile due to timeouts and network interruptions.
- There is less visibility on the server — the server can't tell what the gateway is doing and whether it's doing anything, can't set stricter request timeouts.
- The method only handles proxy jump key updates. If we ever decide to update other replica fields, we'll have to introduce more API methods, potentially with a lot of code duplication.
I would look into introducing generic per-replica /{project_name}/services/{run_name}/replicas/{job_id}/update instead
There was a problem hiding this comment.
Existing tunnels aren't reopened here; the update replaces the key file used for future connections. The batch avoids rewriting the full gateway state once per replica.
There is a fallback in get_or_add that can open SSH if the connection is missing from the pool. That should be kept out of this update path.
Making the update API generic sounds reasonable. I'd keep the batching, though: with the current storage, 1,000 individual updates would rewrite the whole gateway state 1,000 times.
There was a problem hiding this comment.
There is a fallback in
get_or_addthat can open SSH if the connection is missing from the pool. That should be kept out of this update path
Btw, if tunnels are not being reopened, what's the point of using get_or_add there?
There was a problem hiding this comment.
I'd keep the batching, though
Ok
There was a problem hiding this comment.
Fixed.
Btw, if tunnels are not being reopened, what's the point of using
get_or_addthere?
I reused get_or_add for its key-update logic. It now updates the existing connection directly. If there's no pooled connection, we save the new key without opening SSH.
In-place SSH key updates
SSH fleets now support updating
ssh_config.identity_fileandssh_config.proxy_jump.identity_filewithdstack apply, including per-host overrides. Previously, these changes required re-creating the fleet, which is blocked while runs are active.Runs continue without reprovisioning hosts or restarting containers. All established SSH connections stay open, including server management connections, attach sessions, and service tunnels. Existing sessions are not forced to reconnect or reauthenticate.
Example
Authorize the replacement public key on the relevant host, update the identity-file path, and apply the fleet. For example, when replacing both keys:
Then apply the configuration:
Per-host changes appear as
ssh_config.hosts[0].identity_fileorssh_config.hosts[0].proxy_jump.identity_filein the apply plan. Changes to SSH users, ports, orproxy_jumpaddresses still require re-creating the fleet.Removing the old key
For
proxy_jump.identity_file, allow about 20 seconds after apply before removing the old public key from the host configured byproxy_jump. Gateways and the optional SSH proxy need time to receive the new key. Removing the old public key does not close sessions already authenticated with it.Implementation details
Existing SSH authentication
These rules are unchanged by this PR:
ssh_config.identity_file: the server uses this key to provision fleet hosts and authorize the project public key.ssh_config.proxy_jump.identity_file: these components use this key for the additional hop through the fleet'sproxy_jump, when configured.Applying key changes
The server now supports in-place replacement of
identity_fileandproxy_jump.identity_file, including per-host overrides. Apply waits for affected instances' ongoing processing to finish, then saves the replacement keys in the fleet configuration and those instances' SSH connection details. New server connections throughproxy_jumpuse the replacement key after apply.The update preserves all established SSH connections. Each component uses the replacement
proxy_jumpkey for new connections and reconnects after receiving it; sessions authenticated with the old key are not disconnected.Propagating
proxy_jumpkeysThe shared service connection pool now supports replacing a
proxy_jumpkey while retaining the established SSH tunnel, HTTP client, and Unix socket.proxy_jumpkey with the instance's current key and sends replacements where needed. Gateways update existing pooled connections' credentials without unregistering replicas or reloading Nginx. If a replica has no pooled connection, they save its new key without opening SSH. They report replacement keys only after saving them, so failed updates are retried during later synchronization.Apply does not wait for gateways to receive the new key or for the SSH proxy's cache to expire.
Performance
Closes #4450
Backward compatibility
proxy_jumpchanges to existing gateway service replicas requires the updated gateway app, deployed through the existing server-managed gateway update process. The SSH proxy requires no update.