AWS プライベート認証局を設定して、IAM Roles Anywhere で使用する証明書を発行する

記事タイトルとURLをコピーする

エンタープライズクラウド部 技術3課の山本拓海です。
好きなプリンスのアルバムは「Prince」です。

このPVの大学生みたいな風貌のプリンスが、後のイメージと大きくかけ離れていて、なんかかわいくて好きです。

さて。
先日AWSセキュリティブログにてポストされた「Set up AWS Private Certificate Authority to issue certificates for use with IAM Roles Anywhere」という記事の内容が大変興味深いものでした。

aws.amazon.com

こちらの内容を深掘りして理解を深めます。

イントロ 記事の内容で何を解決できるか

元ポストの構成は、AWSリソースを使い、IAM Roles Anywhereで使用する、AWS外デバイスのエンドエンティティ証明書を自動で更新するシステムです。 このシステムは実際の運用を考慮された構成になっており、IAM Roles Anywhereの実運用構成の1つのリファレンスになると思います。

この構成を使うことで、システム用のIAM Userを発行する必要がなくなるので、長期認証情報によるセキュリティの脆弱性が減らせることがメリットです。

1点だけ気になるのは、実行する全てのコマンドがAWSのCloudShellと運用するAWS外デバイスの2環境だけで対応ができない点です。
CloudFormationの実行や準備に、git, AWS CLIを準備済みの特定のS3バケットに書き込み権限を持った環境が必要になります。 ただ、このままでも十分に実運用可能なので、今回はこのまま行います。

IAM Roles Anywhereの仕組み

IAM Roles Anywhereの仕組みや関係性の理解するのにあたり、AWS Private CAとIAM Roles AnywhereとAWS外デバイスの関係を整理した図が以下の通りです。

AWS Private CAとIAM Roles AnywhereとAWS外デバイスの構成図
AWS Private CAとIAM Roles AnywhereとAWS外デバイスの構成図

IAM Roles AnywhereとAWS Private CAの連携についてより詳しい内容は、以下の記事をご覧ください。

blog.serverworks.co.jp

アーキテクチャの解説

上述した以前の弊社の記事で手動で行っている、署名要求(CSR)と秘密鍵の発行、エンドエンティティ証明書のデプロイを1日2回実行するEventBridgeからキックされたLambda関数で自動で行います。

構成図に番号が降ってありますが、基本的には3つあるLambda関数からエンドエンティティ証明書発行までの全てのプロセスを実行しています。 それぞれのLambda関数がどのプロセスを実行しているか、詳しく見ていきます。

構築するシステムの構成図
構築するシステムの構成図

以下の関数で紹介している番号は構成図内の番号です。

1. CertCheck

EventBridgeで1日2回キックされるCertCheck関数では、②③の処理でDynamoDBのテーブルをスキャンし、証明書管理が必要なインスタンスを特定を行い、有効期限が切れているデバイスがあれば、CertIssue関数をキックします。(④)

2. CertIssue

CertCheck関数で証明書の有効期限が切れたデバイスがある場合、CertIssue関数がキックされ、SSM Run Command経由でCSRと秘密鍵生成のコマンドをAWS外デバイス内で実行します。(⑥)
⑦の処理内のコマンドでCSRと秘密鍵を生成し、⑧⑨の処理では生成したCSRを用いてAWS Private CAにて証明書に署名をリクエストし、エンドエンティティ証明書を発行します。

3. CertDeploy

CertIssue関数実行時にEventBridgeにて証明書の発行イベントを検知したら(⑩)CertDeploy関数に発行したエンドエンティティ証明書のARNを渡して、関数をキックします(⑪)。
CertDeploy関数内で、エンドエンティティ証明書をAWS Private CAから取得し、デバイスにSSM Run Command経由でエンドエンティティ証明書のCRTファイルを書き込みます。(⑫⑬)⑫では同時にDynamoDB上の有効期限を書き換えます。

CertDeploy関数実行後、デバイス上には、エンドエンティティ証明書と秘密鍵があるので、IAM Roles AnywhereからIAM Roleを付与できる状態となります。(⑭)

費用について

本システムの実行に必要なAWSリソースと費用です。
元ポストに以下の記述があるので、参考にしてください。

100 台のホストの証明書を管理する場合、月額約 85 ドルがかかります。ただし、Systems Manager のアドバンスト・ティアで 1,100 台のホストを大規模に導入する場合、コストは月額約 5937 ドルとなります

