diff --git a/dev-box/README.md b/dev-box/README.md index 1c2ee1f..03d7d03 100644 --- a/dev-box/README.md +++ b/dev-box/README.md @@ -35,7 +35,18 @@ volumes: home: ``` -Deploy with your public key or keys in `SSH_AUTHORIZED_KEYS`, in `authorized_keys` format with one key per line. The container exits with an error if the variable is empty. +## SSH keys + +Two channels feed `/root/.ssh/authorized_keys`. They are merged and de-duplicated when the container starts, so a key in either channel gets you in, a key in both is listed once, and a key the platform manages never displaces the one you deployed with. + +| Channel | Source | Use it for | +|---------|--------|------------| +| `SSH_AUTHORIZED_KEYS` | Environment variable in the compose file | A key you paste at deploy time | +| `user_config` | The `ssh_authorized_keys` field of the `user_config` Phala Cloud pushes into the guest; the host root is mounted at `/host`, so it reads `/host/dstack/user_config` | Workspace keys the platform keeps in sync | + +Both channels grant root. Setting `SSH_AUTHORIZED_KEYS` and changing this app's workspace SSH keys both hand over the CVM, and the dev box trusts every key the platform delivers through `user_config`; there is no switch to ignore that channel today. Which workspace members and roles may change an app's SSH keys is decided by Phala Cloud's key management, not by this example. + +Deploy with your public key or keys in `SSH_AUTHORIZED_KEYS`, in `authorized_keys` format with one key per line: ```bash phala deploy -n my-app -c docker-compose.yaml \ @@ -43,16 +54,20 @@ phala deploy -n my-app -c docker-compose.yaml \ -e SSH_AUTHORIZED_KEYS="$(cat ~/.ssh/id_ed25519.pub)" ``` -On a plain dstack deployment, set `SSH_AUTHORIZED_KEYS` as an encrypted environment variable. +On a plain dstack deployment, set `SSH_AUTHORIZED_KEYS` as an encrypted environment variable. If your keys live in the Phala Cloud workspace instead, drop the `environment:` entry from the service: the dev box still picks them up from `user_config`. + +`user_config` is optional and read defensively: a missing file, invalid JSON, a missing field, or an empty value all mean "this channel has no keys". The container exits with an error only when both channels turn up empty. + +Keys added to your Phala Cloud workspace reach the guest's `user_config` only at CVM boot, so a workspace key change works on the next start of the VM, not immediately. ## Connect -`sshd` listens on port `1022`. Reach it through the dstack-gateway by adding this to `~/.ssh/config`: +`sshd` listens on port `22`, the same as the 0.5.x dev image. Reach it through the dstack-gateway by adding this to `~/.ssh/config`: ``` Host my-app User root - ProxyCommand openssl s_client -quiet -connect -1022.:443 -servername -1022. -verify_hostname -1022. -verify_return_error + ProxyCommand openssl s_client -quiet -connect -22.:443 -servername -22. -verify_hostname -22. -verify_return_error ``` Replace `` with your app ID and `` with the gateway base domain of the node the CVM runs on, for example `dstack-pha-prod15.phala.network`. Change `443` if your gateway uses a different port. diff --git a/dev-box/docker-compose.yaml b/dev-box/docker-compose.yaml index 6fe8ad3..dec1ae8 100644 --- a/dev-box/docker-compose.yaml +++ b/dev-box/docker-compose.yaml @@ -15,6 +15,8 @@ services: uts: host environment: # One or more public keys, newline-separated (authorized_keys format). + # Merged with the platform-managed keys in /host/dstack/user_config; + # drop this line to rely on those alone. - SSH_AUTHORIZED_KEYS=${SSH_AUTHORIZED_KEYS} volumes: - /:/host:rslave diff --git a/dev-box/entrypoint.sh b/dev-box/entrypoint.sh index f33061f..89ad589 100644 --- a/dev-box/entrypoint.sh +++ b/dev-box/entrypoint.sh @@ -1,12 +1,50 @@ #!/bin/sh +# Authorized keys arrive on two channels with different purposes: +# +# SSH_AUTHORIZED_KEYS key pasted at deploy time, one per line +# user_config workspace keys the platform pushes to the CVM before +# it boots; the host root is mounted at /host, so the +# guest's /dstack/user_config is /host/dstack/user_config +# +# They are merged, not overridden: a key set in either channel works, and a +# platform-side key update never drops the deploy-time key. Duplicates are +# removed. Each channel is optional; only losing both is fatal. set -eu -if [ -z "${SSH_AUTHORIZED_KEYS:-}" ]; then - echo "SSH_AUTHORIZED_KEYS is not set" >&2 + +# Overridable to exercise the merge logic outside a CVM. +USER_CONFIG=${USER_CONFIG:-/host/dstack/user_config} + +env_keys=${SSH_AUTHORIZED_KEYS:-} + +# A missing file or malformed JSON makes jq fail; `.ssh_authorized_keys // empty` +# turns a missing field and a null into no output, select() drops a non-string +# value, and an empty string comes out as an empty line that the blank-line +# filter below removes. Every path ends with this channel contributing no key. +# +# The assignment, not the jq call, is what swallows the failure: jq streams, so +# `{"ssh_authorized_keys":"k"} garbage` prints the key and then exits non-zero. +# Dropping output only inside the substitution would keep that partial key from +# a config we just decided not to trust, so a non-zero exit discards it all. +config_keys=$( + jq -r '.ssh_authorized_keys // empty | select(type == "string")' \ + "$USER_CONFIG" 2>/dev/null +) || config_keys= + +# Blank and whitespace-only lines would become empty authorized_keys entries. +keys=$( + printf '%s\n%s\n' "$env_keys" "$config_keys" | + sed -e 's/[[:space:]]*$//' -e '/^$/d' | sort -u +) + +if [ -z "$keys" ]; then + echo "no SSH public keys in SSH_AUTHORIZED_KEYS or ${USER_CONFIG}" >&2 exit 1 fi + install -d -m 700 /root/.ssh -printf '%s\n' "$SSH_AUTHORIZED_KEYS" > /root/.ssh/authorized_keys +printf '%s\n' "$keys" > /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys +echo "authorized_keys: $(printf '%s\n' "$keys" | wc -l) key(s)" >&2 key=/etc/ssh/host-keys/ssh_host_ed25519_key [ -f "$key" ] || ssh-keygen -q -t ed25519 -N '' -f "$key" install -d -m 755 /run/sshd diff --git a/dev-box/sshd_config b/dev-box/sshd_config index c3fcc55..decbbeb 100644 --- a/dev-box/sshd_config +++ b/dev-box/sshd_config @@ -1,4 +1,4 @@ -Port 1022 +Port 22 HostKey /etc/ssh/host-keys/ssh_host_ed25519_key AllowUsers root PermitRootLogin prohibit-password