AWS Nitro Enclavesで独自デーモンACMを適用できるか(Apache)

サービス概要

AWS Nitro Enclavesとは、EC2インスタンスから、エンクレーブ(飛び地、明確に他と区別される集団)と呼ばれる分離・独立・強化され、高度に制約された仮想マシン実行環境を作成できるEC2の機能です。親インスタンスとの安全なローカルソケット接続のみを提供し、外部ネットワークはないため、ユーザーはエンクレーブにSSHで接続できず、エンクレーブ内のデータやアプリケーションには、親インスタンスのプロセス、アプリケーション、ユーザーがアクセスできません。これにより、PIIなどの最も機密性の高いデータや、データ処理アプリケーションを保護できます。
(https://docs.aws.amazon.com/images/enclaves/latest/user/images/enclave-overview.pngより)

AWS Certificate Manager(ACM) for Nitro Enclavesは、Nitro Enclavesを使用したEC2で実行されているWebアプリケーション・ Webサーバーで、パブリック/プライベートSSL/TLS 証明書を使用できます。
EC2 インスタンスでHTTPSサーバーを実行する場合、SSL証明書を作成し、インスタンスにプレーンテキストとして保存していました。ACM for Nitro Enclavesを使用すると、ACM証明書をエンクレーブにバインドし、証明書をプレーンテキスト形式で親インスタンスとそのユーザーに公開することなく、それらの証明書をWebサーバーで直接使用できます。

目的・やりたいこと

2024年7月現在、ACM for Nitro EnclavesはNginxまたはApacheで動作します。
ところが、顧客からの要望では「NginxもApacheも使わず、独自デーモンプログラムを使う」とのこと。果たしてこの独自デーモンでACM for Nitro Enclavesが機能するかわかりません。さらに、独自デーモンの仕様もわかりません。
そこで、以下の手順でApacheとNginxを仮称独自デーモンとして動作するか検証していきます。

  1. ApacheとNginxをポート番号変えてインストールしておく
  2. Apache側にACM for Nitro Enclaves設定する
  3. 自動で証明書ファイルが作られる??(動作確認)
  4. Nginx側から作られた証明書を指定してSSL化できるかどうか

対象となる技術

参考URL

条件(Nitro Enclavesの要件)

https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html#nitro-enclave-reqs

考慮事項

  • 親インスタンスごとに最大 4 つの個別のエンクレーブを作成できる
  • エンクレーブは親インスタンスとのみ通信できる(同じまたは異なる親インスタンスで実行されているエンクレーブは相互に通信できない)
  • エンクレーブは、親インスタンスがrunning状態にある間のみアクティブになる(親インスタンスが停止・終了すると、そのエンクレーブも終了する)
  • 同じインスタンスで休止状態とエンクレーブを有効にすることはできない
  • Nitro Enclaves は Outposts、ローカルゾーンまたは Wavelength ゾーンではサポートされていない
  • ACM for Nitro Enclavesは RSA 証明書のみをサポート
  • ACM for Nitro Enclavesは Linux インスタンスでのみ利用可能(2024å¹´7月現在、Windows インスタンスではサポートされていない)
  • ACM for Nitro Enclavesは2024å¹´7月現在、アジア太平洋 (大阪) およびアジア太平洋 (ジャカルタ) ではサポートされていない

注意事項

https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave-refapp.html

現在、Nitro EnclavesのACMはNGINXサーバー、およびApache HTTPサーバーで動作します。**追加の Web サーバーのサポートは、今後追加される予定です。**
※ACM for Nitro Enclaves は、親インスタンスとエンクレーブ間の標準化された PKCS11 暗号化インターフェースを使用します。PKCS11 プロトコルをサポートするアプリケーションであれば、証明書とキーを保護するために ACM for Nitro Enclaves を使用するように適応できます。

作業の流れ

事前作業

1.EC2インスタンスの起動
インスタンスタイプ:m5.xlarge(4vCPU、16GB)
OS:Amazon Linux 2023(無料利用枠)

[▼高度な詳細]を展開し、下の方にあるNitro Enclaveを有効化

ちなみにm5.largeなど2vCPUのインスタンスタイプを選択すると、以下のような警告が出ます。
「You cannot enable Nitro Enclaves for "m5.large" instance types. Specify a supported instance type and try again.」

2.aws-nitro-enclaves-cliのインストール

# yum install aws-nitro-enclaves-cli -y
# yum install aws-nitro-enclaves-cli-devel -y
# nitro-cli --version
Nitro CLI 1.3.1

3.nitro-enclaves-allocatorの起動&自動起動を有効化

# systemctl start nitro-enclaves-allocator.service && systemctl enable nitro-enclaves-allocator.service
Created symlink /etc/systemd/system/multi-user.target.wants/nitro-enclaves-allocator.service → /usr/lib/systemd/system/nitro-enclaves-allocator.service.

# more /etc/nitro_enclaves/allocator.yaml
---
# Enclave configuration file.
#
# How much memory to allocate for enclaves (in MiB).
memory_mib: 512
#
# How many CPUs to reserve for enclaves.
cpu_count: 2

4.Dockerサービスの起動&自動起動を有効化

# systemctl start docker && systemctl enable docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.

5.Dockerサンプルファイル(/usr/share/nitro_enclaves/examples/hello)のビルド

# docker build /usr/share/nitro_enclaves/examples/hello -t hello
[+] Building 2.3s (7/7) FINISHED                                                                                                              docker:default
 => [internal] load build definition from Dockerfile                                                                                                    0.0s
 => => transferring dockerfile: 307B                                                                                                                    0.0s
 => [internal] load metadata for docker.io/library/busybox:latest                                                                                       1.8s
 => [internal] load .dockerignore                                                                                                                       0.0s
 => => transferring context: 2B                                                                                                                         0.0s
 => [internal] load build context                                                                                                                       0.0s
 => => transferring context: 307B                                                                                                                       0.0s
 => [1/2] FROM 
 ・・・
 => [2/2] COPY hello.sh /bin/hello.sh                                                                                                                   0.0s
 => exporting to image                                                                                                                                  0.0s
 => => exporting layers                                                                                                                                 0.0s
 => => writing image sha256:****                                                                                                                   0.0s
 => => naming to docker.io/library/hello
 
# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
hello        latest    0d44f8558cf6   2 minutes ago   4.26MB

6.DocerkイメージをNitro Enclavesイメージに変換

# nitro-cli build-enclave --docker-uri hello:latest --output-file sample.eif
Start building the Enclave Image...
Using the locally available Docker image...
Enclave Image successfully created.
{
  "Measurements": {
    "HashAlgorithm": "Sha384 { ... }",
    "PCR0": "4***f",
    "PCR1": "4***3",
    "PCR2": "6***2"
  }

7.Nitro Enclavesの起動
例えば、次のコマンドは、同じディレクトリにあるsample.eifというNitro Enclaveイメージファイルを使用して、2vCPU、512MBのNitro Enclaveを作成します。

# nitro-cli run-enclave --cpu-count 2 --memory 512 --eif-path sample.eif
Start allocating memory...
Started enclave with enclave-cid: 16, memory: 512 MiB, cpu-ids: [1, 3]
{
  "EnclaveName": "sample",
  "EnclaveID": "i-0****7",
  "ProcessID": 28484,
  "EnclaveCID": 16,
  "NumberOfCPUs": 2,
  "CPUIDs": [
    1,
    3
  ],
  "MemoryMiB": 512
}

8.Nitro Enclaves起動確認

# nitro-cli describe-enclaves
[
  {
    "EnclaveName": "sample",
    "EnclaveID": "i-0****7",
    "ProcessID": 28484,
    "EnclaveCID": 16,
    "NumberOfCPUs": 2,
    "CPUIDs": [
      1,
      3
    ],
    "MemoryMiB": 512,
    "State": "RUNNING",
    "Flags": "NONE",
    "Measurements": {
      "HashAlgorithm": "Sha384 { ... }",
      "PCR0": "4****f",
      "PCR1": "4****3",
      "PCR2": "6****2"
    }
  }
]

"State"が"RUNNING"になってますね。

手順

さて、ようやくNitro Enclavesインスタンスが起動できたので、次はいよいよACM証明書を設定します。

1.ACM証明書の発行
事前にお名前.comでnozaki2.comドメインを取得し、ネームサーバーにRoute 53のnozaki2.comゾーンのNSレコードを登録しておきます。
ACMでnozaki2.comのパブリック証明書を発行し、証明書ARN:arn:aws:acm:ap-northeast-1:************:certificate/c60a1383-e409-42ad-83ad-70044654c8c3をメモしておきます。

2.Apacheのインストール

# yum -y install httpd mod_ssl

3.ACM for Nitro Enclavesのインストール

# yum install aws-nitro-enclaves-acm -y

4.IAMロールの作成
ACMと関連付けるため、ACM for Nitro Enclaves専用のロールを作成しておくといいです。もちろんコンソール作業のためにSSMの権限も忘れずに
以下のポリシーを設定した「ACMforNitroEnclavesRole」を作成します。

  • 信頼ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Principal": {
                "Service": [
                    "ec2.amazonaws.com"
                ]
            }
        }
    ]
}

