From 83bf907908ac713d334bf3ed4424989c86be9294 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 23 Oct 2016 00:34:05 +0200 Subject: Use the QEMU Guest Agent to retrive public key material. Unlike filesystem passthrough (9p VirtFS), this allows us to create guests remotely without using sudo privileges. (We can't do this with VirtFS currently due to lack of relabelling, and the kernel won't let us `chgrp libvirt-qemu` without sudoing.) --- tdfvm-install | 136 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 45 deletions(-) (limited to 'tdfvm-install') diff --git a/tdfvm-install b/tdfvm-install index 052e173..d0138c6 100755 --- a/tdfvm-install +++ b/tdfvm-install @@ -13,15 +13,60 @@ usage() { exit 1 } +# Run a command locally or remotely +unset SSH_ARGS +run() { + if [ ${SSH_ARGS+x} ]; then ssh $SSH_ARGS -- "$@"; else "$@"; fi +} + +unset CDROM +unset SSH_COMMAND +unset OUTPUT +unset VMDIR +SEED=$(head -c24 /dev/urandom | base64 -w0 | tr '+/' '-_') +SSH_CONTROLPATH="${XDG_RUNTIME_DIR:-$HOME/.ssh}/S.$SEED" # unique, unpredicable socket path +cleanup() { + set +e + [ ! ${CDROM+x} ] || run rm -f "$CDROM" + [ ! ${SSH_COMMAND+x} ] || rm -f "$SSH_COMMAND" + [ "${OUTPUT+x}" -o ! "${VMDIR+x}" ] || rm -rf "$VMDIR" + [ ! -S "$SSH_CONTROLPATH" ] || ssh -Oexit -S"$SSH_CONTROLPATH" x 2>/dev/null +} +trap cleanup EXIT TERM INT LIBVIRT_URI=qemu:///system +VIRT_USER="$USER" +prepare_remote_uri() { + local dest="$1" user port + [ ! ${SSH_COMMAND+x} ] || return # already defined + + if [ "${dest%:*}" != "$dest" ]; then + port="${dest##*:}" + dest="${dest%:*}" + fi + if [ "${dest%@*}" != "$dest" ]; then + VIRT_USER="${dest%%@*}" + dest="${dest#*@}" + fi + + SSH_COMMAND="$(mktemp --tmpdir)" + SSH_ARGS="-oControlMaster=auto -oControlPath=$SSH_CONTROLPATH ${VIRT_USER+-l $VIRT_USER} ${port+-p $port} $dest" + cat >"$SSH_COMMAND" <<-EOF + #!/bin/sh + export SSH_AUTH_SOCK="${SSH_AUTH_SOCK-}" + exec ssh -oControlPersist=yes $SSH_ARGS -- nc -q0 -U /run/libvirt/libvirt-sock + EOF + chmod u+x "$SSH_COMMAND" + LIBVIRT_URI="qemu+ext:///system?command=${SSH_COMMAND//\//%2F}" +} + + ARCH=$(dpkg-architecture -qDEB_TARGET_ARCH) unset ISO USER_NAME=root PROMPT_PASSWORD=n unset AUTHORIZED_KEYS FORCE=n -unset OUTPUT CPU="host" unset VCPUS @@ -34,13 +79,14 @@ TRANSIENT=n HELP_MESSAGE="$(cat <<-EOF Install a new VM in an unattended fashion Usage $0 [OPTIONS] NAME - --arch=ARCH target architecture (default: "$ARCH") - --iso=FILENAME path to the installation ISO image to preseed (required) - -u,--username=USERNAME user account to create (default: "$USER_NAME") - -p,--password prompt for USERNAME's password (password login are disabled by default) - --authorized-keys=FILENAME pass to USERNAME's authorized_keys(5) file - -f,--force imediately the domain NAME if it exists, and remove existing configuration - -o,--output=DIRNAME directory where to export the XML definition and key material from the guest + --ssh=[USERNAME@]HOSTNAME[:PORT] connect over SSH to a remote libvirtd + --arch=ARCH target architecture (default: "$ARCH") + --iso=FILENAME path to the installation ISO image to preseed (required) + -u,--username=USERNAME user account to create (default: "$USER_NAME") + -p,--password prompt for USERNAME's password (password login are disabled by default) + --authorized-keys=FILENAME path to USERNAME's authorized_keys(5) file + -f,--force imediately the domain NAME if it exists, and remove existing configuration + -o,--output=DIRNAME directory where to export the XML definition and key material from the guest virt-install(1) options --cpu=MODEL,... CPU model and CPU features exposed to the guest (default: "$CPU") @@ -69,6 +115,7 @@ while [ $# -gt 0 ]; do -o|--output) OUTPUT="$2"; shift;; -o*) OUTPUT="${1#-o}";; --output=*) OUTPUT="${1#--output=}";; + --ssh=*) prepare_remote_uri "${1#--ssh=}";; --vcpus) VCPUS="$2"; shift;; --vcpus=*) VCPUS="${1#--vcpus=}";; @@ -108,7 +155,6 @@ done ISOHDPFX=/usr/lib/ISOLINUX/isohdpfx.bin [ -f "$ISOHDPFX" ] || error "Missing $ISOHDPFX. Is the 'isolinux' package installed?" -[ $(id -u) -eq 0 ] || error "This script needs to run as root" if [ "$PROMPT_PASSWORD" = n ]; then PASSWORD_CRYPTED='*' @@ -116,7 +162,7 @@ else PASSWORD_CRYPTED="$( [ "$USER_NAME" = root ] && prompt="Enter root password" || prompt="Enter password for $USER_NAME" read -rs -p "$prompt (leave blank to auto-generate): " pw - + if [ "$pw" ]; then printf '\n' >/dev/tty read -rs -p "Re-enter password to confirm: " pw2 @@ -138,19 +184,14 @@ fi # Presseed the ISO image # -VMTMPDIR="$(mktemp --tmpdir=/var/lib/libvirt/images --directory "$VM_NAME.XXXXXX" )" -trap 'rm -rf "$VMTMPDIR"' EXIT TERM INT -chmod a+x "$VMTMPDIR" -install -o libvirt-qemu -m 0400 /dev/null "$VMTMPDIR/install.iso" -install -o libvirt-qemu -m 0700 --directory "$VMTMPDIR/virtfs" - +CDROM="$(run mktemp --tmpdir "$VM_NAME-XXXXXX.iso")" ( mountdir="$(mktemp --tmpdir --directory)" fuseiso "$ISO" "$mountdir" isoeditdir="$(mktemp --tmpdir --directory)" trap 'rm -rf "$isoeditdir"' EXIT TERM INT - + rsync -aH --exclude=TRANS.TBL --chmod=u+w "$mountdir/" "$isoeditdir/" fusermount -u "$mountdir" @@ -211,7 +252,9 @@ install -o libvirt-qemu -m 0700 --directory "$VMTMPDIR/virtfs" -partition_offset 16 \ -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot \ --efi-boot boot/grub/efi.img -append_partition 2 0x01 ./boot/grub/efi.img \ - -o "$VMTMPDIR/install.iso" ./ + ./ \ + | if [ ${SSH_ARGS+x} ]; then run "cat >$CDROM"; else cat >"$CDROM"; fi + run chmod g+r "$CDROM" ) @@ -226,6 +269,7 @@ if [ "$FORCE" = y ]; then virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" >/dev/null 2>&1 || true fi +VMDIR="${OUTPUT-$(mktemp --tmpdir --directory)}" virt-install -q --connect "$LIBVIRT_URI" \ --name "$VM_NAME" \ --os-variant "debianwheezy" \ @@ -233,60 +277,62 @@ virt-install -q --connect "$LIBVIRT_URI" \ --virt-type "kvm" \ --cpu "$CPU" ${VCPUS+--vcpus "$VCPUS"} \ --memory "$MEMORY" --memballoon "virtio" \ - --disk "path=$VMTMPDIR/install.iso,device=cdrom,bus=sata,readonly=on" \ + --disk "path=$CDROM,device=cdrom,bus=sata,readonly=on,seclabel.model=dac,seclabel.label=$VIRT_USER:libvirt-qemu" \ --disk "$DISK,bus=virtio" \ --channel "unix,target_type=virtio,name=org.qemu.guest_agent.0" \ --rng "random,device=/dev/random,model=virtio,rate_bytes=512" \ - --security "type=static,model=dac,label=libvirt-qemu:libvirt-qemu,relabel=no" \ - --filesystem "source=$VMTMPDIR/virtfs,target=virtfs" \ + --security "type=static,model=dac,label=libvirt-qemu:libvirt-qemu,relabel=yes" \ --network "$NETWORK" \ --graphics "$GRAPHICS" \ --controller "virtio-serial" \ --noautoconsole \ - --print-xml 1 >"$VMTMPDIR/domain.xml" + --print-xml 1 >"$VMDIR/$VM_NAME.xml" -virsh -c "$LIBVIRT_URI" create "$VMTMPDIR/domain.xml" +virsh -c "$LIBVIRT_URI" create "$VMDIR/$VM_NAME.xml" # never boot again on CDROM, detach unnecessary devices and remove # unnecessary controllers xmlstarlet edit --inplace \ --delete "/domain/os/boot[@dev='cdrom']" \ - --delete "/domain/devices/filesystem[source/@dir='$VMTMPDIR/virtfs'][target/@dir='virtfs']" \ --delete "/domain/devices/disk[@type='file'][@device='cdrom']" \ --delete "/domain/devices/sound" \ --delete "/domain/devices/controller[@type='usb']" \ --delete "/domain/devices/input[@type='tablet'][@bus='usb']" \ --delete "/domain/devices/redirdev[@type='spicevmc'][@bus='usb']" \ - "$VMTMPDIR/domain.xml" + "$VMDIR/$VM_NAME.xml" + +[ "$TRANSIENT" = y ] || virsh -c "$LIBVIRT_URI" define "$VMDIR/$VM_NAME.xml" -[ "$TRANSIENT" = y ] || virsh -c "$LIBVIRT_URI" define "$VMTMPDIR/domain.xml" +# wait until the guest starts its QEMU Agent at the end of the installation +while :; do + ./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" ping && break || rv=$? + [ $rv -eq 128 ] || exit $rv + sleep 1 +done + +# then copy the public key material +for path in /etc/ssh/ssh_host_rsa_key.pub \ + /etc/ssh/ssh_host_ed25519_key.pub \ + /etc/salt/pki/minion/minion.pub; do + ./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" cat "$path" >"$VMDIR/${path##*/}" +done -# wait until the VM terminates (there is actually a race condition here, -# but the XML massaging above should be faster than any install) -virsh -c "$LIBVIRT_URI" console "$VM_NAME" --safe >/dev/null +./virsh-ga -c "$LIBVIRT_URI" "$VM_NAME" touch /etc/tdf-install-continue +virsh -c "$LIBVIRT_URI" console "$VM_NAME" --safe >/dev/null # wait until shutdown ( - if [ -f "$VMTMPDIR/virtfs/minion.pub" ]; then - echo "Salt minion MD5 fingerprint:" - # salt uses a bizarre scheme... - grep -v '^-----.*-----$' "$VMTMPDIR/virtfs/minion.pub" \ - | openssl dgst -md5 -c | sed '/.*=\s*/ {s//\t/;q}' - fi + echo "Salt minion MD5 fingerprint:" + # salt uses a bizarre scheme... + grep -v '^-----.*-----$' "$VMDIR/minion.pub" \ + | openssl dgst -md5 -c | sed '/.*=\s*/ {s//\t/;q}' echo - sshkeys=$(find "$VMTMPDIR/virtfs" -maxdepth 1 -type f -name 'ssh_host_*_key.pub') - if [ "$sshkeys" ]; then - echo "SSH hostkey fingerprints:" - for pk in $sshkeys; do - ssh-keygen -lf "$pk" - done | sed 's/^/\t/' - fi + echo "SSH hostkey fingerprints:" + find "$VMDIR" -maxdepth 1 -type f -name 'ssh_host_*_key.pub' \ + -execdir ssh-keygen -lf {} \; | sed 's/^/\t/' ) >&2 if [ ${OUTPUT+x} ]; then - cp --no-preserve=mode "$VMTMPDIR/domain.xml" "$OUTPUT/$VM_NAME.xml" - find "$VMTMPDIR/virtfs" -name '*.pub' -print0 \ - | xargs -r0 cp --no-preserve=mode -t "$OUTPUT" printf "\nExported files:\n" >&2 find "$OUTPUT" -type f -printf '\t%p\n' >&2 fi -- cgit v1.2.3