Skip to content
Open
10 changes: 10 additions & 0 deletions docs/snp.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ The `--non-upm` option can be specified with the above command if a non-upm vers
of the kernel is desired. The `setup-host` command must be run with this same option
if launching the guest with a non-upm kernel.

A user can launch separate SNP guests at the same time using unique guest name and guest qemu port.
A user can set guest name and guest port with the `--guest-name` option and `--guest-port` option while the launch of a separate SNP guest as follows:
```
./snp.sh launch-guest --guest-name <user-guest-name> --guest-port <user-guest-port>
```

Attest the guest using the following command:
```
./snp.sh attest-guest
Expand All @@ -105,6 +111,10 @@ All script created guests can be stopped by running the following command:
./snp.sh stop-guests
```

User created SNP guest via guest-name option can be stopped with the `--guest-name` option as follows:
```
./snp.sh stop-guests --guest-name <user-guest-name>
```
## BYO Image

The SNP script utility provides support for the user to provide their own image.
Expand Down
184 changes: 170 additions & 14 deletions tools/snp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ CPU_MODEL="${CPU_MODEL:-EPYC-v4}"
GUEST_USER="${GUEST_USER:-amd}"
GUEST_PASS="${GUEST_PASS:-amd}"
GUEST_SSH_KEY_PATH="${GUEST_SSH_KEY_PATH:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}-key}"
GUEST_ROOT_LABEL="${GUEST_ROOT_LABEL:-cloudimg-rootfs}"
GUEST_ROOT_LABEL="${GUEST_ROOT_LABEL:""}"
GUEST_KERNEL_APPEND="root=LABEL=${GUEST_ROOT_LABEL} ro console=ttyS0"
QEMU_CMDLINE_FILE="${QEMU_CMDLINE:-${LAUNCH_WORKING_DIR}/qemu.cmdline}"
IMAGE="${IMAGE:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}.img}"
SEED_IMAGE="${SEED_IMAGE:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}-seed.img}"
GENERATED_INITRD_BIN="${SETUP_WORKING_DIR}/initrd.img"

# URLs and repos
Expand All @@ -98,6 +99,15 @@ SNPGUEST_URL="https://github.com/virtee/snpguest.git"
SNPGUEST_BRANCH="tags/v0.8.0"
NASM_SOURCE_TAR_URL="https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/nasm-2.16.01.tar.gz"
CLOUD_INIT_IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
CLOUD_INIT_IMAGE_URL_UBUNTU="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
IMAGE_BASENAME_UBUNTU=$(basename "${CLOUD_INIT_IMAGE_URL_UBUNTU}")
CLOUD_INIT_IMAGE_URL_FEDORA="https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/38/Cloud/x86_64/images/Fedora-Cloud-Base-38-1.6.x86_64.qcow2"
IMAGE_BASENAME_FEDORA=$(basename "${CLOUD_INIT_IMAGE_URL_FEDORA}")
IMAGE_BASENAME=""
GUEST_ROOT_LABEL_UBUNTU="cloudimg-rootfs"
GUEST_KERNEL_APPEND_UBUNTU="root=LABEL=${GUEST_ROOT_LABEL_UBUNTU} ro console=ttyS0"
GUEST_ROOT_LABEL_FEDORA="fedora"
GUEST_KERNEL_APPEND_FEDORA="console=ttys0 root=LABEL=${GUEST_ROOT_LABEL_FEDORA} ro rootflags=subvol=root"
DRACUT_TARBALL_URL="https://github.com/dracutdevs/dracut/archive/refs/tags/059.tar.gz"
SEV_SNP_MEASURE_VERSION="0.0.11"

Expand All @@ -117,6 +127,8 @@ usage() {
>&2 echo " where OPTIONS are:"
>&2 echo " -n|--non-upm Build AMDSEV non UPM kernel (sev-snp-devel)"
>&2 echo " -i|--image Path to existing image file"
>&2 echo " -g-n|--guest-name Create a separate guest launch working directory"
>&2 echo " -g-p|--guest-port Set guest qemu port for networking"
>&2 echo " -h|--help Usage information"

return 1
Expand Down Expand Up @@ -501,6 +513,67 @@ generate_guest_ssh_keypair() {
ssh-keygen -q -t ed25519 -N '' -f "${GUEST_SSH_KEY_PATH}" <<<y
}

create_guest_seed_image(){
local linux_distro=$(get_linux_distro)

case ${linux_distro} in
ubuntu)
cloud-localds "${SEED_IMAGE}" \
"${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" \
"${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml"
;;
fedora)
mv -v "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" "${LAUNCH_WORKING_DIR}/user-data"
mv -v "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml" "${LAUNCH_WORKING_DIR}/meta-data"