許可ポリシー:AmazonSSMManagedInstanceCore

 

作成されたら、ARN:arn:aws:iam::************:role/ACMforNitroEnclavesRoleをメモしておきます。

5.IAMロールとACM証明書の関連付け
aws-cliがインストールされている環境で、4.のIAMロールと1.のACM証明書を関連付けるaws-cliコマンドを実行します。
ここで、4.でメモしたARNを--role-arnに、1.でメモしたARNをcertificate_ARNに指定

# aws ec2 associate-enclave-certificate-iam-role --certificate-arn arn:aws:acm:ap-northeast-1:************:certificate/c60a1383-e409-42ad-83ad-70044654c8c3 --role-arn arn:aws:iam::************:role/ACMforNitroEnclavesRole --no-verify

urllib3/connectionpool.py:1063: InsecureRequestWarning: Unverified HTTPS request is being made to host 'ec2.ap-northeast-1.amazonaws.com'. Adding certificate verification is strongly advised.
See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
{
    "CertificateS3BucketName": "aws-ec2-enclave-certificate-ap-northeast-1-prod",
    "CertificateS3ObjectKey": "arn:aws:iam::123456789012:role/ACMforNitroEnclavesRole/arn:aws:acm:ap-northeast-1:123456789012:certificate/c****3",
    "EncryptionKmsKeyId": "0****3"
}

