diff --git a/overlay/usr/local/bin/veilor-installer b/overlay/usr/local/bin/veilor-installer index 59a988b..158a94b 100644 --- a/overlay/usr/local/bin/veilor-installer +++ b/overlay/usr/local/bin/veilor-installer @@ -309,6 +309,34 @@ sed_escape() { printf '%s' "$1" | sed -e 's/[\\&|/]/\\&/g' } +# detect_seed_pubkey — search attached cdroms for a NoCloud cidata seed +# with an ssh_authorized_keys entry. Returns the first key on stdout, or +# empty string if none found. Used by both auto-install.sh (cloud-init +# seed pre-built with host pubkey) and humans (drop a seed iso next to +# the install media). +detect_seed_pubkey() { + local dev label tmpmnt key="" + for dev in /dev/sr0 /dev/sr1 /dev/sr2 /dev/sr3; do + [ -b "$dev" ] || continue + label=$(blkid -o value -s LABEL "$dev" 2>/dev/null) + if [[ $label == "cidata" || $label == "CIDATA" ]]; then + tmpmnt=$(mktemp -d) + if mount -o ro "$dev" "$tmpmnt" 2>/dev/null; then + # NoCloud user-data format: + # ssh_authorized_keys: + # - ssh-ed25519 AAAA... user@host + # Extract first ssh-* line, strip leading '- '. + key=$(grep -E '^\s*-\s+ssh-' "$tmpmnt/user-data" 2>/dev/null \ + | head -1 | sed -e 's/^\s*-\s*//' -e 's/[[:space:]]*$//') + umount "$tmpmnt" 2>/dev/null + fi + rmdir "$tmpmnt" 2>/dev/null + [[ -n $key ]] && { printf '%s' "$key"; return 0; } + fi + done + return 1 +} + generate_ks() { # Build kickstart for actual disk install. # NOTE: passwords go in via --plaintext to avoid storing crypted hash @@ -345,6 +373,7 @@ firewall --enabled --service=ssh rootpw --lock user --name=admin --groups=wheel --gecos="veilor admin" --password=__ADMIN_PW__ --plaintext +__SSHKEY_DIRECTIVE__ # Full hardening cmdline (installed system, not live): # --location=none: anaconda auto-places bootloader (UEFI grub2-efi or BIOS). @@ -594,12 +623,25 @@ KSEOF # already rejects "$\`&|/\n at input — sed_escape() is defence in # depth in case future code paths feed unsanitised values (e.g. # locale/hostname from a file, or a relaxed validator). + # Detect cloud-init seed pubkey (if attached) → embed as anaconda + # `sshkey --username=admin` directive. Adds key to admin's + # authorized_keys at install time so SSH validation works first boot. + # No seed = empty directive line (anaconda treats blank line as no-op). + local seed_pubkey sshkey_directive="" + seed_pubkey=$(detect_seed_pubkey 2>/dev/null) || true + if [[ -n $seed_pubkey ]]; then + echo "[INFO] using cloud-init seed pubkey for admin authorized_keys" + # sshkey directive — quote pubkey, no shell meta in pubkeys. + sshkey_directive="sshkey --username=admin \"${seed_pubkey}\"" + fi + sed -i \ -e "s|__LOCALE__|$(sed_escape "$SEL_LOCALE")|" \ -e "s|__HOSTNAME__|$(sed_escape "$SEL_HOSTNAME")|" \ -e "s|__DISK_BASENAME__|$(sed_escape "$disk_basename")|" \ -e "s|__LUKS_PW__|$(sed_escape "$SEL_LUKS_PW")|" \ -e "s|__ADMIN_PW__|$(sed_escape "$SEL_ADMIN_PW")|" \ + -e "s|__SSHKEY_DIRECTIVE__|$(sed_escape "$sshkey_directive")|" \ "$out" echo "[INFO] generated kickstart at $out" return 0