[アップデート] AWS Nitro EnclavesがWindows OSでサポートされました

2021.04.28

しばたです。

先ほどAWSのWhtat's New Blogで「 AWS Nitro EnclavesがWindows OSをサポートした」という記事が流れてきて非常に驚いています。

本記事ではこれが一体どういうことなのかを試しながら解説していきます。

AWS Nitro EnclavesのWindows OSサポートとは?

AWS Nitro Enclavesはre:Invent 2019で発表されたNitro世代のEC2インスタンス内部に構築可能なアプリケーション分離環境です。
AWS Nitro Enclavesの詳細については弊社岩田による以下の記事が非常にわかりやすいのでご一読ください。

で、これまでAWS Nitro EnclavesはLinux OS上でしか構築できなかったのですが今回のサポートによりWindows Server OS内部にも分離環境(Enclave)を構築可能になりました。
これがどういうことかについてはドキュメントにあるこの図をご覧いただくとわかりやすいでしょう。

(Nitro Enclaves Application development on Windows instancesより引用)

今回のサポートではEnclaveの親となるOSにWindows Serverが追加されたということです。
ちなみに作成可能なEnclaveは従来どおり常にLinux Kernelベースであり、Windows Containerの様にWindows KernelのEnclaveが作成できるわけではないのでご注意ください。

システム要件

システム要件としては従来のAWS Nitro Enclavesと同様に

  • Nitro世代のインスタンス (ただしt3,t3a,t4g,a1,c6g,c6gd,m6g,m6gd,r6g,r6gdは除く)
  • Linux OSであること

なことに加えて

  • Windows Server 2012 R2以降であること

が追加されました。

試してみた

それでは早速試してみます。

0. 検証環境

今回は東京リージョンに最新の英語版Windows Server 2019 (ami-0a628b83485f640e7 Windows_Server-2019-English-Full-Base-2021.04.14) をインストールしました。
システム要件にはOSの言語指定はありませんでしたが、「絶対日本語OSでは動かないだろうなぁ...」という確信めいた予感がしたので英語版にしています。(日本語OSは後日試しておきます...)

環境構築手順は割愛しますが、AWS Tools for PowerShellを使いざっくり以下の様に構築しています。

# EC2構築例 (AWS Tools for PowerShell / AWS.Tools.EC2 Ver.4.1.11 使用)
Set-DefaultAWSRegion -Region ap-northeast-1
Set-AWSCredential -ProfileName "<your profile>"

# Create windows instance
$params = @{
    ImageId = 'ami-0a628b83485f640e7' # Windows_Server-2019-English-Full-Base-2021.04.14 
    InstanceType = 'm5.xlarge'        # EnclavesがサポートされるNitro世代インスタンス
    EnclaveOptions_Enabled = $true    # Enclavesを有効に!
    # 後は普通のWindows EC2と同様に
    KeyName = 'EC2用キーペア名'
    MinCount = 1
    MaxCount = 1
    SubnetId = (Get-EC2Subnet -Filter @{Name = 'tag:Name'; Values = 'EC2を配置するサブネット名'}).SubnetId
    SecurityGroupId = (Get-EC2SecurityGroup -Filter @{Name = 'group-name'; Values = 'EC2用のセキュリティグループ名'}).GroupId
    EbsOptimized = $false
    BlockDeviceMapping = @(
        [Amazon.EC2.Model.BlockDeviceMapping]@{
            DeviceName = '/dev/sda1'
            Ebs = @{
                VolumeType = 'gp3'
                VolumeSize = 50
                DeleteOnTermination = $true
            }
        }
    )
    TagSpecification = @{
        ResourceType = 'Instance';
        Tags         = @( @{Key = 'Name'; value = 'enclaves-test-2019'});
    }
}
New-EC2Instance @params

基本的に通常のEC2の構築手順と変わりませんが、大事な点は

  • 適切なOSバージョン
  • 適切なインスタンスタイプ
  • EC2構築時に「Enclaves のサポート」を有効にする (nclaveOptions_Enabled = $true)

であることです。

加えて次項のNitro Enclaves CLIのインストールでAWS Systems Manager(以後SSM)を使うのでEC2インスタンスに適切なIAMロールをアタッチしておいてください。
今回はシンプルに最低限のAmazonSSMManagedInstanceCoreポリシーだけ保持するロールをアタッチしています。

1. Nitro Enclaves CLIのインストール