自己署名証明書を使っているため、そのままだとCERTIFICATE_VERIFY_FAILEDエラーになるため--no-verifyオプションを指定して回避してます。警告は出ちゃってますがコマンドは通ってます。

6.IAMロールに許可ポリシーを追加
4.で作成したIAMロールに、証明書が保存されるS3バケットへのアクセス、KMS、ロールへのアクセスができるよう許可ポリシー「ACMforNitroEnclaves-policy」を作成して追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::aws-ec2-enclave-certificate-ap-northeast-1-prod/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:*:key/0****3"
        },
        {
            "Effect": "Allow",
            "Action": "iam:GetRole",
            "Resource": "arn:aws:iam::************:role/ACMforNitroEnclavesRole"
        }
    ]
}

7.EC2インスタンスにIAMロールをアタッチ

8.ACM for Nitro Enclaves設定ファイル作成
ACM for Nitro Enclavesにはデフォルトのサンプル設定ファイルacm-httpd.example.yaml があるので、それをリネームしてから編集します。

# cd /etc/nitro_enclaves/
# ls
acm-httpd.example.yaml  acm.example.yaml  allocator.yaml  vsock-proxy.yaml
# mv acm-httpd.example.yaml acm.yaml
# ls -la
total 32
drwxr-xr-x.  2 root root    92 Jul  4 01:22 .
drwxr-xr-x. 83 root root 16384 Jul  3 07:17 ..
-rw-r--r--.  1 root root  2948 Feb  8  2023 acm.example.yaml
-rw-r--r--.  1 root root  3006 Feb  8  2023 acm.yaml
-rw-rw-r--.  1 root root   473 Jun  6 21:12 allocator.yaml
-rw-r--r--.  1 root root  2955 Jun  6 21:12 vsock-proxy.yaml