1100台のホストで費用が高くなるのは、ハイブリッドアクティベーションにて管理するデバイスが1000台以上は課金されるためです。

東京リージョンでの費用の時間あたりの単価は以下の通りです。
一番費用が大きいサービスが、AWS Private CA の 月額50USDですが、プライベート CA の運用が 1 か月に満たない場合、CA を作成および削除した時点に基づいて按分計算されるので、お試しで構築する場合は50USDはかかりません。

  • EventBridge
    • スケジューラーでの実行は月間 14,000,000 回の呼び出しを無料
  • AWS SSM Run Command
    • 無料
  • AWS SSM ハイブリッドアクティベーション
    • アカウントごとにリージョンあたり最大 1,000 までの無料
  • IAM Roles Anywhere
    • 無料
  • Lambda
    • 0.001002 USD / 1分
  • Dynamo DB
    • 書き込み要求単位 (WRU) 書き込み要求ユニット 100 万あたり 1.4269USD
    • 読み出し要求単位 (RRU) 読み出し要求ユニット 100 万あたり 0.285USD
  • AWS Private CA
    • 有効期間の短い証明書モードの場合、プライベート CA あたり月額 50 USD
    • 1 個以上の証明書 0.058 USD

準備と構築

環境の説明

今回使用する環境は、以下の通りです。

  1. AWS外デバイス
    • IAM Roles AnywhereでRoleを付与する環境
    • 記事の実行環境はUbuntu 22.04.2を使用
  2. 個人ローカル環境
    • CloudFormationなどの管理・編集を行うローカル環境
  3. CloudShell
    • Lambda関数の初回実行などを行う環境

準備

元ポストのPrerequisitesとEnabling Systems manager hybrid activationの箇所の説明は省きます。
hybrid activationが完了すると、AWS外デバイスにSSM Run Commandが実行できるようになります。手順に沿って進めてください。

詳しくは以前の弊社のブログを参考に進めてください。

blog.serverworks.co.jp

構築

git, AWS CLIが使用できる個人ローカル環境で行います。

構築での作業は以下の通りです。

  1. gitで構築するリポジトリをclone
  2. IAM Roleの編集とCloudformation packageコマンドを実行
  3. Cloudformation deployコマンドを実行

gitで構築するリポジトリをclone

リポジトリを clone します。

git clone https://github.com/aws-samples/aws-privateca-certificate-deployment-automator.git

IAM Roleの編集とCloudformation packageコマンドを実行

AWS CLIにてCloudformation packageコマンドを実行します。

Cloudfomation Packageコマンドについては、以下のページに詳しい説明があります。

docs.aws.amazon.com

CloudFormation packageコマンド実行前に、デバイスに付与するIAM Roleを編集します。 IAM Roleのポリシーは元の状態だと全てDenyになっています。今回は特定のS3バケットを操作できるよう、フルコントロール可能なポリシーをアタッチします。

cf_template.yamlの77行目付近の、リソースIAMRARoleのPolicies属性を書き換えます。
コメントアウトしているのが元の記述です。

      Policies:
#        - PolicyName: DenyAll
        - PolicyName: Allow-s3-fullcontrol
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
#              - Effect: Deny
#                Action: '*'
#                Resource: '*'
              - Effect: Allow
                Action: 's3:*'
                Resource: 'arn:aws:s3:::iam-ra-handson-sample-202311/target-folder/*'

書き換えたら、CloudFormation packageコマンドを実行します。オプション s3-bucketの引数はCloudFormation用のファイルを置くバケット名を入力します。

aws cloudformation package --template-file cf_template.yaml \
--output-template-file packaged.yaml \
--s3-bucket ${S3_BUCKET_NAME}

CloudFormation packageコマンドが完了し、packaged.yaml が作成されていることを確認します。さらに、必要なファイルがs3にアップロードされているのを確認します。

Cloudformation deployコマンドを実行

CloudFormation deployコマンドで構築します。 CloudFormation deployコマンドで、指定できるパラメータは以下の通りです。適宜変更します。

