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