このもう一つのacm.example.yamlというのが気になったので中身を見てみると、Nginx用の設定っぽい。名前が若干紛らわしい。
acm-httpd.example.yaml⇨Apacheサンプル用
acm.example.yaml   ⇨Nginxサンプル用

acm.yamlを編集し、certificate_arn:に証明書のARNを指定

       certificate_arn: "arn:aws:acm:ap-northeast-1:************:certificate/c****3"

また、acm.yamlで以下のようにパスを指定している箇所があります。

    target:
      Conf:
        # Path to the server configuration file to be written by
        # the ACM service whenever the certificate configuration changes
        # (e.g. after a certificate renewal). The SSLCertificateKeyFile and
        # optionally the SSLCertificateFile directives shall be populated.
        path: /etc/httpd/conf.d/httpd-acm.conf

httpd-acm.confは、証明書の更新など証明書が変更されるたびに、ACMによって書き込まれるサーバー設定ファイルということらしい。
httpd-acm.confというのはデフォルトでは無いので、ssl.confをリネームしてhttpd-acm.confにします。
(コピーだとデフォルトでssl.confが読み込まれてしまうため、必ずリネームしてください)

# cd /etc/httpd/conf.d/
# ls
README  autoindex.conf  ssl.conf  userdir.conf  welcome.conf
# mv ssl.conf httpd-acm.conf

httpd-acm.confの一番下に以下を追加します。

<VirtualHost *:443>
ServerName nozaki2.com
SSLEngine on
SSLProtocol -all +TLSv1.2
SSLCertificateKeyFile "/etc/pki/tls/private/localhost.key"
SSLCertificateFile "/etc/pki/tls/certs/localhost.crt"
</VirtualHost>

ちなみにSSLCertificateKeyFileとSSLCertificateFileのパスは適当でよいようです。サービス起動後に以下のように自動的に変わってました。

SSLCertificateKeyFile "pkcs11:model=p11ne-token;manufacturer=Amazon;token=httpd-acm-token;id=%01;object=acm-key;type=private?pin-value=2****6"
SSLCertificateFile "/run/nitro_enclaves/acm/httpd-cert-6****e.pem"

9.サービス起動
ACM for Nitro Enclavesサービスを起動・有効化します。

# systemctl start nitro-enclaves-acm.service
# systemctl enable nitro-enclaves-acm.service
Created symlink /etc/systemd/system/multi-user.target.wants/nitro-enclaves-acm.service → /usr/lib/systemd/system/nitro-enclaves-acm.service.

10.接続テスト
その前にApache(httpd)がなぜか起動せず失敗。証明書等が読めないっぽい