パラメータキー デフォルト値 用途
AWSSigningHelperPath /root AWS Signing Helperバイナリのホスト上のデフォルトパス
CACertPath /tmp CA 証明書が作成されるホストのデフォルトパス
CACertValidity 10 デフォルトの CA 証明書の長さ(年
CACommonNam ca.example.com デフォルトの CA 証明書コモンネーム
CACountry US デフォルトの CA 証明書の国コード
CertPath /tmp 証明書が作成されるホストのデフォルトパス
CSRPath /tmp 証明書が作成されるホストのデフォルトパス
KeyAlgorithm RSA_2048 CA 秘密鍵の作成に使用されるデフォルト・アルゴリズム
使用できる値
- RSA_2048
- EC_prime256v1
- EC_secp384r1
- RSA_4096
- EC_secp384r1
- RSA_4096
KeyPath /tmp 秘密鍵が作成されるホストのデフォルトパス。
OrgName Example Corp デフォルトの CA 証明書組織名
SigningAlgorithm SHA256WITHRSA 発行された証明書のデフォルトの CA 署名アルゴリズム
使用できる値
- SHA256WITHECDSA
- SHA256WITHRSA
- SHA384WITHECDSA
- SHA384WITHRSA
- SHA512WITHECDSA
- SHA512WITHRSA

CACommonNamとCACountryとOrgNameを変更します。今回は以下のコマンドで実行します。

aws cloudformation deploy --template packaged.yaml \
--stack-name ssm-pca-stack \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides CACommonNam=serverworks.co.jp \
    CACountry=JP \
    OrgName=Serverworks \
    SNSSubscriberEmail=takumi.yamamoto@serverworks.co.jp

3分ほどで構築完了。構築物の内容を確認していく前に、不意に実行されないようにするためEventBridgeのスケジュール実行を無効化しておきます。

EventBridgeのスケジュール実行を無効化
EventBridgeのスケジュール実行を無効化

続いて、証明書の発行前に以下の準備を行います。

  1. SNSのサブスクリプション
  2. DynamoDBの準備

SNSのサブスクリプションの確認の説明は省きます。

DynamoDBの準備を行います。 元ポストの手順に沿って、Fleet ManagerからNode IDをコピーし、DynamoDBにIDを投入します。さらに、元ポストに記載のある、サポートする属性を入力し、CloudFormationの内容をオーバーライドしてみます。

DynamoDBのレコード
DynamoDBのレコード

今回はSingning Helperのパスを上書きします。属性の型はstring型です。
ここまででLambda関数実行前の準備は完了です。

CertCheck-Trigger関数の実行と実行後の確認

以下のコマンドでCertCheck-Trigger関数を実行し、証明書の発行を行います。
CertCheck-Trigger関数の実行は、利用するAWSアカウントのCloudShellから行います。十分な権限を持つユーザーで進めてください。

aws lambda invoke --function-name CertCheck-Trigger \
--cli-binary-format raw-in-base64-out \
response.json

ここでのresponse.jsonの内容は 「null」だけです。
以下は初回の実行後の各Lambda関数のログです。読みにくいので実行時間とリクエストIDは省略。

ロググループ /aws/lambda/CertCheck-Trigger の内容

[INFO]  Starting the certificate check process
[INFO]  Found credentials in environment variables.
[INFO]  No expiry found for host: mi-0d2xxxxxxxxx1b5. A certificate will be issued.
[INFO]  Invoked the certificate issue lambda for host: mi-0d2xxxxxxxxx1b5

ロググループ /aws/lambda/CertIssue の内容

[INFO]   Found credentials in environment variables.
[INFO]  Incoming event: {'hostID': 'mi-0d2xxxxxxxx1b5', 'certPath': '/tmp', 'keyPath': '/tmp'}
[INFO]  Certificate signing issued to PCA successfully for host: mi-0d2xxxxxxxx1b5
[INFO]  {'CertificateArn': 'arn:aws:acm-pca:ap-northeast-1:958xxxxxxx246:certificate-authority/ec4403de-xxxxxxxxxxxx52d7/certificate/b8a7bf5570c7d25c07dd0f0b781ce8da', 'ResponseMetadata': {'RequestId': 'request-id-sample', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Thu, 23 Nov 2023 07:47:59 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '168', 'connection': 'keep-alive', 'x-amzn-requestid': 'request-id-sample'}, 'RetryAttempts': 0}}

ロググループ /aws/lambda/CertDeploy の内容

[INFO]   Extracted details - Common Name: mi-0d2xxxxxxxxx1b5, Serial: 151xxxxxxxxxxx8E0, Expiry: 2023-11-30 08:03:23
[INFO]  Using paths - cacert_path: /tmp, key_path: /tmp, cert_path: /tmp, AWSSigningHelperPath: /home/user-1
[INFO]  SSM Command - ID: 8a90f87b-fb49-4956-bfd6-bff7782abbdb, Status: Success, Output:
[INFO]  DynamoDB update details - Host ID: mi-0d2xxxxxxxxx1b5, Serial: 151xxxxxxxxxxx8E0, Expiry: 2023-11-30 08:03:23

