From c021d8b8147901a7fad056d443c4145924407707 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Fri, 14 Aug 2026 06:25:50 +0800 Subject: [PATCH] fix(prelaunch): v0.0.19 - correct SSH key count and log key fingerprints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A CVM booting with one injected key logged "total 0 keys". The count came from `wc -l`, which counts newlines, while `jq -j` writes the value with no trailing newline — so a one-key file counted as zero. The same missing newline meant a later append would concatenate onto the previous line and silently corrupt authorized_keys, and the "more than one line" guard kept the dedup pass from running for a two-key file. v0.0.19: - Write with `jq -r` so the value is newline-terminated. - Dedup unconditionally, which also drops the blank line an empty key list leaves behind. - Count with a pattern that does not depend on the final newline. - Log the SHA256 fingerprint of every authorized key, in the format `ssh-keygen -lf` and GitHub use, so a boot log can be matched against the account's key list without shelling into the CVM. The guest image has neither ssh-keygen nor a base64 applet — every dstack 0.5.x image builds BusyBox from the same kirkstone defconfig, which ships base32 only — so openssl performs both the decode and the digest, guarded by a command -v check. openssl is present in every 0.5.x image via ca-certificates. `openssl base64 -d` exits 0 on undecodable input and emits nothing, which would print the SHA256 of the empty string as a real fingerprint; the decoded byte count is checked first and such a line prints "" instead. Sample output, verified byte-identical to `ssh-keygen -lf`: Set root authorized_keys from user preferences, total 2 keys ssh-ed25519 SHA256:6OKoIHh0+L2URA+iptwZ1EF3NlwXZ6o0BW9/bnoR7ds ssh-rsa SHA256:LxIr2ZUyQLBZtTcELwmpjnhrYTYL/4ZUhXb2ngIo0RU Matches the v0.0.19 text shipped in Phala Cloud (Phala-Network/phala-cloud-monorepo#1995); sha256 of this file is cec8f68ce6185b912023d886bba20cd06386dd9751af904e6107e758b9d68983, the canonical hash registered there. --- phala-cloud-prelaunch-script/prelaunch.sh | 53 +++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/phala-cloud-prelaunch-script/prelaunch.sh b/phala-cloud-prelaunch-script/prelaunch.sh index cacfa23..81ab395 100644 --- a/phala-cloud-prelaunch-script/prelaunch.sh +++ b/phala-cloud-prelaunch-script/prelaunch.sh @@ -1,6 +1,6 @@ #!/bin/bash echo "----------------------------------------------" -echo "Running Phala Cloud Pre-Launch Script v0.0.18" +echo "Running Phala Cloud Pre-Launch Script v0.0.19" echo "----------------------------------------------" set -e @@ -53,6 +53,40 @@ check_docker_login() { fi } +# Function: print the SHA256 fingerprint of every authorized key +# +# Output matches `ssh-keygen -lf` and the fingerprints GitHub and Phala Cloud +# display, so an operator can match the boot log against the account's key list. +# The guest image ships neither ssh-keygen nor a base64 applet (BusyBox has +# base32 only), so openssl does both the decode and the digest. +print_authorized_key_fingerprints() { + local file="$1" + if ! command -v openssl >/dev/null 2>&1; then + echo "openssl not available; skipping SSH key fingerprints" + return 0 + fi + + local key_type blob comment decoded_bytes fingerprint + # `|| [[ -n "$key_type" ]]` keeps the final line when the file does not end + # with a newline; read returns non-zero there but still fills the variables. + while read -r key_type blob comment || [[ -n "$key_type" ]]; do + if [[ -z "$blob" ]]; then + continue + fi + # openssl exits 0 on undecodable input and emits nothing, which would + # otherwise print the SHA256 of the empty string as a real fingerprint. + decoded_bytes=$(printf '%s' "$blob" | openssl base64 -d -A 2>/dev/null | wc -c || true) + if [[ "$decoded_bytes" -eq 0 ]]; then + echo " $key_type " + continue + fi + fingerprint=$( + printf '%s' "$blob" | openssl base64 -d -A 2>/dev/null | openssl dgst -sha256 -binary 2>/dev/null | openssl base64 -A 2>/dev/null | tr -d '=' || true + ) + echo " $key_type SHA256:$fingerprint" + done < "$file" +} + # Main logic starts here echo "Starting login process..." @@ -309,13 +343,16 @@ if mkdir -p /home/root/.ssh 2>/dev/null; then if [[ -f /dstack/user_config ]] && jq empty /dstack/user_config 2>/dev/null; then if [[ $(jq 'has("ssh_authorized_keys")' /dstack/user_config 2>/dev/null) == "true" ]]; then - jq -j '.ssh_authorized_keys' /dstack/user_config >> /home/root/.ssh/authorized_keys - # Remove duplicates if there are multiple keys - if [[ $(cat /home/root/.ssh/authorized_keys | wc -l) -gt 1 ]]; then - sort -u /home/root/.ssh/authorized_keys > /home/root/.ssh/authorized_keys.tmp - mv /home/root/.ssh/authorized_keys.tmp /home/root/.ssh/authorized_keys - fi - echo "Set root authorized_keys from user preferences, total" $(cat /home/root/.ssh/authorized_keys | wc -l) "keys" + # jq -r terminates the value with a newline; jq -j does not, which + # both concatenated the next appended key onto the same line and + # made line-based counting report one key fewer than the file holds. + jq -r '.ssh_authorized_keys' /dstack/user_config >> /home/root/.ssh/authorized_keys + # Drop duplicates and the blank line an empty key list produces. + sort -u /home/root/.ssh/authorized_keys > /home/root/.ssh/authorized_keys.tmp + mv /home/root/.ssh/authorized_keys.tmp /home/root/.ssh/authorized_keys + KEY_COUNT=$(grep -c '^[^[:space:]]' /home/root/.ssh/authorized_keys || true) + echo "Set root authorized_keys from user preferences, total $KEY_COUNT keys" + print_authorized_key_fingerprints /home/root/.ssh/authorized_keys fi fi else