Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNP Guest Launch for Fedora and Ubuntu #25

Closed
6 changes: 6 additions & 0 deletions docs/snp.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ Launch the guest:
./snp.sh launch-guest
```

User can launch various unique SNP enabled guests at the same time using
different GUEST_NAME and HOST_SSH_PORT environment variables. Export the following environment variables to achieve this:
```
export GUEST_NAME="guest-name"
export HOST_SSH_PORT="your-host-SSH-port"
```
## Accessing the Guest via SSH

Once launched, the guest can be accessed with the following SSH command:
Expand Down
160 changes: 138 additions & 22 deletions tools/snp.sh
Copy link
Contributor Author

@LakshmiSaiHarika LakshmiSaiHarika Dec 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fedora Guest Launch Feature with clean commit history and code cleanup is tracked in the:

fedora-snp-guest-launch-support branch

I will update this PR once the other PR's are addressed

Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,26 @@ trap cleanup EXIT
# Working directory setup
WORKING_DIR="${WORKING_DIR:-$HOME/snp}"
SETUP_WORKING_DIR="${SETUP_WORKING_DIR:-${WORKING_DIR}/setup}"
LAUNCH_WORKING_DIR="${LAUNCH_WORKING_DIR:-${WORKING_DIR}/launch}"
GUEST_NAME="${GUEST_NAME:-snp-guest}"
LAUNCH_WORKING_DIR="${LAUNCH_WORKING_DIR:-${WORKING_DIR}/launch/${GUEST_NAME}}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LAUNCH_WORKING_DIR="${LAUNCH_WORKING_DIR:-${WORKING_DIR}/launch}"

Add command line option for separate user guest name
--guest_name Specify different guest name

ATTESTATION_WORKING_DIR="${ATTESTATION_WORKING_DIR:-${WORKING_DIR}/attest}"

# Export environment variables
COMMAND="help"
UPM=true
SKIP_IMAGE_CREATE=false
HOST_SSH_PORT="${HOST_SSH_PORT:-10022}"
GUEST_NAME="${GUEST_NAME:-snp-guest}"
GUEST_SIZE_GB="${GUEST_SIZE_GB:-20}"
GUEST_MEM_SIZE_MB="${GUEST_MEM_SIZE_MB:-2048}"
GUEST_SMP="${GUEST_SMP:-4}"
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:-""}"
Copy link
Contributor Author

@LakshmiSaiHarika LakshmiSaiHarika Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}")

GUEST_ROOT_LABEL_UBUNTU="root"

IMAGE="${IMAGE:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}.img}"

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"

GUEST_KERNEL_APPEND="root=LABEL=${GUEST_ROOT_LABEL} ro console=ttyS0"
QEMU_CMDLINE_FILE="${QEMU_CMDLINE:-${LAUNCH_WORKING_DIR}/qemu.cmdline}"
BASE_CLOUD_IMAGE="${BASE_CLOUD_IMAGE:-${WORKING_DIR}/base_cloud_image.img}"
IMAGE="${IMAGE:-${LAUNCH_WORKING_DIR}/${GUEST_NAME}.img}"
GENERATED_INITRD_BIN="${SETUP_WORKING_DIR}/initrd.img"

Expand Down Expand Up @@ -243,6 +244,7 @@ install_ubuntu_dependencies() {

# cloud-utils dependency
sudo apt install -y cloud-image-utils
sudo apt install -y genisoimage

# Virtualization tools for resizing image
# virt-resize currently does not work with cloud-init images. It changes the partition
Expand Down Expand Up @@ -329,6 +331,8 @@ install_fedora_dependencies() {

# cloud-utils dependency
sudo dnf install -y cloud-init
sudo dnf install -y genisoimage
sudo dnf install -y qemu-img
}

get_linux_distro() {
Expand Down Expand Up @@ -501,9 +505,29 @@ generate_guest_ssh_keypair() {
ssh-keygen -q -t ed25519 -N '' -f "${GUEST_SSH_KEY_PATH}" <<<y
}

download_guest_os_image(){
local linux_distro=$(get_linux_distro)

# Set the guest OS image-cloud init URL based on the Host OS type
case ${linux_distro} in
ubuntu)
CLOUD_INIT_IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLOUD_INIT_IMAGE_URL=${CLOUD_INIT_IMAGE_URL_UBUNTU}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMAGE_BASENAME=${IMAGE_BASENAME_UBUNTU}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMAGE_BASENAME=$(basename "${CLOUD_INIT_IMAGE_URL}")

;;
fedora)
CLOUD_INIT_IMAGE_URL="https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/38/Cloud/x86_64/images/Fedora-Cloud-Base-38-1.6.x86_64.qcow2"
;;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLOUD_INIT_IMAGE_URL=${CLOUD_INIT_IMAGE_URL_FEDORA}

esac

# Download the guest os-image and change name
if [ ! -f "${BASE_CLOUD_IMAGE}" ]; then
wget "${CLOUD_INIT_IMAGE_URL}" -O "${BASE_CLOUD_IMAGE}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wget "${CLOUD_INIT_IMAGE_URL}" -O "${LAUNCH_WORKING_DIR}/IMAGE_BASENAME_UBUNTU"

fi
cp -v "${BASE_CLOUD_IMAGE}" "${IMAGE}"
}

cloud_init_create_data() {
if [[ -f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml" && \
-f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" && \
if [[ -f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/meta-data" && \
-f "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/user-data" && \
-f "${IMAGE}" ]]; then
echo -e "cloud-init data already generated"
return 0
Expand All @@ -512,13 +536,13 @@ cloud_init_create_data() {
local pub_key=$(cat "${GUEST_SSH_KEY_PATH}.pub")

# Seed image metadata
cat > "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-metadata.yaml" <<EOF
cat > "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/meta-data" <<EOF
instance-id: "${GUEST_NAME}"
local-hostname: "${GUEST_NAME}"
EOF

# Seed image user data
cat > "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-user-data.yaml" <<EOF
cat > "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/user-data" <<EOF
#cloud-config
chpasswd:
expire: false
Expand All @@ -534,13 +558,11 @@ users:
- ${pub_key}
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 the seed image with metadata and user data using genisoimage utility
genisoimage -output "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/ciiso.iso" -volid cidata -joliet -rock "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/user-data" "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/meta-data"

# 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 @@ -713,6 +735,9 @@ copy_launch_binaries() {
# Create directory
mkdir -p "${LAUNCH_WORKING_DIR}"

# Create a separate guest data directory
mkdir -p "${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data"

# Copy the setup generated bins to the guest launch directory
# initrd is copied after the first guest boot and is scp-ed off
cp "${OVMF_BIN}" "${LAUNCH_WORKING_DIR}"
Expand Down Expand Up @@ -879,6 +904,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="cloudimg-rootfs"
GUEST_KERNEL_APPEND="root=LABEL=${GUEST_ROOT_LABEL} ro console=ttyS0"
;;
fedora)
GUEST_ROOT_LABEL="fedora"
GUEST_KERNEL_APPEND="console=ttys0 root=LABEL=${GUEST_ROOT_LABEL} ro rootflags=subvol=root"
;;
*)
>&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 +991,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=${LAUNCH_WORKING_DIR}/${GUEST_NAME}-data/ciiso.iso"
fi

local guest_kernel_installed_file="${LAUNCH_WORKING_DIR}/guest_kernel_already_installed"
Expand All @@ -918,16 +1001,43 @@ 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 guest_kernel_basename="vmlinuz*${guest_kernel_version}*"

# Uses package manager command based on the guest OS linux distro
local package_install_command=$(get_package_install_command)

# Copy the built SNP guest kernel package into the guest
wait_and_retry_command "scp_guest_command ${guest_kernel_package} ${GUEST_USER}@localhost:/home/${GUEST_USER}"

# Install the guest SNP kernel package inside the guest
ssh_guest_command "sudo ${package_install_command} /home/${GUEST_USER}/$(basename ${guest_kernel_package})"

# Copy the installed guest initial ram disk 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}"

# Copy the installed SNP guest kernel from guest into the host
local vmlinuz_filepath=$(ssh_guest_command "ls /boot/${guest_kernel_basename}")
vmlinuz_filepath=$(echo ${vmlinuz_filepath}| tr -d '\r')
ssh_guest_command "sudo cp $(realpath ${vmlinuz_filepath}) /home/${GUEST_USER}"
ssh_guest_command "sudo chmod 644 /home/${GUEST_USER}/$(basename $(realpath ${vmlinuz_filepath}))"
scp_guest_command "${GUEST_USER}@localhost:/home/${GUEST_USER}/$(basename $(realpath ${vmlinuz_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
sed -i -e "s|^\(INITRD_BIN=\).*$|\1\"${LAUNCH_WORKING_DIR}/${guest_initrd_basename}\"|g" "${LAUNCH_WORKING_DIR}/source-bins"
# Update guest initrd, kernel binary file path in the host
GENERATED_INITRD_BIN=$(ls ${LAUNCH_WORKING_DIR}/${guest_initrd_basename} )
GENERATED_KERNEL_BIN=$(ls ${LAUNCH_WORKING_DIR}/${guest_kernel_basename}* )

# Update the source bin file with the latest initrd & kernel file path
sed -i -e "s|^\(INITRD_BIN=\).*$|\1\"${GENERATED_INITRD_BIN}\"|g" "${LAUNCH_WORKING_DIR}/source-bins"
sed -i -e "s|^\(KERNEL_BIN=\).*$|\1\"${GENERATED_KERNEL_BIN}\"|g" "${LAUNCH_WORKING_DIR}/source-bins"

# 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 Down Expand Up @@ -958,6 +1068,12 @@ setup_and_launch_guest() {
# snp object and kernel-hashes on
add_qemu_cmdline_opts "-object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on"

# Update guest initrd, kernel to the updated guest SNP kernel version
source "${LAUNCH_WORKING_DIR}/source-bins"

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

# ovmf, initrd, kernel and append options
add_qemu_cmdline_opts "-bios ${OVMF_BIN}"
add_qemu_cmdline_opts "-initrd ${INITRD_BIN}"
Expand Down Expand Up @@ -1396,7 +1512,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