CertDeployのログにSuccessとあるので、AWS外デバイスへの証明書のデプロイが成功しているようです。

DynamoDBのレコードの確認です。

関数実行後のDynamoDBのレコード
関数実行後のDynamoDBのレコード

有効期限とシリアルNoの項目が追加されています。

デバイス内のファイルの確認と認証ヘルパーコマンドの実行

AWS外デバイス内の各ファイルの確認を行います。

user-1@ubuntu-1:~$ ls -la /tmp

drwxrwxrwt 15 root   root   4096 Nov 23 17:03 .
drwxr-xr-x 21 root   root   4096 Jul 25 20:14 ..
-rw-r--r--  1 root   root   1196 Nov 23 17:03 ca_chain_certificate.crt
-rw-r--r--  1 root   root   1432 Nov 23 17:03 mi-0d2xxxxxxxxx1b5.crt
-rw-r--r--  1 root   root    907 Nov 23 17:03 mi-0d2xxxxxxxxx1b5..csr
-r--------  1 root   root   1708 Nov 23 17:03 mi-0d2xxxxxxxxx1b5..key

IAM Roles AnywhereでIAM Roleを付与できるファイルが揃っています。 実際に認証ヘルパーコマンドを実行し、認証情報を取得してみます。

認証ヘルパーコマンドの詳細な情報は以下のリンクをご確認ください。

docs.aws.amazon.com

認証ヘルパーコマンド実行の際に使用する、信頼アンカーのARNとプロファイルのARNはIAM Roles Anywhereのダッシュボードから遷移できる詳細画面内にあります。

信頼アンカーのARN
信頼アンカーのARN

プロファイルのARN
プロファイルのARN

認証ヘルパーコマンドを実行します。実行結果はjsonで返ってくるので jqで整形します。

sudo /home/user-1/aws_signing_helper credential-process --certificate /tmp/mi-0d2xxxxxx1b5.crt \
--private-key /tmp/mi-0d2xxxxxx1b5.key \
--trust-anchor-arn ${TRUST-ANCHOR-ARN} \
--profile-arn ${PROFILE-ARN} \
--role-arn ${IAM-ROLE-ARN} | jq .

# 実行結果
# {
# "Version": 1,
# "AccessKeyId": "ACCESSKEYID-SAMPLE",
# "SecretAccessKey": "SECRETACCESSKEY-SAMPLE",
# "SessionToken": "SESSIONTOKEN-SAMPLE==",
# "Expiration": "2023-11-23T09:07:59Z"
# }

認証情報を取得できました。 このコマンドをAWS CLIのconfig情報に記述することで、実行結果の認証情報を使用できるので、合わせて設定します。 AWS CLIコマンドを実行するユーザのホームフォルダ内に.aws/configを作成し、defaultプロファイルに認証ヘルパーコマンドを記載します。
既にdefaultプロファイルがある場合、適宜変更してください。

[default]
region = ap-northeast-1
output = json
credential_process = sudo /home/user-1/aws_signing_helper credential-process --certificate /tmp/mi-0d2xxxxxx1b5.crt --private-key /tmp/mi-0d2xxxxxx1b5.key --trust-anchor-arn ${TRUST-ANCHOR-ARN} --profile-arn ${PROFILE-ARN} --role-arn ${IAM-ROLE-ARN}

設定できているか確認します。

aws sts get-caller-identity


# 実行結果
# {
# "UserId": "AROAxxxxxxxxHDC:15133f67xxxxxxxxxxxxea2e8e0",
# "Account": "958xxxxx246",
# "Arn": "arn:aws:sts::958xxxxx246:assumed-role/IAMRA-Role/15133f67xxxxxxxxxxxxea2e8e0"
# }

この認証使って、特定のS3バケットへの書き込みとファイルのダウンロードを行います。

aws s3 cp ./test.txt s3://iam-ra-handson-sample-202311/target-folder/

# upload: ./test.txt to s3://iam-ra-handson-sample-202311/target-folder/test.txt

無事成功です。 当然別フォルダに対しては権限がないはずなので、こちらも確認します。

aws s3 cp ./test.txt s3://iam-ra-handson-sample-202311/not-target-folder/

# upload failed: ./test.txt to s3://iam-ra-handson-sample-202311/not-target-folder/test.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

想定通りできません。

証明書を失効する