genisoimage -output "${SEED_IMAGE}" \
-volid cidata \
-joliet \
-rock \
"${LAUNCH_WORKING_DIR}/user-data" \
"${LAUNCH_WORKING_DIR}/meta-data"

mv -v "${LAUNCH_WORKING_DIR}/user-data" "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml"
mv -v "${LAUNCH_WORKING_DIR}/meta-data" "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml"
;;
*)
>&2 echo -e "ERROR: ${linux_distro}"
return 1
;;
esac
}

download_guest_os_image(){
local linux_distro=$(get_linux_distro)

# Set the guest OS image-cloud init URL, guest image basename based on the Host OS type
case ${linux_distro} in
ubuntu)
CLOUD_INIT_IMAGE_URL=${CLOUD_INIT_IMAGE_URL_UBUNTU}
IMAGE_BASENAME=${IMAGE_BASENAME_UBUNTU}
;;
fedora)
CLOUD_INIT_IMAGE_URL=${CLOUD_INIT_IMAGE_URL_FEDORA}
IMAGE_BASENAME=${IMAGE_BASENAME_FEDORA}
;;
*)
>&2 echo -e "ERROR: ${linux_distro}"
return 1
;;
esac

local base_launch_directory=${LAUNCH_WORKING_DIR//"/$GUEST_NAME"*/}
local base_guest_image=${base_launch_directory}/${IMAGE_BASENAME}

# Download image if not present already
if [ ! -f ${base_guest_image} ]; then
wget "${CLOUD_INIT_IMAGE_URL}" -O ${base_guest_image}
fi

# Copy image to launch directory
cp -v ${base_guest_image} "${IMAGE}"
}

cloud_init_create_data() {
if [[ -f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml" && \
-f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" && \
Expand Down Expand Up @@ -535,12 +608,10 @@ users:
EOF

# Create the seed image with metadata and user data
cloud-localds "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-seed.img" \
"${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" \
"${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml"
create_guest_seed_image

# Download ubuntu 20.04 and change name
wget "${CLOUD_INIT_IMAGE_URL}" -O "${IMAGE}"
# Download Guest Image from cloud init URL
download_guest_os_image
}

resize_guest() {
Expand Down Expand Up @@ -879,6 +950,64 @@ build_and_install_amdsev() {
save_binary_paths
}

get_package_install_command(){
local linux_distro=$(get_linux_distro)

case ${linux_distro} in
ubuntu)
echo "dpkg -i"
;;
fedora)
echo "dnf install -y"
;;
*)
>&2 echo -e "ERROR: ${linux_distro}"
return 1
;;
esac
}

get_guest_kernel_package(){
local linux_distro=$(get_linux_distro)
local guest_kernel_version=$(get_guest_kernel_version)

pushd "${SETUP_WORKING_DIR}/AMDSEV/linux" >/dev/null
case ${linux_distro} in
ubuntu)
echo $(realpath linux-image*${guest_kernel_version}*.deb| grep -v dbg)
;;
fedora)
guest_kernel_version="${guest_kernel_version//-/_}" # SNP kernel RPM package name contains _ in the version
echo $(realpath $(ls -t kernel-*${guest_kernel_version}*.rpm| grep -v header| head -1))
;;
*)
>&2 echo -e "ERROR: ${linux_distro}"
return 1
;;
esac
popd>/dev/null
}

set_default_guest_kernel_append() {
local linux_distro=$(get_linux_distro)

# Sets default kernel append based on the linux distro
case ${linux_distro} in
ubuntu)
GUEST_ROOT_LABEL="${GUEST_ROOT_LABEL_UBUNTU}"
GUEST_KERNEL_APPEND="${GUEST_KERNEL_APPEND_UBUNTU}"
;;
fedora)
GUEST_ROOT_LABEL="${GUEST_ROOT_LABEL_FEDORA}"
GUEST_KERNEL_APPEND="${GUEST_KERNEL_APPEND_FEDORA}"
;;
*)
>&2 echo -e "ERROR: ${linux_distro}"
return 1
;;
esac
}

