Use UEFI Secure Boot and TPM on Ubuntu-based EC2 instances

UEFI Secure Boot is a security feature specified in UEFI, which verifies the state of the boot chain. With UEFI Secure Boot enabled, after firmware self-initialisation only cryptographically verified UEFI binaries are allowed to be executed. This prevents any unauthorised modification of the instance boot flow.

Trusted Platform Module (TPM) is a virtual device provided by the AWS Nitro System. It securely stores artifacts (such as passwords, certificates, or encryption keys) that are used to authenticate the instance. Check the AWS NitroTPM documentation for more details.

Although UEFI Secure Boot is supported by both Ubuntu images and EC2, it is not enabled in the EC2 AMIs. So to use UEFI Secure Boot (along with TPM), a couple of configuration steps are needed that create and register a new AMI based on an existing Ubuntu AMI:

  1. Download a prebuilt UEFI Secure Boot variable store

  2. Get an Ubuntu AMI ID to be used as the base image

  3. Register a new AMI configured with - UEFI boot mode, the downloaded UEFI Secure Boot variable store and support for a virtual TPM

To follow these steps, you’ll need aws-cli and jq:

sudo snap install aws-cli
sudo apt install jq

Download a prebuilt UEFI Secure Boot variable store

The variable store is prebuilt and can be downloaded using:

wget https://github.com/canonical/aws-secureboot-blob/releases/latest/download/blob.bin

For information on how this binary blob gets created, you can refer to its source code and read the relevant AWS documentation.

Get an Ubuntu AMI ID

The SSM parameter store can be used to get e.g. the latest 22.04 LTS AMI ID:

AMI=$(aws ssm get-parameters \
    --names /aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id \
    --query 'Parameters[0].Value' --output text)

If you want to use a different Ubuntu image, refer to Find Ubuntu images on AWS. Once you have the AMI ID, use it to get the image name and its snapshot ID. These will be needed during the registration of a new AMI.

AMI_NAME=$(aws ec2 describe-images \
    --image-id "${AMI}" \
    | jq -r '.Images[0].Name')
AMI_SNAPSHOT=$(aws ec2 describe-images  \
    --image-id "${AMI}" \
    | jq -r '.Images[0].BlockDeviceMappings[0].Ebs.SnapshotId')

The ${AMI} variable should now contain a valid AMI ID (e.g. ami-0373268fb2dac8b4b), ${AMI_NAME} should have something like ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20230711 and ${AMI_SNAPSHOT} should have a valid Snapshot ID (e.g. snap-095ef5e2836b5a9e3).

Register a new AMI

To register a new AMI, first create a copy of the chosen AMI’s snapshot:

REGION=$(aws configure get region)
AMI_SNAPSHOT_NEW=$(aws ec2 copy-snapshot \
    --source-region "${REGION}" \
    --source-snapshot-id "${AMI_SNAPSHOT}" \
    | jq -r '.SnapshotId')
aws ec2 wait snapshot-completed --snapshot-ids "${AMI_SNAPSHOT_NEW}"

Now register a new AMI with the boot mode set to uefi, TPM support enabled, and the downloaded UEFI variable store attached:

AMI_NEW=$(aws ec2 register-image \
    --name "${AMI_NAME}-secureboot" \
    --uefi-data "$(cat blob.bin)" \
    --block-device-mappings "DeviceName=/dev/sda1,Ebs= {SnapshotId=""${AMI_SNAPSHOT_NEW}"",DeleteOnTermination=true}" \
    --architecture x86_64 \
    --root-device-name /dev/sda1 \
    --virtualization-type hvm \
    --ena-support \
    --boot-mode uefi \
    --tpm-support v2.0 \
    | jq -r '.ImageId')
aws ec2 wait image-available --image-ids "${AMI_NEW}"

$AMI_NEW now contains the new AMI ID. This new registered image can now be booted with UEFI Secure Boot.

Verify secure boot mode

Note

Let’s start a new instance with the newly created image ($AMI_NEW) and verify two things - (1) Secure Boot is enabled and (2) A TPM device is present. To do this, first set the KEY_NAME variable to your keypair name. This allows you to log in to the instance over ssh.

KEY_NAME=my-uploaded-keypair-name

Next start an instance:

INSTANCE=$(aws ec2 run-instances --image-id "${AMI_NEW}" --instance-type t3.medium --key-name "${KEY_NAME}"|jq -r '.Instances[].InstanceId')
INSTANCE_IP=$(aws ec2 describe-instances --instance-ids "${INSTANCE}"|jq -r '.Reservations[].Instances[].PublicIpAddress')

Now login and check the Secure Boot status:

ssh ubuntu@${INSTANCE_IP} mokutil --sb-state

... which should output:

SecureBoot enabled

Finally check that the TPM device is available:

ssh ubuntu@${INSTANCE_IP} ls -al /dev/tpm*

... which should output something like:

crw-rw---- 1 tss root  10,   224 Jul 18 10:53 /dev/tpm0
crw-rw---- 1 tss tss  253, 65536 Jul 18 10:53 /dev/tpmrm0