UEFIブートのx86_64 Ubuntu AMIを作成する

x86_64のEC2インスタンスは通常BIOSを利用しますが、[UEFIも利用できます](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-boot.html)。ところがAWSはx86_64向けにUEFIベースのAMIを提供しておらず、ディストリビューションが提供する公式AMIもBIOSベースです。Ubuntu, Debian, CentOSなどいずれもUEFIイメージを提供しておらず、おそらく公式にUEFIベースで提供しているディストリビューションは存在しない模様です。

UEFIイメージを使うメリットはAWS外にあるUEFI VMからの移行が楽な点、SecureBootが利用できる点、TPMが利用できる点です。
現状SecureBoot対応のAMIが公開されていないため、SecureBootに対応させるには手間がかかります。現時点ではUEFIを使っていたサーバからの移行支援機能と扱われているようです。

先述の通りUEFIモードのAMIは非常にレアなので、x86_64でUEFIを使うには自分でAMIを作成する必要があります。
普通AMIを作成するときはAWS CLIのcreate-imageコマンドを利用しますが、create-imageコマンドは作成元イメージの起動モードを引き継ぎます。つまりBIOSモードのイメージからcreate-imageを実行すると、引き続きBIOSモードのイメージになります。

UEFIモードのイメージを作るには、register-imageでEBSスナップショットからAMIに変換します。この方法によるイメージ作成はPackerのEBS Surrogateビルダーなどを使いますが、OSイメージを一から作るのは手間がかかるので、Ubuntu公式AMIのEBSスナップショットを流用する方法を紹介します。