エンドエンティティの証明書を失効させて、IAM Roleが付与されないようにしてみます。
元ポストの構成図です。

エンドエンティティ証明書失効処理構成図
エンドエンティティ証明書失効処理構成図

AWS CLIコマンドにてAWS Private CAで証明書失効リスト(CRL)を発行後、発行イベントを検知したEventBridgeが、LambdaのCRL Processor関数をキックします。CRL Processor関数でIAM Roles Anywhereに失効証明書をインポートすることでエンドエンティティ証明書を失効させます。

失効はコマンドだけで完結しますが、その後の状態を確認したいので以下の手順で行います。

  1. (準備 )AWS Private CAの監査レポート出力先S3バケットにバケットポリシーを付与する
  2. CloudShellからエンドエンティティ証明書失効コマンドを実行する
  3. 発行されたCRLファイルを確認する
  4. AWS Private CAの監査レポートを確認し、失効されていることを確認する

準備

AWS Private CAの監査レポート出力先S3バケットにバケットポリシーを付与する

エンドエンティティ証明書失効後にAWS Private CAの監査レポートから失効されていることを確認します。監査レポートの出力先のS3バケットにバケットポリシーの付与が必要です。 以下のURLを参考に行ってください。

docs.aws.amazon.com

実行

CloudShellからエンドエンティティ証明書失効コマンドを実行する

失効はCloudShellからコマンドを実行します。

aws acm-pca revoke-certificate \
  --certificate-authority-arn <certificate-authority-arn> \
  --certificate-serial <certificate-serial> \
  --revocation-reason CESSATION_OF_OPERATION

発行されたCRLファイルを確認する

コマンド実行後、30分以内にAWS Private CAはCRLファイルを生成し、CloudFormationテンプレートで作成されたCRL S3バケットのcrlフォルダにアップロードします。 crlファイルをダウンロードし、以下のコマンドで内容を確認します。

openssl crl -inform DER -in ${CRL_FILE} -text -noout

エンドエンティティ証明書が失効している場合、以下の内容が出力されています。

Revoked Certificates:
    Serial Number: ${SERIAL_NUMBER}
        Revocation Date: Nov 24 01:22:40 2023 GMT
        CRL entry extensions:
            X509v3 CRL Reason Code:
                Cessation Of Operation

また、Cloudwatch Logsの/aws/lambda/CRLProcessorにもCRL Processor関数の実行ログが出力されているので確認します。詳細は省きます。

AWS Private CAの監査レポートを確認し、失効されていることを確認する

監査レポートの確認を行います。 監査レポートの出力は、AWS Private CAの画面から行えます。

Audit Reportの出力
Audit Reportの出力

監査レポートに以下の内容が含まれています。

  {
    "awsAccountId": "958xxxxxx246",
    "certificateArn": "arn:aws:acm-pca:ap-northeast-1:958xxxxxx246:certificate-authority/ec4403xxxxxxxxxxxxxxx5c52d7/certificate/6ec39cf34c73ec550ef3d380b9495e09",
    "serial": "6e:c3:xx:xx:xx:xx:xx:xx:xx:xx:5e:09",
    "subject": "CN=mi-0d2xxxxxxxxx1b5",
    "notBefore": "2023-11-23T23:35:51+0000",
    "notAfter": "2023-12-01T00:35:51+0000",
    "issuedAt": "2023-11-24T00:35:52+0000",
    "revokedAt": "2023-11-24T01:22:40+0000",
    "revocationReason": "CESSATION_OF_OPERATION",
    "templateArn": "arn:aws:acm-pca:::template/EndEntityCertificate/V1"
  }

失効されていることが確認できました。

証明書の手動ローテーション

証明書の手動ローテーションは、DynamoDBのレコードにある有効期限をNullにした後、エンドエンティティ証明書発行コマンドを実行するとローテーション可能です。
ここまでの内容に記載があるので、詳細は省きます。

最後に

AWSセキュリティブログ「Set up AWS Private Certificate Authority to issue certificates for use with IAM Roles Anywhere」で紹介されている内容について深掘りしていきました。

本システムを実環境で運用される場合の注意点として、EventBriegeのスケジュール実行を最初に止めています。
スケジュール実行で行いたい場合は再度有効化してください。

ご一読いただきありがとうございました。

山本 拓海(執筆記事の一覧)

エンタープライズクラウド部 クラウドリライアビリティ課
Security Hubになりたい

写真は黒猫のくま。
記事に関するお問い合わせや修正依頼⇒ takumi.yamamoto@serverworks.co.jp