[Thu Jul 04 02:59:31.637600 2024] [ssl:error] [pid 25984:tid 25984] AH10491: Init: OSSL_STORE_open failed for PKCS#11 URI `pkcs11:model=p11ne-token;manufacturer=Amazon;token=httpd-acm-token;id=%01;object=acm-key;type=private?pin-value=41f033d01ee982a4936c426daf2fd110' 
[Thu Jul 04 02:59:31.637618 2024] [ssl:emerg] [pid 25984:tid 25984] AH10492: Init: OSSL_STORE_INFO_PKEY lookup failed for private key identifier `pkcs11:model=p11ne-token;manufacturer=Amazon;token=httpd-acm-token;id=%01;object=acm-key;type=private?pin-value=41f033d01ee982a4936c426daf2fd110' 
[Thu Jul 04 02:59:31.637624 2024] [ssl:emerg] [pid 25984:tid 25984] AH02312: Fatal error initialising mod_ssl, exiting.

考察

AL2023 OpenSSLとの相性か、結局OpenSSLが証明書等を読み込めず。
せっかくなのでここでnitro-enclaves-acmサービスの中身を考察してみます。

# cat /usr/lib/systemd/system/nitro-enclaves-acm.service
[Unit]
Description=Nitro Enclaves ACM Agent
After=network-online.target
DefaultDependencies=no
Requires=nitro-enclaves-allocator.service
After=nitro-enclaves-allocator.service
Before=nginx.service httpd.service

[Service]
Type=simple
ExecStartPre=-/usr/bin/mkdir -p /run/nitro_enclaves/acm
ExecStart=/usr/bin/p11ne-agent
ExecStopPost=/usr/bin/rm -r /run/nitro_enclaves/acm
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=5

[Install]
WantedBy=multi-user.target
  1. [Unit]セクション:
    依存関係を定義

    • nitro-enclaves-allocator.serviceの後、かつnginx.serviceã‚„httpd.serviceの前に起動するように設定
    • nitro-enclaves-allocatorでCPUの割り当て
  2. [Service]セクション:

    • 起動前に/run/nitro_enclaves/acmディレクトリを作成
    • /usr/bin/p11ne-agentを実行してサービス開始
    • サービス停止後に/run/nitro_enclaves/acmディレクトリを削除
    • サービスが終了した場合に5秒後に再起動するように設定
    • 60秒間に5回までサービスを再起動できるように制限
  3. [Install]セクション:

    • multi-userターゲットでサービスを有効化

このsystemdサービスは、Nitro Enclaves環境でのACMエージェント(p11ne-agent)の管理を行っています。残念ながらp11ne-agentはバイナリのため中身は見れず

# more /usr/bin/p11ne-agent
******** /usr/bin/p11ne-agent: Not a text file ********

nitro-enclaves-acmサービスのステータスを見ると、

# systemctl status nitro-enclaves-acm
● nitro-enclaves-acm.service - Nitro Enclaves ACM Agent
     Loaded: loaded (/usr/lib/systemd/system/nitro-enclaves-acm.service; enabled; preset: disabled)
     Active: active (running) since Sat 2024-07-06 09:43:03 JST; 40min ago
    Process: 2167 ExecStartPre=/usr/bin/mkdir -p /run/nitro_enclaves/acm (code=exited, status=0/SUCCESS)
   Main PID: 2172 (p11ne-agent)
      Tasks: 5 (limit: 18909)
     Memory: 45.7M
        CPU: 9.448s
     CGroup: /system.slice/nitro-enclaves-acm.service
             ├─2172 /usr/bin/p11ne-agent
             └─2191 nitro-cli run-enclave --eif-path /usr/share/nitro_enclaves/p11ne/p11ne.eif --cpu-count 2 --memory 256

Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |INFO  | Refreshing token httpd-acm-token
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |INFO  | Service: httpd | Force_Start: true | Reload: 0 | Sync: 600
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |INFO  | Reloading HTTPD configuration.
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |INFO  | HTTPD is not running. Starting it now.
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal systemctl[2895]: Job for httpd.service failed because the control process exited with error c>
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal systemctl[2895]: See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for>
Jul 06 09:53:13 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |ERROR | Unable to reload HTTPD: SystemdStartHttpdError(Some(1))
Jul 06 10:03:11 ip-10-0-16-102.ap-northeast-1.compute.internal p11ne-agent[2172]: |INFO  | Syncing token httpd-acm-token

p11ne-agentがHTTPD構成のリロードをして呼び出してるっぽい。このことから、ACMエージェントがApacheやNginxの設定ファイルの証明書のパス等を書き換えてるのではと。
なので、独自Webデーモンを使っていたとしても、このパス等を設定ファイルにコピーして呼び出すようにすれば、ApacheやNginxの代替ができるのではないかと推測します。

所要時間

2時間