Ubuntu 22.04公式AMIはUEFI用の設定がすでに入っているため、モードを切り替えるだけで動作しますが、必ずしもすべてのAMIでできるわけではないので注意してください。例えばUbuntu20.04の公式イメージはモードを切り替えると起動しなくなります。
# Ubuntu AMIのEBSスナップショットIDを調べる
[Amazon EC2 AMI Locator](https://cloud-images.ubuntu.com/locator/ec2/)からベースにしたいUbuntu AMIのAMI-IDを探します。ここでは東京リージョンのUbuntu 22.04イメージ（ami-081ce1b631be2b337）を利用します。なお、AMIは随時更新されるので、現在は異なるami-idになっている可能性があります。
(![ap-northeast-1 Ubuntu 22.04 x86_64 Image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19807/ee24e7d5-30c9-e901-65b3-952bf02af365.png)
利用したいAMIにdescribe-imagesを実行し、パラメータを確認します。
```
$ aws ec2 describe-images --image-ids ami-081ce1b631be2b337
{
    "Images": [
        {
            "Architecture": "x86_64",
            "CreationDate": "2022-04-21T14:55:23.000Z",
            "ImageId": "ami-081ce1b631be2b337",
            "ImageLocation": "099720109477/ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20220420",
            "ImageType": "machine",
            "Public": true,
            "OwnerId": "099720109477",
            "PlatformDetails": "Linux/UNIX",
            "UsageOperation": "RunInstances",
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/sda1",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "SnapshotId": "snap-056cc90ae7df4ae18",
                        "VolumeSize": 8,
                        "VolumeType": "gp2",
                        "Encrypted": false
                    }
                },
                {
                    "DeviceName": "/dev/sdc",
                    "VirtualName": "ephemeral1"
                },
                {
                    "DeviceName": "/dev/sdb",
                    "VirtualName": "ephemeral0"
                }
            ],
            "Description": "Canonical, Ubuntu, 22.04 LTS, amd64 jammy image build on 2022-04-20",
            "EnaSupport": true,
            "Hypervisor": "xen",
            "Name": "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20220420",
            "RootDeviceName": "/dev/sda1",
            "RootDeviceType": "ebs",
            "SriovNetSupport": "simple",
            "VirtualizationType": "hvm",
            "DeprecationTime": "2024-04-21T14:55:23.000Z"
        }
    ]
}
```
実行すると、このようにBlockDeviceMappingsから/dev/sda1がEBSスナップショット（snap-056cc90ae7df4ae18）から生成されることがわかります。

/dev/sdb, /dev/sdcはインスタンスストアの設定です。

# スナップショットのコピー
このスナップショットをベースにregister-imageすれば、UEFI AMIが作成できます。ただし、他アカウントが保有するスナップショットからAMIを作ることはできないので、まずcopy-snapshotで自分のアカウントにコピーします。
```
$ aws ec2 copy-snapshot --source-snapshot-id snap-056cc90ae7df4ae18 --source-region ap-northeast-1 --destination-region ap-northeast-1
{
    "SnapshotId": "snap-xxx"
}
```

# AMIの作成
スナップショットをコピーしたら、register-imageでこのスナップショットからAMIを作成します。architectureでx86_64を指定するのを忘れないようにしましょう。architecureのデフォルト値はi386のため、起動できないイメージになってしまいます。

追記：NitroTPMがリリースされました。AMIビルド時のパラメータに--tpm-support v2.0を追加するとTPMが有効化されたAMIが作成されます。
```
$ aws ec2 register-image --name jammy-uefi-image --root-device-name /dev/sda1 --block-device-mappings DeviceName=/dev/sda1,Ebs={SnapshotId=snap-xxx} DeviceName=/dev/sdb,VirtualName=ephemeral0 DeviceName=/dev/sdc,VirtualName=ephemeral1 --ena-support --sriov-net-support simple --virtualization-type hvm --boot-mode uefi --architecture x86_64
{
    "ImageId": "ami-xxx"
}
```

作成したイメージにdescribe-imagesを実行してみると、確かにuefiモードになっていることが確認できます。
```
$ aws ec2 describe-images --image-ids ami-xxx
{
    "Images": [
        {
            "Architecture": "x86_64",
            "CreationDate": "2022-05-08T04:08:51.000Z",
            "ImageId": "ami-xxx",
            "ImageLocation": "xxx/jammy-uefi-image",
            "ImageType": "machine",
            "Public": false,
            "OwnerId": "xxx",
            "PlatformDetails": "Linux/UNIX",
            "UsageOperation": "RunInstances",
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/sda1",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "SnapshotId": "snap-xxx",
                        "VolumeSize": 8,
                        "VolumeType": "gp2",
                        "Encrypted": false
                    }
                },
                {
                    "DeviceName": "/dev/sdb",
                    "VirtualName": "ephemeral0"
                },
                {
                    "DeviceName": "/dev/sdc",
                    "VirtualName": "ephemeral1"
                }
            ],
            "EnaSupport": true,
            "Hypervisor": "xen",
            "Name": "jammy-uefi-image",
            "RootDeviceName": "/dev/sda1",
            "RootDeviceType": "ebs",
            "SriovNetSupport": "simple",
            "VirtualizationType": "hvm",
            "BootMode": "uefi"
        }
    ]
}
```
実際にこのイメージでEC2を起動してみますと、UEFI関連のパラメータが取得できます。
```
$ sudo /usr/bin/efibootmgr
BootCurrent: 0001
Timeout: 0 seconds
BootOrder: 0002,0000,0001
Boot0000* UiApp
Boot0001* UEFI Amazon Elastic Block Store volxxx 1
Boot0002* ubuntu
```

```
$ efivar -l
605dab50-e046-4300-abb6-3dd810dd8b23-SbatLevelRT
605dab50-e046-4300-abb6-3dd810dd8b23-MokListXRT
605dab50-e046-4300-abb6-3dd810dd8b23-MokListRT
8be4df61-93ca-11d2-aa0d-00e098032b8c-BootOrder
8be4df61-93ca-11d2-aa0d-00e098032b8c-Boot0002
8be4df61-93ca-11d2-aa0d-00e098032b8c-BootCurrent
8be4df61-93ca-11d2-aa0d-00e098032b8c-Boot0001
8be4df61-93ca-11d2-aa0d-00e098032b8c-ErrOutDev
8be4df61-93ca-11d2-aa0d-00e098032b8c-ConInDev
8be4df61-93ca-11d2-aa0d-00e098032b8c-Key0001
8be4df61-93ca-11d2-aa0d-00e098032b8c-Key0000
8be4df61-93ca-11d2-aa0d-00e098032b8c-ConOutDev
8be4df61-93ca-11d2-aa0d-00e098032b8c-ErrOut
8be4df61-93ca-11d2-aa0d-00e098032b8c-ConOut
8be4df61-93ca-11d2-aa0d-00e098032b8c-ConIn
7c436110-ab2a-4bbb-a880-fe41995c9f82-csr-active-config
7c436110-ab2a-4bbb-a880-fe41995c9f82-boot-args
4d1ede05-38c7-4a6a-9cc6-4bcca8b38c14-FirmwareFeaturesMask
4d1ede05-38c7-4a6a-9cc6-4bcca8b38c14-FirmwareFeatures
4d1ede05-38c7-4a6a-9cc6-4bcca8b38c14-BackgroundClear
8be4df61-93ca-11d2-aa0d-00e098032b8c-PlatformRecovery0000
8be4df61-93ca-11d2-aa0d-00e098032b8c-PlatformLang
8be4df61-93ca-11d2-aa0d-00e098032b8c-Lang
8be4df61-93ca-11d2-aa0d-00e098032b8c-PlatformLangCodes
8be4df61-93ca-11d2-aa0d-00e098032b8c-LangCodes
8be4df61-93ca-11d2-aa0d-00e098032b8c-BootOptionSupport
8be4df61-93ca-11d2-aa0d-00e098032b8c-Timeout
8be4df61-93ca-11d2-aa0d-00e098032b8c-OsIndicationsSupported
8be4df61-93ca-11d2-aa0d-00e098032b8c-Boot0000
eb704011-1402-11d3-8e77-00a0c969723b-MTC
8be4df61-93ca-11d2-aa0d-00e098032b8c-X-Nitro-SecurebootStatus
e20939be-32d4-41be-a150-897f85d49829-MemoryOverwriteRequestControl
c076ec0c-7028-4399-a072-71ee5c448b9f-CustomMode
8be4df61-93ca-11d2-aa0d-00e098032b8c-DeployedMode
f0a30bc7-af08-4556-99c4-001009c93a44-SecureBootEnable
8be4df61-93ca-11d2-aa0d-00e098032b8c-SetupMode
8be4df61-93ca-11d2-aa0d-00e098032b8c-SecureBoot
8be4df61-93ca-11d2-aa0d-00e098032b8c-X-Nitro-BootServicesExited
```

X-Nitro-SecurebootStatusという変数もありますが、今のところ中身は0です。NitroTPMがリリースされたタイミングで、SecureBootも利用可能になるものと思われます。
[Deep dive into NitroTPM and UEFI Secure Boot support in Amazon EC2](https://aws.amazon.com/jp/blogs/compute/deep-dive-into-nitrotpm-and-uefi-secure-boot-support-in-amazon-ec2/)

```
$ efivar --print -n 8be4df61-93ca-11d2-aa0d-00e098032b8c-X-Nitro-SecurebootStatus
GUID: 8be4df61-93ca-11d2-aa0d-00e098032b8c
Name: "X-Nitro-SecurebootStatus"
Attributes:
        Boot Service Access
        Runtime Service Access
Value:
00000000  00 00 00 00 00 00 00 00                           |........        |
```

# UEFI未対応のインスタンスタイプ
x86系インスタンスの対応状況は以下の通りです。Nitro世代の仮想インスタンスは基本的に概ね対応します。armインスタンスの記載はありませんが、armインスタンスは必ずUEFIを使います。

表記上わかりにくいのですが、ベアメタルインスタンスはUEFIモードに対応していません。（インスタンスファミリー一覧の頭にVirtualizedとあるのはそのため）
そんなに使わないとは思いますが、ベアメタルインスタンスを利用する可能性があるなら、現時点ではUEFIモードのAMIを避けておくほうがよいでしょう。
[Boot modes](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ami-boot.html#boot-considerations)
> Intel and AMD instance types that support UEFI, in addition to Legacy BIOS:
>    Virtualized: C5, C5a, C5ad, C5d, C5n, D3, D3en, G4, I3en, M5, M5a, M5ad, M5d, M5dn, M5n, M5zn, M6i, R5, R5a, R5ad, R5b, R5d, R5dn, R5n, T3, T3a, and z1d

# NitroTPM未対応のインスタンスタイプ
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nitrotpm-instance.html#launch-instance-with-nitrotpm
記載がありますが、現時点ではUEFI対応していてもNitroTPMに対応しないタイプが複数あります。特にt3シリーズには未対応なので注意してください。
また今のところarmインスタンスでは利用できません。個人的にはエントロピーの少ないGravitonではTPMを乱数生成器にしたかったので残念でした。そのうち実装されると思うので楽しみに待っています。

# SecureBootの注意点
NitroTPMを有効化してもただちにSecureBootが有効になるわけではありません。SecureBoot対応AMIを使うならよいのですが、今のところ対応AMIは提供されていないので、自分でSecureBoot用の鍵を生成しAMIに同梱する必要があります。[ドキュメントはあります](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/create-ami-with-uefi-secure-boot.html)が、かなり面倒で手間がかかります。いずれSecureBoot対応AMIが増えてくると思うので、急いでいなければ待った方がいいかもしれません。

---
# おまけ
UEFI対応AMIに変換するスクリプトを書きました。
https://github.com/bateleurX/generate_uefi_ami