|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +set -x |
| 4 | + |
| 5 | +# ================================ |
| 6 | +# GLOBAL VARIABLES |
| 7 | +# ================================ |
| 8 | + |
| 9 | +if command -v nvidia-smi; then |
| 10 | + GPU_MODE=1 |
| 11 | +fi |
| 12 | + |
| 13 | +CONFIG_DIR=/mnt/config |
| 14 | +CONFIG_FILE=$CONFIG_DIR/secret-vm.json |
| 15 | + |
| 16 | +SECRET_FS_PERSISTENT=$(jq -r '.secret_fs_persistent' $CONFIG_FILE) |
| 17 | +SECRET_FS_MOUNT_POINT=/mnt/secure |
| 18 | +SECRET_FS_DEVICE=/dev/vda |
| 19 | + |
| 20 | +CERT_DIR=$SECRET_FS_MOUNT_POINT/cert |
| 21 | +CERT_NAME=secret_vm |
| 22 | +CERT_PATH=$CERT_DIR/"$CERT_NAME"_cert.pem |
| 23 | +DOMAIN_NAME=$(jq -r '.domain_name' $CONFIG_FILE) |
| 24 | + |
| 25 | + |
| 26 | +PATH_ATTESTATION_TDX=$SECRET_FS_MOUNT_POINT/tdx_attestation.txt |
| 27 | +PATH_ATTESTATION_GPU_1=$SECRET_FS_MOUNT_POINT/gpu_attestation.txt |
| 28 | +PATH_ATTESTATION_GPU_2=$SECRET_FS_MOUNT_POINT/gpu_attestation_token.txt |
| 29 | + |
| 30 | +KMS_SERVICE_ID=0 |
| 31 | + |
| 32 | +# the following variables will be set up during setup_env function call |
| 33 | +G_ERROR="" |
| 34 | +SEED="" |
| 35 | +QUOTE="" |
| 36 | +COLLATERAL="" |
| 37 | + |
| 38 | +# ================================ |
| 39 | +# INCLUDES |
| 40 | +# ================================ |
| 41 | + |
| 42 | +source secret-vm-generate-cert.sh |
| 43 | +source utils.sh |
| 44 | + |
| 45 | +# ================================ |
| 46 | +# FUNCTIONS |
| 47 | +# ================================ |
| 48 | + |
| 49 | +setup_env() { |
| 50 | + # get random 32 bytes |
| 51 | + SEED=$(crypt-tool rand) |
| 52 | + if ! test_valid_hex_data "SEED"; then |
| 53 | + return 1 |
| 54 | + fi |
| 55 | + |
| 56 | + # use it to derive initial pubkey |
| 57 | + local pubkey=$(crypt-tool generate-key -s $SEED) |
| 58 | + if ! test_valid_hex_data "pubkey"; then |
| 59 | + return 1 |
| 60 | + fi |
| 61 | + |
| 62 | + # get attestation with this pubkey as report data |
| 63 | + echo "Getting initial attestation..." |
| 64 | + |
| 65 | + QUOTE=$(attest-tool attest $pubkey) |
| 66 | + if ! test_valid_hex_data "QUOTE"; then |
| 67 | + return 1 |
| 68 | + fi |
| 69 | + |
| 70 | + COLLATERAL=$(curl -s -X POST https://pccs.scrtlabs.com/dcap-tools/quote-parse -H "Content-Type: application/json" -d "{\"quote\": \"$QUOTE\"}" |jq '.collateral') |
| 71 | + COLLATERAL=$(echo $COLLATERAL | sed 's/"//g') # remove quotes |
| 72 | + if ! test_valid_hex_data "COLLATERAL"; then |
| 73 | + return 1 |
| 74 | + fi |
| 75 | +} |
| 76 | + |
| 77 | +setup_gpu() { |
| 78 | + nvidia-smi conf-compute -srs 1 |
| 79 | + nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place |
| 80 | +} |
| 81 | + |
| 82 | +setup_docker() { |
| 83 | + systemctl stop docker |
| 84 | + |
| 85 | + # setup docker config |
| 86 | + mkdir -p /etc/docker |
| 87 | + echo '{}' > /etc/docker/daemon.json |
| 88 | + test -n "$GPU_MODE" && nvidia-ctk runtime configure --runtime=docker |
| 89 | + jq ". + {data-root: \"$SECRET_FS_MOUNT_POINT\"}" /etc/docker/daemon.json > tmp.json |
| 90 | + mv tmp.json /etc/docker/daemon.json |
| 91 | + |
| 92 | + # create docker working directory |
| 93 | + mkdir -p $SECRET_FS_MOUNT_POINT/docker_wd |
| 94 | + cp $CONFIG_DIR/docker-compose.yaml $SECRET_FS_MOUNT_POINT/docker_wd |
| 95 | + |
| 96 | + pushd . |
| 97 | + cd $SECRET_FS_MOUNT_POINT/docker_wd |
| 98 | + # these files are optional |
| 99 | + #local kms_res=$(kms-query get_env_by_image $QUOTE $COLLATERAL) |
| 100 | + cp $CONFIG_DIR/docker-files.tar . && tar xvf ./docker-files.tar || true |
| 101 | + popd |
| 102 | + |
| 103 | + systemctl start docker |
| 104 | +} |
| 105 | + |
| 106 | +setup_network() { |
| 107 | + systemctl stop systemd-networkd |
| 108 | + local ip_addr=$(jq -r '.ip_addr' $CONFIG_FILE) |
| 109 | + local gateway=$(jq -r '.gateway' $CONFIG_FILE) |
| 110 | + sed -i "s%IP_ADDR_PLACEHOLDER%$ip_addr%" /usr/lib/systemd/network/10-enp.network |
| 111 | + sed -i "s%GATEWAY_PLACEHOLDER%$gateway%" /usr/lib/systemd/network/10-enp.network |
| 112 | + systemctl start systemd-networkd |
| 113 | +} |
| 114 | + |
| 115 | +setup_secret_fs() { |
| 116 | + if [ "$SECRET_FS_PERSISTENT" == false ]; then |
| 117 | + local password=$(crypt-tool rand) |
| 118 | + if ! test_valid_hex_data "password"; then |
| 119 | + return 1 |
| 120 | + fi |
| 121 | + else |
| 122 | + if get_master_secret; then |
| 123 | + local password=$MASTER_SECRET |
| 124 | + echo "$MASTER_SECRET" > $SECRET_FS_MOUNT_POINT/master_secret.txt |
| 125 | + else |
| 126 | + echo "Couldn't get master secret: $G_ERROR" |
| 127 | + exit 1 |
| 128 | + fi |
| 129 | + fi |
| 130 | + |
| 131 | + mount_secret_fs $password $SECRET_FS_DEVICE |
| 132 | + safe_remove_outdated |
| 133 | + attest-tool report > $SECRET_FS_MOUNT_POINT/self_report.txt |
| 134 | +} |
| 135 | + |
| 136 | +safe_remove_outdated() { |
| 137 | + rm -f $PATH_ATTESTATION_GPU_1 |
| 138 | + rm -f $PATH_ATTESTATION_GPU_2 |
| 139 | + rm -f $PATH_ATTESTATION_TDX |
| 140 | +} |
| 141 | + |
| 142 | +# Get the master secret from kms contract, based on our attestation |
| 143 | +get_master_secret() { |
| 144 | + # Query kms contract |
| 145 | + echo "Querying KMS..." |
| 146 | + |
| 147 | + local kms_res=$(kms-query get_secret_key $KMS_SERVICE_ID $QUOTE $COLLATERAL) |
| 148 | + |
| 149 | + # the result must consist of 2 lines, which are encrypted master secret and the export pubkey respectively. Parse it. |
| 150 | + kms_res=$(echo "$kms_res" | xargs) # strip possible leading and trailing spaces |
| 151 | + |
| 152 | + read encrypted_secret export_pubkey <<< "$kms_res" |
| 153 | + if ! test_valid_hex_data "encrypted_secret"; then |
| 154 | + return 1 |
| 155 | + fi |
| 156 | + |
| 157 | + if ! test_valid_hex_data "export_pubkey"; then |
| 158 | + return 1 |
| 159 | + fi |
| 160 | + |
| 161 | + # finally decrypt the result |
| 162 | + MASTER_SECRET=$(crypt-tool decrypt -s $SEED -d $encrypted_secret -p $export_pubkey) |
| 163 | + if ! test_valid_hex_data "master_secret"; then |
| 164 | + return 1 |
| 165 | + fi |
| 166 | + |
| 167 | + return 0 |
| 168 | +} |
| 169 | + |
| 170 | +mount_secret_fs() { |
| 171 | + local fs_passwd="$1" |
| 172 | + local fs_container_path="$2" |
| 173 | + |
| 174 | + echo "Opening existing encrypted file system..." |
| 175 | + if ! (echo -n $fs_passwd | cryptsetup luksOpen $fs_container_path encrypted_volume); then |
| 176 | + echo "Creating encrypted file system..." |
| 177 | + echo -n $fs_passwd | cryptsetup luksFormat --pbkdf pbkdf2 $fs_container_path |
| 178 | + echo -n $fs_passwd | cryptsetup luksOpen $fs_container_path encrypted_volume |
| 179 | + mkfs.ext4 /dev/mapper/encrypted_volume |
| 180 | + fi |
| 181 | + |
| 182 | + echo "Mounting encrypted file system..." |
| 183 | + mkdir -p $SECRET_FS_MOUNT_POINT |
| 184 | + mount /dev/mapper/encrypted_volume $SECRET_FS_MOUNT_POINT |
| 185 | +} |
| 186 | + |
| 187 | +finalize() { |
| 188 | + local ssl_cert_path="$1" |
| 189 | + |
| 190 | + echo "Fetching fingerptint from SSL certificate..." |
| 191 | + local ssl_fingerprint=$(openssl x509 -in $ssl_cert_path -noout -fingerprint -sha256 | awk -F= '{gsub(":", "", $2); print $2}') |
| 192 | + |
| 193 | + if ! test_valid_hex_data "ssl_fingerprint"; then |
| 194 | + return 1 |
| 195 | + fi |
| 196 | + |
| 197 | + safe_remove_outdated |
| 198 | + |
| 199 | + echo "SSL certificate fingerprint: $ssl_fingerprint" |
| 200 | + local report_data="${ssl_fingerprint}" |
| 201 | + |
| 202 | + if [ -n "$GPU_MODE" ]; then |
| 203 | + # get random 32 bytes |
| 204 | + local gpu_nonce=$(crypt-tool rand) |
| 205 | + if ! test_valid_hex_data "gpu_nonce"; then |
| 206 | + return 1 |
| 207 | + fi |
| 208 | + |
| 209 | + gpu-attest secret-vm $gpu_nonce $PATH_ATTESTATION_GPU_1 $PATH_ATTESTATION_GPU_2 |
| 210 | + |
| 211 | + if [ ! -e $PATH_ATTESTATION_GPU_1 ] || [ ! -e $PATH_ATTESTATION_GPU_2 ]; then |
| 212 | + echo "GPU attestation not created" |
| 213 | + return 1 |
| 214 | + fi |
| 215 | + echo "GPU attestation nonce: $gpu_nonce" |
| 216 | + report_data="${report_data}${gpu_nonce}" |
| 217 | + fi |
| 218 | + |
| 219 | + if [ ${#report_data} -gt 128 ]; then |
| 220 | + G_ERROR=$(echo "reportdata length: ${#report_data}") |
| 221 | + return 1 |
| 222 | + fi |
| 223 | + |
| 224 | + local quote=$(attest-tool attest $report_data) |
| 225 | + if ! test_valid_hex_data "quote"; then |
| 226 | + return 1 |
| 227 | + fi |
| 228 | + |
| 229 | + echo $quote > $PATH_ATTESTATION_TDX |
| 230 | + echo "TDX attestation done" |
| 231 | + |
| 232 | + return 0 |
| 233 | +} |
| 234 | + |
| 235 | +# ================================ |
| 236 | +# MAIN |
| 237 | +# ================================ |
| 238 | + |
| 239 | +# the order is crucial |
| 240 | +setup_network |
| 241 | +test -n "$GPU_MODE" && setup_gpu |
| 242 | +setup_env |
| 243 | +setup_secret_fs |
| 244 | +setup_docker |
| 245 | + |
| 246 | +if [ ! -e $CERT_PATH ]; then |
| 247 | + echo "SSL certificate not ready yet. Attempting to generate..." |
| 248 | + generate_cert $CERT_NAME $CERT_DIR $DOMAIN_NAME $DOMAIN_EMAIL |
| 249 | +fi |
| 250 | + |
| 251 | +finalize $CERT_PATH |
0 commit comments