Initial commit
This commit is contained in:
commit
da4ce0789d
|
@ -0,0 +1,3 @@
|
||||||
|
ssh/*
|
||||||
|
image/*
|
||||||
|
!**/.gitkeep
|
|
@ -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
|
|
@ -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 <<EOL
|
||||||
|
set -v
|
||||||
|
|
||||||
|
echo 'Server = http://ftp.uni-bayreuth.de/linux/archlinux/\$repo/os/\$arch' >> /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 <<EOL
|
||||||
|
pacman -Rs --noconfirm linux linux-firmware mkinitcpio
|
||||||
|
rm -r /var/cache/pacman/pkg
|
||||||
|
rm -r /boot
|
||||||
|
EOL
|
||||||
|
|
||||||
|
sudo umount "$mountpoint"
|
||||||
|
sudo losetup -d "$loop"
|
||||||
|
qemu-img convert -f raw -O qcow2 "$image" image/image.qcow2
|
||||||
|
rm image/image.raw
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
script_dir="$( cd -- "$( dirname -- "$(readlink -f "${BASH_SOURCE[0]}" )" )" &> /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 <ssh_port> <app_port> <tmp_dir> [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 "$@"
|
||||||
|
|
Loading…
Reference in New Issue