commit da4ce0789dc2a867dedad881d1b5903649da7904 Author: mawalu Date: Sun Nov 7 12:30:57 2021 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a411d73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +ssh/* +image/* +!**/.gitkeep diff --git a/README.md b/README.md new file mode 100644 index 0000000..eee4090 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# qemu-sandbox + +PoC shell sandboxing using QEMU and virtiofsd. + +## Installation + +Clone the repo and link `qsandbox` somewhere in your path. The script currently expects the `image` and `ssh` folder next to its location on disk. + +## Setup + +You'll need a few things for the script to work: + + * A ssh key pair in `ssh/qemu_ssh` & `ssh/qemu_ssh.pub`. You can link your default key pair or use the chance to generate one without a passphrase. + * `image/image.qcow2`, `image/vmlinuz-linux`, `image/initramfs-linux-custom.img`. The `build.sh` script can build these based on arch + +These requirements are currently hard coded but should be configurable in the future. + +## Usage + +``` +Usage: + qsandbox run [dir] - start sandbox and mount current working dir + qsandbox list - list running sandboxes + qsandbox enter - open ssh connection to a sandbox + qsandbox qemu - start the qemu process for a new sandbox, used by run +``` + +### `qsandbox run` + +Starts a new vm using `systemd-run` and `qsandbox qemu`, mounts the current working dir or the specified directory and opens an ssh session. + +### `qsandbox list` + +Lists all running sandboxes and their ssh ports. + +### `qsandbox enter` + +A wrapper around `ssh`. Takes port as only argument but defaults to `5555`. + +### `qsandbox qemu` + +Starts the actual sandbox. + +## Accessing the sandbox + +By default, QEMU exposes to ports for each sandbox. An ssh port (starting at `5555`) and an "app port" that can be used by some app in the vm (starting at `8000`). Ports should be configurable in the future. + +## Tips for custom images + +Mount the default share automatically: + +``` +echo -e "share.1\t/mnt\tvirtiofs\trw,_netdev\t0\t0" >> /etc/fstab +``` + +Disable auth on the QEMU serial console: + +``` +mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d +echo -e "[Service]\nExecStart=\nExecStart=-/usr/bin/agetty --autologin root -s %I 115200,38400,9600 vt102" > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf +```` + +# License + +MIT diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..e1bdf96 --- /dev/null +++ b/build.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Based on https://blog.stefan-koch.name/2020/05/31/automation-archlinux-qemu-installation + +src=https://ftp.halifax.rwth-aachen.de/archlinux/iso/2021.11.01/archlinux-bootstrap-2021.11.01-x86_64.tar.gz +archive=image/archlinux.tar.gz +image=image/image.raw +mountpoint=image/arch + +if [[ ! -f $archive ]]; then + wget $src -O $archive +fi + +mkdir -p $mountpoint +mkdir -p ssh + +qemu-img create -f raw $image 20G +loop="$(sudo losetup --show -f -P $image)" +sudo mkfs.ext4 "$loop" +sudo mount "$loop" "$mountpoint" +sudo tar zxf "$archive" -C "$mountpoint" --strip-components 1 + +key="$(cat ssh/qemu_ssh.pub)" + +sudo "$mountpoint/bin/arch-chroot" "$mountpoint" /bin/bash <> /etc/pacman.d/mirrorlist + +pacman-key --init +pacman-key --populate archlinux + +pacman -Syu --noconfirm +pacman -S --noconfirm base linux linux-firmware mkinitcpio dhcpcd dropbear kitty-terminfo +systemctl enable dhcpcd dropbear + +dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key + +# Standard Archlinux Setup +ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime +hwclock --systohc +echo en_US.UTF-8 UTF-8 >> /etc/locale.gen +locale-gen +echo LANG=en_US.UTF-8 > /etc/locale.conf +echo arch-qemu > /etc/hostname +echo -e '127.0.0.1 localhost\n::1 localhost' >> /etc/hosts + +mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d +echo -e "[Service]\nExecStart=\nExecStart=-/usr/bin/agetty --autologin root -s %I 115200,38400,9600 vt102" > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf + +echo -e "share.1\t/mnt\tvirtiofs\trw,_netdev\t0\t0" >> /etc/fstab + +mkdir /root/.ssh +echo "$key" > /root/.ssh/authorized_keys + +# Create an initramfs without autodetect, because this breaks with the +# combination host/chroot/qemu +linux_version=\$(ls /lib/modules/ | sort -V | tail -n 1) +mkinitcpio -c /etc/mkinitcpio.conf -S autodetect --kernel \$linux_version -g /boot/initramfs-linux-custom.img + +echo 'root:root' | chpasswd +EOL + +cp "$mountpoint/boot/vmlinuz-linux" image/ +cp "$mountpoint/boot/initramfs-linux-custom.img" image/ + +sudo "$mountpoint/bin/arch-chroot" "$mountpoint" /bin/bash < /dev/null && pwd )" +qemu_args=() + +function run_virtiofsd { + local shares=("$@") + local share_count=1 + + { + echo "mount -t tmpfs swap /var/run" + for m in "${shares[@]}"; do + local share_name="share.${share_count}" + local socket="$tmp_dir/$share_name" + + echo "/usr/lib/qemu/virtiofsd --socket-path=$socket -o source=$m &" + echo "echo starting $m $socket" + + share_count=$((share_count+1)) + done + } | (unshare --user --map-root-user --mount bash)& + + local mounts=${#shares[@]} + + for i in $(seq "$mounts"); do + qemu_args+=("-chardev" "socket,id=share.$i,path=$tmp_dir/share.$i") + qemu_args+=("-device" "vhost-user-fs-pci,queue-size=1024,chardev=share.$i,tag=share.$i") + done + +} + +function start_qemu { + if [[ $# -lt 3 ]]; then + echo "Usage $(basename "$0") qemu [mount]" + exit 1 + fi + + trap finish EXIT + + local ssh_port="$1" + local app_port="$2" + tmp_dir="$3" + + local image + image="$tmp_dir/$(openssl rand -hex 12).qcow2" + + qemu-img create -b "$script_dir/image/image.qcow2" -F qcow2 -f qcow2 "$image" + + run_virtiofsd "${4:-$PWD}" + + qemu-system-x86_64 \ + -enable-kvm -cpu host -m 512m -smp 2 \ + -kernel "$script_dir/image/vmlinuz-linux" -append "earlyprintk=ttyS0 console=ttyS0 root=/dev/vda rw quiet" \ + -initrd "$script_dir/image/initramfs-linux-custom.img" \ + -m 4G -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on -numa node,memdev=mem \ + -device virtio-rng-pci \ + -bios /usr/share/qemu/qboot.rom \ + -drive if=virtio,file="$image",format=qcow2 \ + -netdev user,id=net0,hostfwd=tcp::"$ssh_port"-:22,hostfwd=tcp::"$app_port"-:8000 \ + -device virtio-net-pci,netdev=net0 \ + -nodefaults -no-user-config -nographic \ + -serial stdio "${qemu_args[@]}" +} + +function get_port { + local port="$1" + + if netstat -tulen | grep -q "$port"; then + get_port "$((port+1))" + else + echo "$port" + fi +} + +function run { + local ssh_port + ssh_port="$(get_port 5555)" + local app_port + app_port="$(get_port 8000)" + + local work_dir="${1:-$PWD}" + + tmp_dir="$(mktemp -d --suffix=.qemu)" + + echo "$work_dir" + + echo "$ssh_port" > "$tmp_dir/ssh" + echo "$ssh_port" > "$tmp_dir/app" + echo "$work_dir" > "$tmp_dir/work_dir" + + systemd-run --user -d "$0" qemu "$ssh_port" "$app_port" "$tmp_dir" "$work_dir" + + echo "[ ] SSH Port: $ssh_port" + echo "[ ] App Port: $app_port" + + sleep 1 + enter "$ssh_port" +} + +function finish { + rm -rf "$tmp_dir" +} + +function list { + for d in /tmp/*.qemu; do + if [[ -f "$d/ssh" ]]; then + echo "$(cat "$d/ssh") - $(cat "$d/work_dir")" + fi + done +} + +function enter { + if [[ "$#" -eq 1 ]]; then + local port="$1" + else + echo "Using default port 5555" + local port="5555" + fi + + ssh -i "$script_dir/ssh/qemu_ssh" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@localhost -p "$port" +} + +function usage { + local bin + bin="$(basename "$0")" + + echo "Usage:" + echo " $bin run [dir] - start sandbox and mount current working dir" + echo " $bin list - list running sandboxes" + echo " $bin enter - open ssh connection to a sandbox" + echo " $bin qemu - start the qemu process for a new sandbox, used by run" +} + +function main { + if [[ "$#" -lt 1 ]]; then + usage "$@" + exit 1 + fi + + local cmd="$1" + shift + + if [[ "$cmd" == "qemu" ]]; then + start_qemu "$@" + fi + + if [[ "$cmd" == "list" ]]; then + list + fi + + if [[ "$cmd" == "run" ]]; then + run "$@" + fi + + if [[ "$cmd" == "enter" ]]; then + enter "$@" + fi +} + +main "$@" + diff --git a/ssh/.gitkeep b/ssh/.gitkeep new file mode 100644 index 0000000..e69de29