EC2が起動したらNitro Enclaves CLIをインストールします。
Amazon Linuxではyumからインストールできましたが、Windows Serverの場合、現時点では単独のMSIインストーラーなどは提供されておらずSSMからインストールしなければならない様です。

Windows向けNitro Enclaves CLIはSSM Ditributerで「AWSNitroEnclavesWindows」というパッケージで公開されています。

SSM Run CommandからAWS-ConfigureAWSPackageを以下のパラメーターで実行してやることでNitro Enclaves CLIをインストールできます。

パラメーター 設定値 備考
Action Install
Installation Type Uninstall and reinstall
Name AWSNitroEnclavesWindows

※その他パラメーターは任意

インストールが正常終了すれば完了です。

インストール後Windows Serverにログインし、nitro-cli --versionを打つとNitro Enclaves CLIのバージョンを確認できます。

現在はVer.1.0.0がインストールされます。
また、Nitro Enclaves CLIはC:\ProgramData\Amazon\AwsNitroEnclaves配下にインストールされます。

2. Enclaveのビルド

Linux版のAWS Nitro Enclavesだとnitro-cli build-enclaveコマンドを使ってEnclaveイメージ(.eif)を作るのですが、Windows版CLIではこのコマンドはサポートされていないとの事です。

仕方ないので別途LinuxのEnclaves環境を用意しEnclaveイメージを用意します。
本記事では最初に紹介した岩田の記事に倣いサンプルのhello.eifを作成しC:\Enclavesフォルダに配置しておきました。

なお、hello.eifのベースとなるサンプルのDockerfileはこんな感じで"Hello from the enclave side!"を無限にカウントアップするだけのイメージです。

Dockerfile

# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

FROM busybox

ENV HELLO="Hello from the enclave side!"
COPY hello.sh /bin/hello.sh

CMD ["/bin/hello.sh"]

hello.sh

#!/bin/sh
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

count=1
while true; do
    printf "[%4d] $HELLO\n" $count
    count=$((count+1))
    sleep 5
done

3. Enclaveの起動

nitro-cli run-enclaveコマンドを使いEnclaveを起動します。

# CPUやメモリパラメーターは適当に
nitro-cli run-enclave --cpu-count 2 --memory 512 --eif-path C:\Enclaves\hello.eif --debug-mode

Linuxの場合と同様に無事Enclaveが起動しました。

4. Enclaveの確認

nitro-cli describe-enclavesコマンドを使い起動中のEnclaveを確認します。

nitro-cli describe-enclaves

こちらも良い感じです。

nitro-cli consoleコマンドでコンソールログを確認します。

nitro-cli console --enclave-id "対象EnclaveのEnclaveID"