setup_and_launch_guest() {
# Return error if user specified file that doesn't exist
if [ ! -f "${IMAGE}" ] && ${SKIP_IMAGE_CREATE}; then
Expand Down Expand Up @@ -908,7 +1037,7 @@ setup_and_launch_guest() {

# Add seed image option to qemu cmdline
add_qemu_cmdline_opts "-device scsi-hd,drive=disk1"
add_qemu_cmdline_opts "-drive if=none,id=disk1,format=raw,file=${LAUNCH_WORKING_DIR}/${GUEST_NAME}-seed.img"
add_qemu_cmdline_opts "-drive if=none,id=disk1,format=raw,file=${SEED_IMAGE}"
fi

local guest_kernel_installed_file="${LAUNCH_WORKING_DIR}/guest_kernel_already_installed"
Expand All @@ -918,16 +1047,26 @@ setup_and_launch_guest() {

# Install the guest kernel, retrieve the initrd and then reboot
local guest_kernel_version=$(get_guest_kernel_version)
local guest_kernel_deb=$(echo "$(realpath ${SETUP_WORKING_DIR}/AMDSEV/linux/linux-image*snp-guest*.deb)" | grep -v dbg)
local guest_initrd_basename="initrd.img-${guest_kernel_version}"
wait_and_retry_command "scp_guest_command ${guest_kernel_deb} ${GUEST_USER}@localhost:/home/${GUEST_USER}"
ssh_guest_command "sudo dpkg -i /home/${GUEST_USER}/$(basename ${guest_kernel_deb})"
scp_guest_command "${GUEST_USER}@localhost:/boot/${guest_initrd_basename}" "${LAUNCH_WORKING_DIR}"
local guest_kernel_package=$(get_guest_kernel_package)
local guest_initrd_basename="init*${guest_kernel_version}*"
local os_package_install_command=$(get_package_install_command)
wait_and_retry_command "scp_guest_command ${guest_kernel_package} ${GUEST_USER}@localhost:/home/${GUEST_USER}"
ssh_guest_command "sudo ${os_package_install_command} /home/${GUEST_USER}/$(basename ${guest_kernel_package})"

# Copy the guest initrd in the guest home directory into the host
local initrd_filepath=$(ssh_guest_command "ls /boot/${guest_initrd_basename} | grep -v kdump")
initrd_filepath=$(echo ${initrd_filepath}| tr -d '\r')
ssh_guest_command "sudo cp $(realpath ${initrd_filepath}) /home/${GUEST_USER}"
ssh_guest_command "sudo chmod 644 /home/${GUEST_USER}/$(basename $(realpath ${initrd_filepath}))"
scp_guest_command "${GUEST_USER}@localhost:/home/${GUEST_USER}/$(basename $(realpath ${initrd_filepath}))" "${LAUNCH_WORKING_DIR}"

ssh_guest_command "sudo shutdown now" || true
echo "true" > "${guest_kernel_installed_file}"

# Update the initrd file path and name in the guest launch source-bins file
# Update the initrd file path and name(initrd/initramfs) in the guest launch source-bins file
guest_initrd_basename=$(basename $(realpath ${initrd_filepath}))
sed -i -e "s|^\(INITRD_BIN=\).*$|\1\"${LAUNCH_WORKING_DIR}/${guest_initrd_basename}\"|g" "${LAUNCH_WORKING_DIR}/source-bins"
INITRD_BIN="${LAUNCH_WORKING_DIR}/${guest_initrd_basename}"

# Wait for shutdown to complete
wait_and_retry_command "! ps aux | grep \"${WORKING_DIR}.*qemu.*${IMAGE}\" | grep -v \"tail.*qemu.log\" | grep -v \"grep.*qemu\""
Expand All @@ -937,6 +1076,9 @@ setup_and_launch_guest() {
return 0
fi

# Set the default guest kernel append parameter as per the linux distro
[ -z "${GUEST_ROOT_LABEL}" ] && set_default_guest_kernel_append

# Add sev-guest module to host generated initrd
# To be used as the guest initrd
# NO LONGER NEEDED: initrd built after kernel generation (build_guest_initrd)
Expand Down Expand Up @@ -1319,6 +1461,20 @@ main() {
shift; shift
;;

-g-n|--guest-name)
GUEST_NAME="${2}"
LAUNCH_WORKING_DIR="${LAUNCH_WORKING_DIR}/${GUEST_NAME}"
GUEST_SSH_KEY_PATH="${LAUNCH_WORKING_DIR}/${GUEST_NAME}-key"
QEMU_CMDLINE_FILE="${LAUNCH_WORKING_DIR}/qemu.cmdline"
IMAGE="${LAUNCH_WORKING_DIR}/${GUEST_NAME}.img"
shift; shift
;;

-g-p|--guest-port)
HOST_SSH_PORT="${2}"
shift; shift
;;

setup-host)
COMMAND="setup-host"
shift
Expand Down Expand Up @@ -1396,7 +1552,7 @@ main() {

echo -e "Guest SSH port forwarded to host port: ${HOST_SSH_PORT}"
echo -e "The guest is running in the background. Use the following command to access via SSH:"
echo -e "ssh -p ${HOST_SSH_PORT} -i ${LAUNCH_WORKING_DIR}/snp-guest-key amd@localhost"
echo -e "ssh -p ${HOST_SSH_PORT} -i ${GUEST_SSH_KEY_PATH} ${GUEST_USER}@localhost"
;;

attest-guest)
Expand Down