detee-cli/scripts/detee-cli_injector.sh
2025-07-12 02:07:29 +03:00

233 lines
7.7 KiB
Bash
Executable File

#!/bin/bash
# SPDX-License-Identifier: Apache-2.0
set -e
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
echo_blue() {
echo -e "\033[34m$1\033[0m"
}
echo_yellow() {
echo -e "\033[0;33m$1\033[0m"
}
echo_red() {
echo -e "\033[0;31m$1\033[0m"
}
if [[ -z "${SERVER_ADDR}" ]]; then
echo_red "The SERVER_ADDR environment variable is needed."
exit 1
fi
if [[ -z "${MEASUREMENT}" ]]; then
echo_red "The MEASUREMENT environment variable is needed."
exit 1
fi
if [[ -z "${VM_HOSTNAME}" ]]; then
VM_HOSTNAME="detee-vm"
fi
if [[ "$LOG_LEVEL" == "DEBUG" ]]; then
echo Environment variables:
env | grep \
-e SERVER_ADDR \
-e SSH_KEY_FILE \
-e DETEE_INSTALL_URL \
-e DETEE_INSTALL_SHA \
-e MEASUREMENT \
-e VM_HOSTNAME
fi
server="$SERVER_ADDR"
ssh_pubkey_dir="${HOME}/.detee/cli/vms/ssh"
cert_dir="${HOME}/.detee/cli/vms/certs"
snp_reports_dir="${HOME}/.detee/cli/vms/snpreports"
keyfile_dir="${HOME}/.detee/cli/vms/secrets"
keyfile="${keyfile_dir}/${server}"
mkdir -p "$ssh_pubkey_dir"
mkdir -p "$cert_dir"
mkdir -p "$snp_reports_dir"
mkdir -p "$keyfile_dir"
server_crt="${cert_dir}/${server}.crt"
server_report="${snp_reports_dir}/${server}-report.bin"
mkdir -p "$(dirname "$keyfile")"
if [[ -f "$keyfile" ]]; then
echo_yellow "Found keyfile $keyfile"
else
echo_yellow "Creating keyfile $keyfile"
dd if=/dev/urandom "of=$keyfile" bs=32 count=4
fi
snpguest --help > /dev/null \
|| {
echo_yellow "Please install https://github.com/virtee/snpguest"
exit 3
}
try_countdown=20;
echo -n Trying $server
while [[ $try_countdown -gt 0 ]]; do
echo -n .
curl --max-time 1 -k "https://$server" > /dev/null 2>&1 && break
sleep 1
((try_countdown--))
done
echo
openssl s_client -connect "$server" </dev/null \
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > "$server_crt"
openssl x509 -in "$server_crt" -noout
crt_hash=$(openssl dgst -sha3-512 < "$server_crt" | awk '{ print $2}')
echo_blue "The certificate hash is $crt_hash"
echo_blue "Getting hash from attestation report..."
ip=$(echo "$server" | cut -d ":" -f1)
port=$(echo "$server" | cut -d ":" -f2)
echo "$server" | grep -F '[' | grep -F ']' && {
ip=$(echo "$server" | grep -oE '\[[a-f0-9\:]*\]')
port=$(echo "$server" | grep -oE '\]:[0-9]+' | cut -d ':' -f2)
}
curl --cacert "$server_crt" \
--resolve dtrfs-api:${port}:${ip} \
"https://dtrfs-api:${port}/report" > "${server_report}.base64"
cat "${server_report}.base64" | basenc --base64url -d > "$server_report"
rm "${server_report}.base64"
report_crt_hash=$( snpguest display report "$server_report" \
| grep "Report Data" -A 4 | tail -4 | tr '\n' ' ' | sed 's/\s//g')
echo_blue "The hash in the report is $report_crt_hash"
if [[ "${crt_hash,,}" != "${report_crt_hash,,}" ]]; then
echo The hash of the certificate does not match the hash from the report. Exiting.
exit 2
fi
echo_blue "The hash matches!"
echo_blue "Verifying AMD signature in attestation report..."
chip_id_hash=$( snpguest display report "$server_report" \
| grep "Chip ID:" -A 4 | tail -3 | tr '\n' ' ' | sed 's/\s//g' \
| md5sum | awk '{ print $1 }')
tcb_hash=$(grep -e "Committed TCB" -e "Reported TCB" -A 10 "$server_report" |
md5sum | awk '{ print $1 }')
vcek_path="${cert_dir}/${chip_id_hash}-${tcb_hash}.vcek.pem"
amd_certs_dir="${cert_dir}/amd_certs_${server}"
mkdir -p "$amd_certs_dir"
# TODO: add support for Genoa, Bergamo, Siena and Turin
# Or at least for Genoa...
[[ -f "${cert_dir}/ask-milan.pem" ]] || {
snpguest fetch ca pem "$amd_certs_dir" milan --endorser vcek
mv "${amd_certs_dir}/ask.pem" "${cert_dir}/ask-milan.pem"
mv "${amd_certs_dir}/ark.pem" "${cert_dir}/ark-milan.pem"
}
ln -fs "${cert_dir}/ask-milan.pem" "${amd_certs_dir}/ask.pem"
ln -fs "${cert_dir}/ark-milan.pem" "${amd_certs_dir}/ark.pem"
[[ -f "${vcek_path}" ]] || {
snpguest fetch vcek --processor-model milan pem "$amd_certs_dir" "$server_report" || {
# You are probably wondering what this weird shit is doing here.
# The AMD API for VCEK has throttling, and this scripts needs to run in parallel.
sleep 10
[[ -f "${vcek_path}" ]] || {
snpguest fetch vcek --processor-model milan pem "$amd_certs_dir" "$server_report"
}
}
mv "${amd_certs_dir}/vcek.pem" "${vcek_path}"
}
ln -fs "${vcek_path}" "${amd_certs_dir}/vcek.pem"
snpguest verify certs "$amd_certs_dir"
echo snpguest verify attestation "$amd_certs_dir" "$server_report"
snpguest verify attestation --processor-model milan "$amd_certs_dir" "$server_report"
echo_yellow "The attestation got verified based on the CA from AMD for the Milan generation!"
echo_blue "Verifying if measurement is $MEASUREMENT..."
guest_measurement=$( snpguest display report "$server_report" \
| grep Measurement -A 3 | tail -3 | tr '\n' ' ' | sed 's/\s//g' )
echo_blue "The guests's measurement is $guest_measurement"
if [[ "${guest_measurement,,}" != "${MEASUREMENT,,}" ]]; then
echo_red "The measurement of the server does not match."
echo_yellow "Please use this project to get your measurement: https://github.com/virtee/sev-snp-measure"
echo_yellow "After that, please sepcify the measurement ast the MEASUREMENT environment variable."
exit 2
fi
echo_yellow "The measurement matched!"
echo_blue "Creating ed25519 signature..."
ed25519_signature=$(detee-cli account sign "$server_crt" || exit 1)
if [[ -n "$DETEE_INSTALL_URL" ]] && [[ -n "$DETEE_INSTALL_URL" ]]; then
echo_blue "Injecting installation url: ${DETEE_INSTALL_URL}"
curl --fail-with-body \
-H "ed25519-signature: ${ed25519_signature}" \
--cacert "$server_crt" \
--resolve "dtrfs-api:${port}:${ip}" \
--data-urlencode "url=${DETEE_INSTALL_URL}" \
-d "sha=${DETEE_INSTALL_SHA}" \
-d "keyfile=$(cat "$keyfile" | basenc --base64url -w 0)" \
-d "hostname=${VM_HOSTNAME}" \
"https://dtrfs-api:${port}/install" || exit 1
else
echo
echo_blue "Injecting keyfile..."
curl --fail-with-body \
-H "ed25519-signature: ${ed25519_signature}" \
--cacert "$server_crt" \
--resolve "dtrfs-api:${port}:${ip}" \
-d "keyfile=$(cat "$keyfile" | basenc --base64url -w 0)" \
"https://dtrfs-api:${port}/decrypt" || exit 1
fi
[[ -z "$SSH_KEY_FILE" ]] && SSH_KEY_FILE="$HOME/.ssh/id_ed25519.pub"
echo
echo_blue "Injecting SSH key from file ${SSH_KEY_FILE}"
curl --fail-with-body \
-H "ed25519-signature: ${ed25519_signature}" \
--cacert "$server_crt" \
--resolve "dtrfs-api:${port}:${ip}" \
--data-urlencode "ssh_key=$(cat ${SSH_KEY_FILE})" \
"https://dtrfs-api:${port}/authorized_keys" || exit 1
echo
echo_blue "Downloading SSH keys from server"
curl --fail-with-body \
-H "ed25519-signature: ${ed25519_signature}" \
--cacert "$server_crt" \
--resolve "dtrfs-api:${port}:${ip}" \
"https://dtrfs-api:${port}/server_ssh_pubkeys" > "${ssh_pubkey_dir}/${server}" || {
echo_red "Could not grab SSH pubkeys from the server. This could be a MITM attack. Error:"
cat "${ssh_pubkey_dir}/${server}"
echo
echo_yellow "SSH at your own risk. IP: ${ip} Port: ${port} User: root"
curl -X POST --cacert "$server_crt" \
-H "ed25519-signature: ${ed25519_signature}" \
--resolve dtrfs-api:${port}:${ip} \
"https://dtrfs-api:${port}/switch_root" 2>/dev/null
exit 1
}
[[ "$port" != "22" ]] && ssh_keygen_ip="[$ip]:$port" || ssh_keygen_ip="$ip"
ssh-keygen -R "$ssh_keygen_ip" -f "${HOME}/.ssh/known_hosts2" || true
cat "${ssh_pubkey_dir}/${server}" |
awk '{ print $1 " " $2 }' |
xargs -I {} echo "$ssh_keygen_ip" {} >> "${HOME}/.ssh/known_hosts2"
echo
echo_blue "Starting guest OS. Give it a few seconds and try to SSH into the guest."
# this command is supposed to fail; it kills the API in the initrd
curl -X POST --cacert "$server_crt" \
-H "ed25519-signature: ${ed25519_signature}" \
--resolve dtrfs-api:${port}:${ip} \
"https://dtrfs-api:${port}/switch_root" 2>/dev/null || echo Success.