PS C:\> nitro-cli console --enclave-id i-072d2efe8370872d3-enc1791677ca91bbf6
[    0.000000] Linux version 4.14.177-104.253.amzn2.x86_64 (mockbuild@ip-10-0-1-32) (gcc version 7.3.1 20180712 (Red Hat 7.3.1-6) (GCC)) #1 SMP Fri May 1 02:01:13 UTC 2020
[    0.000000] Command line: reboot=k panic=30 pci=off nomodules console=ttyS0 i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd random.trust_cpu=on virtio_mmio.device=4K@0xd0000000:5 virtio_mmio.device=4K@0xd0001000:6
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x020: 'AVX-512 opmask'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x040: 'AVX-512 Hi256'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x080: 'AVX-512 ZMM_Hi256'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x200: 'Protection Keys User registers'
[    0.000000] x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256
[    0.000000] x86/fpu: xstate_offset[3]:  832, xstate_sizes[3]:   64
[    0.000000] x86/fpu: xstate_offset[4]:  896, xstate_sizes[4]:   64
[    0.000000] x86/fpu: xstate_offset[5]:  960, xstate_sizes[5]:   64
[    0.000000] x86/fpu: xstate_offset[6]: 1024, xstate_sizes[6]:  512
[    0.000000] x86/fpu: xstate_offset[7]: 1536, xstate_sizes[7]: 1024
[    0.000000] x86/fpu: xstate_offset[9]: 2560, xstate_sizes[9]:    8
[    0.000000] x86/fpu: Enabled xstate features 0x2ff, context size is 2568 bytes, using 'compacted' format.
[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000001f7fffff] usable
[    0.000000] NX (Execute Disable) protection: active
[    0.000000] DMI not present or invalid.
[    0.000000] Hypervisor detected: KVM
[    0.000000] tsc: Fast TSC calibration using PIT
[    0.000000] e820: last_pfn = 0x1f800 max_arch_pfn = 0x400000000
[    0.000000] MTRR: Disabled
[    0.000000] x86/PAT: MTRRs disabled, skipping PAT initialization too.
[    0.000000] CPU MTRRs all blank - virtualized system.
[    0.000000] x86/PAT: Configuration [0-7]: WB  WT  UC- UC  WB  WT  UC- UC
[    0.000000] found SMP MP-table at [mem 0x0009fc00-0x0009fc0f]
[    0.000000] Scanning 1 areas for low memory corruption
[    0.000000] Using GB pages for direct mapping
[    0.000000] RAMDISK: [mem 0x02447000-0x0266cfff]
[    0.000000] No NUMA configuration found
[    0.000000] Faking a node at [mem 0x0000000000000000-0x000000001f7fffff]
[    0.000000] NODE_DATA(0) allocated [mem 0x1f7de000-0x1f7fffff]
[    0.000000] kvm-clock: Using msrs 4b564d01 and 4b564d00
[    0.000000] kvm-clock: cpu 0, msr 0:1f7dc001, primary cpu clock
[    0.000000] kvm-clock: using sched offset of 278178994 cycles
[    0.000000] clocksource: kvm-clock: mask: 0xffffffffffffffff max_cycles: 0x1cd42e4dffb, max_idle_ns: 881590591483 ns
[    0.000000] Zone ranges:
[    0.000000]   DMA      [mem 0x0000000000001000-0x0000000000ffffff]
[    0.000000]   DMA32    [mem 0x0000000001000000-0x000000001f7fffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000000001000-0x000000000009efff]
[    0.000000]   node   0: [mem 0x0000000000100000-0x000000001f7fffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000000001000-0x000000001f7fffff]
[    0.000000] Intel MultiProcessor Specification v1.4
[    0.000000] MPTABLE: OEM ID: FC
[    0.000000] MPTABLE: Product ID: 000000000000
[    0.000000] MPTABLE: APIC at: 0xFEE00000
[    0.000000] Processor #0 (Bootup-CPU)
[    0.000000] Processor #1
[    0.000000] IOAPIC[0]: apic_id 3, version 17, address 0xfec00000, GSI 0-23
[    0.000000] Processors: 2
[    0.000000] smpboot: Allowing 2 CPUs, 0 hotplug CPUs
[    0.000000] PM: Registered nosave memory: [mem 0x00000000-0x00000fff]
[    0.000000] PM: Registered nosave memory: [mem 0x0009f000-0x000fffff]
[    0.000000] e820: [mem 0x1f800000-0xffffffff] available for PCI devices
[    0.000000] Booting paravirtualized kernel on KVM
[    0.000000] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645519600211568 ns
[    0.000000] random: get_random_bytes called from start_kernel+0x94/0x486 with crng_init=0
[    0.000000] setup_percpu: NR_CPUS:128 nr_cpumask_bits:128 nr_cpu_ids:2 nr_node_ids:1
[    0.000000] percpu: Embedded 41 pages/cpu s128600 r8192 d31144 u1048576
[    0.000000] KVM setup async PF for cpu 0
[    0.000000] kvm-stealtime: cpu 0, msr 1f415040
[    0.000000] PV qspinlock hash table entries: 256 (order: 0, 4096 bytes)
[    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 126889
[    0.000000] Policy zone: DMA32
[    0.000000] Kernel command line: reboot=k panic=30 pci=off nomodules console=ttyS0 i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd random.trust_cpu=on virtio_mmio.device=4K@0xd0000000:5 virtio_mmio.device=4K@0xd0001000:6
[    0.000000] PID hash table entries: 2048 (order: 2, 16384 bytes)
[    0.000000] Memory: 485460K/515704K available (10252K kernel code, 653K rwdata, 1572K rodata, 1300K init, 2808K bss, 30244K reserved, 0K cma-reserved)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1
[    0.004000] Hierarchical RCU implementation.
[    0.004000]  RCU restricting CPUs from NR_CPUS=128 to nr_cpu_ids=2.
[    0.004000] RCU: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2
[    0.004000] NR_IRQS: 4352, nr_irqs: 56, preallocated irqs: 16
[    0.004000] Console: colour dummy device 80x25
[    0.004000] console [ttyS0] enabled
[    0.004000] tsc: Detected 2500.004 MHz processor
[    0.004000] Calibrating delay loop (skipped) preset value.. 5000.00 BogoMIPS (lpj=10000016)
[    0.004000] pid_max: default: 32768 minimum: 301
[    0.004000] Security Framework initialized
[    0.004000] SELinux:  Initializing.
[    0.004000] Dentry cache hash table entries: 65536 (order: 7, 524288 bytes)
[    0.004000] Inode-cache hash table entries: 32768 (order: 6, 262144 bytes)
[    0.004000] Mount-cache hash table entries: 1024 (order: 1, 8192 bytes)
[    0.004000] Mountpoint-cache hash table entries: 1024 (order: 1, 8192 bytes)
[    0.004451] Last level iTLB entries: 4KB 64, 2MB 8, 4MB 8
[    0.004832] Last level dTLB entries: 4KB 64, 2MB 0, 4MB 0, 1GB 4
[    0.005254] Spectre V1 : Mitigation: usercopy/swapgs barriers and __user pointer sanitization
[    0.005862] Spectre V2 : Mitigation: Enhanced IBRS
[    0.006208] Spectre V2 : Spectre v2 / SpectreRSB mitigation: Filling RSB on context switch
[    0.006802] Spectre V2 : mitigation: Enabling conditional Indirect Branch Prediction Barrier
[    0.007396] Speculative Store Bypass: Mitigation: Speculative Store Bypass disabled via prctl and seccomp
[    0.008248] Freeing SMP alternatives memory: 32K
[    0.010469] smpboot: Max logical packages: 2
[    0.010942] x2apic enabled
[    0.011318] Switched APIC routing to physical x2apic.
[    0.012812] ..TIMER: vector=0x30 apic1=0 pin1=0 apic2=-1 pin2=-1
[    0.013273] smpboot: CPU0: Intel(R) Xeon(R) Processor @ 2.50GHz (family: 0x6, model: 0x55, stepping: 0x7)
[    0.014053] Performance Events: unsupported p6 CPU model 85 no PMU driver, software events only.
[    0.014744] Hierarchical SRCU implementation.
[    0.015507] smp: Bringing up secondary CPUs ...
[    0.015990] x86: Booting SMP configuration:
[    0.016000] .... node  #0, CPUs:      #1
[    0.004000] kvm-clock: cpu 1, msr 0:1f7dc041, secondary cpu clock
[    0.016136] KVM setup async PF for cpu 1
[    0.016731] kvm-stealtime: cpu 1, msr 1f515040
[    0.017198] smp: Brought up 1 node, 2 CPUs
[    0.017198] smpboot: Total of 2 processors activated (10000.01 BogoMIPS)
[    0.017204] devtmpfs: initialized
[    0.017204] x86/mm: Memory block size: 128MB
[    0.017204] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.020092] futex hash table entries: 512 (order: 3, 32768 bytes)
[    0.020262] NET: Registered protocol family 16
[    0.020778] cpuidle: using governor ladder
[    0.020778] cpuidle: using governor menu
[    0.024158] HugeTLB registered 1.00 GiB page size, pre-allocated 0 pages
[    0.024818] HugeTLB registered 2.00 MiB page size, pre-allocated 0 pages
[    0.026345] SCSI subsystem initialized
[    0.026345] pps_core: LinuxPPS API ver. 1 registered
[    0.026345] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
[    0.026345] PTP clock support registered
[    0.028016] dmi: Firmware registration failed.
[    0.028477] NetLabel: Initializing
[    0.028730] NetLabel:  domain hash size = 128
[    0.029044] NetLabel:  protocols = UNLABELED CIPSOv4 CALIPSO
[    0.029465] NetLabel:  unlabeled traffic allowed by default
[    0.029912] clocksource: Switched to clocksource kvm-clock
[    0.030332] VFS: Disk quotas dquot_6.6.0
[    0.030630] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
[    0.031319] NET: Registered protocol family 2
[    0.031789] TCP established hash table entries: 4096 (order: 3, 32768 bytes)
[    0.032351] TCP bind hash table entries: 4096 (order: 4, 65536 bytes)
[    0.032813] TCP: Hash tables configured (established 4096 bind 4096)
[    0.033318] UDP hash table entries: 256 (order: 1, 8192 bytes)
[    0.033738] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes)
[    0.034226] NET: Registered protocol family 1
[    0.034636] RPC: Registered named UNIX socket transport module.
[    0.035066] RPC: Registered udp transport module.
[    0.035405] RPC: Registered tcp transport module.
[    0.035745] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.036275] Unpacking initramfs...
[    0.040853] Freeing initrd memory: 2200K
[    0.041185] virtio-mmio: Registering device virtio-mmio.0 at 0xd0000000-0xd0000fff, IRQ 5.
[    0.041797] virtio-mmio: Registering device virtio-mmio.1 at 0xd0001000-0xd0001fff, IRQ 6.
[    0.042437] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x24093d6e846, max_idle_ns: 440795249997 ns
[    0.043167] platform rtc_cmos: registered platform RTC device (no PNP device found)
[    0.043893] Scanning for low memory corruption every 60 seconds
[    0.044598] audit: initializing netlink subsys (disabled)
[    0.045178] Initialise system trusted keyrings
[    0.045521] Key type blacklist registered
[    0.045825] audit: type=2000 audit(1619579620.749:1): state=initialized audit_enabled=0 res=1
[    0.046455] workingset: timestamp_bits=36 max_order=17 bucket_order=0
[    0.048149] zbud: loaded
[    0.048791] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[    0.049341] NFS: Registering the id_resolver key type
[    0.049712] Key type id_resolver registered
[    0.050010] Key type id_legacy registered
[    0.050300] nfs4filelayout_init: NFSv4 File Layout Driver Registering...
[    0.052357] Key type asymmetric registered
[    0.052668] Asymmetric key parser 'x509' registered
[    0.053036] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 252)
[    0.053601] io scheduler noop registered (default)
[    0.053978] io scheduler cfq registered
[    0.054335] virtio-mmio virtio-mmio.0: Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might not work.
[    0.055167] virtio-mmio virtio-mmio.1: Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might not work.
[    0.056051] Serial: 8250/16550 driver, 1 ports, IRQ sharing disabled
[    0.077710] serial8250: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a U6_16550A
[    0.079829] loop: module loaded
[    0.080118] Loading iSCSI transport class v2.0-870.
[    0.080587] iscsi: registered transport (tcp)
[    0.080914] tun: Universal TUN/TAP device driver, 1.6
[    0.081373] hidraw: raw HID events driver (C) Jiri Kosina
[    0.081835] nf_conntrack version 0.5.0 (4096 buckets, 16384 max)
[    0.082451] ip_tables: (C) 2000-2006 Netfilter Core Team
[    0.083031] Initializing XFRM netlink socket
[    0.083513] NET: Registered protocol family 10
[    0.084374] Segment Routing with IPv6
[    0.084661] NET: Registered protocol family 17
[    0.084999] Bridge firewalling registered
[    0.085337] Key type dns_resolver registered
[    0.085733] NET: Registered protocol family 40
[    0.086587] sched_clock: Marking stable (85700938, 0)->(141387982, -55687044)
[    0.087316] registered taskstats version 1
[    0.087622] Loading compiled-in X.509 certificates
[    0.088689] Loaded X.509 cert 'Build time autogenerated kernel key: c582bb2a1fdabdd195bbc9c8840fff8158852907'
[    0.089411] zswap: loaded using pool lzo/zbud
[    0.089948] Key type encrypted registered
[    0.091372] Freeing unused kernel memory: 1300K
[    0.108111] Write protecting the kernel read-only data: 14336k
[    0.110022] Freeing unused kernel memory: 2016K
[    0.111291] Freeing unused kernel memory: 476K
[    0.111917] nsm: loading out-of-tree module taints kernel.
[    0.112362] nsm: module verification failed: signature and/or required key missing - tainting kernel
[   1] Hello from the enclave side!
[   2] Hello from the enclave side!
[   3] Hello from the enclave side!
[   4] Hello from the enclave side!
[   5] Hello from the enclave side!
[   6] Hello from the enclave side!
[   7] Hello from the enclave side!
[   8] Hello from the enclave side!
[   9] Hello from the enclave side!
[  10] Hello from the enclave side!

# ・・・後略・・・

EnclaveのOS情報がLinux version 4.14.177-104.253.amzn2.x86_64であることと後半にちゃんと「Hello from the enclave side!」のメッセージがカウントアップされスクリプトが動作していることがわかります。

5. Enclaveの終了

最後にnitro-cli terminate-enclaveコマンドでEnclaveを終了します。

nitro-cli terminate-enclave --enclave-id "対象EnclaveのEnclaveID"

これで完了です。

最後に

以上となります。

簡単なサンプルではありますがWindows環境で無事Enclaveを実行できました。
現状Enclaveイメージ(.eif)はDockerイメージから作る仕組みになっているためEnclaveのビルドは難しいのでしょうが、これもいずれサポートされ全ての手順がWindows環境で完結できると嬉しい感じですね。