Amazon Web Services ブログ
タスクIAMロールとパラメータストアを利用したAmazon ECSアプリケーションの秘密情報管理
同僚のStas Vonholskyが、Amazon ECSアプリケーションの秘密情報管理に関する素晴らしいブログを寄稿してくれました。
—–
コンテナ化されたアプリケーションとマイクロサービス指向のアーキテクチャが普及するにつれて、アプリケーションデータベースにアクセスするためのパスワードなどの秘密情報を管理することは、より困難かつ重要になっています。
いくつか課題の例を挙げます:
- dev、test、prodなどのさまざまなアクセスパターンをコンテナ環境全体でサポートしたい
- ホストレベルではなくコンテナ/アプリケーションレベルで秘密情報へのアクセスを隔離したい
- サービスとしても、他サービスのクライアントとしても、アクセスが必要である疎結合なサービスが複数存在する
この記事では、Amazon ECS上で動作するコンテナアプリケーションの秘密情報管理をサポートする、新しくリリースされた機能に焦点を当てています。同僚のMatthew McCleanもAWSセキュリティブログに、S3とDockerを使用してECSベースのアプリケーションの秘密情報を管理する方法についての素晴らしい記事を公開しています。この記事では、コンテナパラメータ変数を使って秘密情報を受け渡し/保存する際の制限について説明しています。
多くの秘密情報管理ツールは、次のような機能を提供しています。
- 高セキュアなストレージシステム
- 集中管理機能
- セキュアな認証と認証メカニズム
- 鍵管理および暗号化プロバイダとの統合
- アクセスのための安全な導入メカニズム
- 監査
- 秘密情報のローテーションと失効
Amazon EC2 Systems Manager パラメータストア
パラメータストアは、Amazon EC2 Systems Managerの1機能です。秘密情報のための集中化/暗号化されたデータストアを提供し、Run CommandやステートマネージャーなどのSystems Managerの他の機能と組み合わせることで多くの利点をもたらします。このサービスはフルマネージドで、高い可用性・セキュリティを持っています。
System Manager API、AWS CLI、およびAWS SDKを使用してパラメータストアにアクセスできるため、汎用の秘密情報管理ストアとして使用することができます。秘密情報は簡単にローテートしたり取り消したりすることができます。パラメータストアはAWS Key Management Service (KMS)と統合されているため、デフォルトまたはカスタムのKMSキーを使用して特定のパラメータを暗号化できます。 KMSキーをインポートすることで、独自のキーを使用して機密データを暗号化することも可能です。
パラメータストアへのアクセスはIAMポリシーによって実現にされ、リソースレベルのアクセス許可をサポートします。特定のパラメータまたは名前空間にアクセス権を持つIAMポリシーを使用して、これらのパラメータへのアクセスを制限することができます。 CloudTrailログを有効にすると、パラメータへのアクセスを記録することも可能です。
Amazon S3も上記の機能の多くを持つので、集約秘密情報ストアの実装にも使用できますが、パラメータストアにはさらに次のような利点があります。
- アプリケーションライフサイクルのさまざまなステージをサポートするためのネームスペースを簡単に作成できる
- インスタンスまたはコンテナからKMSキーにアクセスし、ローカルメモリ内で復号化を実行することで、アプリケーションからパラメータの暗号化を抽象化するKMS統合機能を提供する
- パラメータの変更履歴を保管できる
- 他の多くのアプリケーションで使用されるS3とは別に制御できる
- 複数システムを実装する際のオーバーヘッドを削減する構成情報ストアとなる
- 使用料がかからない
注:本記事の公開時点では、Systems ManagerはVPCプライベートエンドポイント機能をサポートしていません。プライベートVPCからのパラメータストアエンドポイントへのより厳密なアクセスを強制するには、限られたIPアドレスセットからのみにアクセスを制限するIAMポリシーConditionとともに、Elastic IPアドレスを持つNATゲートウェイを使用します。
タスクIAMロール
Amazon ECSタスク用のIAMロールを使用すると、タスク内のコンテナが使用するIAMロールを指定することができます。 AWSサービスと対話するアプリケーションは、AWSクレデンシャルでAPIリクエストに署名する必要があります。ECSタスクIAMロールは、Amazon EC2インスタンスプロファイルがEC2インスタンスにクレデンシャルを提供するのと同じように、コンテナアプリケーションのクレデンシャルを管理する方法を提供します。
AWSクレデンシャルを作成してコンテナに配布したり、EC2インスタンスロールを使用する代わりに、IAMロールをECSタスク定義またはRunTask API操作に関連付けることができます。詳細については、IAM Roles for Tasksを参照してください。
タスク用のIAMロールを利用することで、集約パラメータストアを使用してアプリケーションまたはコンテナを安全に導入および認証することができます。秘密情報管理機能へのアクセスには、次のような機能が含まれている必要があります。
- 使用されたクレデンシャルのTTL制限
- きめ細やかな認可ポリシー
- リクエストを追跡するためのIDのログ保存
- デプロイされたコンテナまたはタスクと関連するアクセス権との間をマッピングできるスケジューラの統合サポート
タスクIAMロールは、ロールが定義されているコンテナ内からのみロールクレデンシャルにアクセスできるため、これらのユースケースをうまくサポートします。このロールは一時的なクレデンシャルを提供し、これらは自動的にローテーションされます。IAMポリシーは、送信元インスタンス、送信元IPアドレス、時刻などのオプション条件をサポートされています。
ソースとなるIAMロールは、一意のAmazonリソースネームに基いてCloudTrailログで識別でき、アクセス許可はいつでもIAM APIまたはコンソールで取り消すことができます。パラメータストアはリソースレベルの権限をサポートするため、特定のキーと名前空間へのアクセスを制限するポリシーを作成できます。
動的な環境の関連付け
多くの場合、環境を移動する際にもコンテナイメージは変更されず、イミュータブルなデプロイメントと結果再現性を保証します。変更されうるものは構成情報、この文脈では具体的には秘密情報です。たとえば、データベースとそのパスワードは、ステージング環境と本番環境で異なる場合があります。正しい秘密情報を取得するためにどのようにアプリケーションを指定すればよいでしょうか?
1つのオプションは、環境変数として環境タイプをコンテナに渡すことです。次に、アプリケーションは環境タイプ(prod、testなど)を相対キーパスに連結し、関連する秘密情報を取得します。ほとんどの場合、この方法では多数の独立したECSタスク定義が生まれてしまいます。
IAMロールは、“environment type”などの単一のCloudFormationパラメータで、パラメータストア、KMSキーおよび環境プロパティへのアクセスを提供します。
このアプローチを利用することで、一般的なCloudFormationテンプレートに基づいた単一のタスク定義をサポートすることができます。
ウォークスルー:タスクIAMロールを使用してパラメータストアリソースに安全にアクセスする
このウォークスルーはNorth Virginiaリージョン(us-east-1)向けに構成されています。同じリージョンを使って試すことをお勧めします。
Step 1: KMSキーとパラメータを作成する
最初に、既定のセキュリティポリシーを使用して、いくつかのパラメータを暗号化するためのKMSキーを作成します。
- prod-app1 – app1の秘密情報を暗号化するために使用されます
- license-key – ライセンス関連の秘密情報を暗号化するために使用されます
aws kms create-key --description prod-app1 --region us-east-1
aws kms create-key --description license-code --region us-east-1
両方のコマンドの出力のKeyIdプロパティを確認してください。ウォークスルー全体でKMSキーを識別するために使用します。
次のコマンドで、パラメータストアに3つのパラメータを作成します。
- prod.app1.db-pass(prod-app1 KMSキーで暗号化)
- general.license-code(license-key KMSキーで暗号化)
- prod.app2.user-name(暗号化されていない標準文字列として保存)
aws ssm put-parameter --name prod.app1.db-pass --value "AAAAAAAAAAA" --type SecureString --key-id "<key-id-for-prod-app1-key>" --region us-east-1
aws ssm put-parameter --name general.license-code --value "CCCCCCCCCCC" --type SecureString --key-id "<key-id-for-license-code-key>" --region us-east-1
aws ssm put-parameter --name prod.app2.user-name --value "BBBBBBBBBBB" --type String --region us-east-1
Step 2: IAMロールとポリシーを作成する
ここで、後で作成するECSタスクと関連付けるロールとIAMポリシーを作成します。
IAMロールの信頼ポリシーは、ecs-tasksエンティティがロールを引き受けることを許可する必要があります。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
上記のポリシーを、ecs-tasks-trust-policy.jsonという名前のファイルとしてローカルディレクトリに保存します。
aws iam create-role --role-name prod-app1 --assume-role-policy-document file://ecs-tasks-trust-policy.json
次のポリシーはロールにアタッチされ、後でapp1コンテナに関連付けられます。 prod.app1.*という名前空間パラメータおよびライセンスコードパラメータへのアクセスと、prod.app1.db-passパラメータを復号化するために必要な暗号化キーへのアクセスが許可されています。名前空間リソースのパーミッション構造は、さまざまな階層を構築するのに便利です(環境、アプリケーションなどに基づいています)。
次のポリシーの<key-id-for-prod-app1-key>を関連するKMSキーのキーIDに、<account-id>をあなたのアカウントIDに置き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:DescribeParameters"
],
"Resource": "*"
},
{
"Sid": "Stmt1482841904000",
"Effect": "Allow",
"Action": [
"ssm:GetParameters"
],
"Resource": [
"arn:aws:ssm:us-east-1:<account-id>:parameter/prod.app1.*",
"arn:aws:ssm:us-east-1:<account-id>:parameter/general.license-code"
]
},
{
"Sid": "Stmt1482841948000",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:us-east-1:<account-id>:key/<key-id-for-prod-app1-key>"
]
}
]
}
上記のポリシーを、app1-secret-access.jsonという名前のファイルとしてローカルディレクトリに保存します。
aws iam create-policy --policy-name prod-app1 --policy-document file://app1-secret-access.json
次のコマンドの<account-id>はあなたのアカウントIDに置き換えてください:
aws iam attach-role-policy --role-name prod-app1 --policy-arn "arn:aws:iam::<account-id>:policy/prod-app1"
Step 3:S3バケットにテストスクリプトを追加する
以下のスクリプトファイルを作成し、access-test.shという名前をつけてアカウントのS3バケットに追加します。オブジェクトがパブリックアクセス可能であることを確認し、オブジェクトリンクを書き留めておいてください。
例えば、https://s3-eu-west-1.amazonaws.com/my-new-blog-bucket/access-test.shのようになります。
#!/bin/bash
#This is simple bash script that is used to test access to the EC2 Parameter store.
# Install the AWS CLI
apt-get -y install python2.7 curl
curl -O https://bootstrap.pypa.io/get-pip.py
python2.7 get-pip.py
pip install awscli
# Getting region
EC2_AVAIL_ZONE=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`
EC2_REGION="`echo \"$EC2_AVAIL_ZONE\" | sed -e 's:\([0-9][0-9]*\)[a-z]*\$:\\1:'`"
# Trying to retrieve parameters from the EC2 Parameter Store
APP1_WITH_ENCRYPTION=`aws ssm get-parameters --names prod.app1.db-pass --with-decryption --region $EC2_REGION --output text 2>&1`
APP1_WITHOUT_ENCRYPTION=`aws ssm get-parameters --names prod.app1.db-pass --no-with-decryption --region $EC2_REGION --output text 2>&1`
LICENSE_WITH_ENCRYPTION=`aws ssm get-parameters --names general.license-code --with-decryption --region $EC2_REGION --output text 2>&1`
LICENSE_WITHOUT_ENCRYPTION=`aws ssm get-parameters --names general.license-code --no-with-decryption --region $EC2_REGION --output text 2>&1`
APP2_WITHOUT_ENCRYPTION=`aws ssm get-parameters --names prod.app2.user-name --no-with-decryption --region $EC2_REGION --output text 2>&1`
# The nginx server is started after the script is invoked, preparing folder for HTML.
if [ ! -d /usr/share/nginx/html/ ]; then
mkdir -p /usr/share/nginx/html/;
fi
chmod 755 /usr/share/nginx/html/
# Creating an HTML file to be accessed at http://<public-instance-DNS-name>/ecs.html
cat > /usr/share/nginx/html/ecs.html <<EOF
<!DOCTYPE html>
<html>
<head>
<title>App1</title>
<style>
body {padding: 20px;margin: 0 auto;font-family: Tahoma, Verdana, Arial, sans-serif;}
code {white-space: pre-wrap;}
result {background: hsl(220, 80%, 90%);}
</style>
</head>
<body>
<h1>Hi there!</h1>
<p style="padding-bottom: 0.8cm;">Following are the results of different access attempts as expirienced by "App1".</p>
<p><b>Access to prod.app1.db-pass:</b><br/>
<pre><code>aws ssm get-parameters --names prod.app1.db-pass --with-decryption</code><br/>
<code><result>$APP1_WITH_ENCRYPTION</result></code><br/>
<code>aws ssm get-parameters --names prod.app1.db-pass --no-with-decryption</code><br/>
<code><result>$APP1_WITHOUT_ENCRYPTION</result></code></pre><br/>
</p>
<p><b>Access to general.license-code:</b><br/>
<pre><code>aws ssm get-parameters --names general.license-code --with-decryption</code><br/>
<code><result>$LICENSE_WITH_ENCRYPTION</result></code><br/>
<code>aws ssm get-parameters --names general.license-code --no-with-decryption</code><br/>
<code><result>$LICENSE_WITHOUT_ENCRYPTION</result></code></pre><br/>
</p>
<p><b>Access to prod.app2.user-name:</b><br/>
<pre><code>aws ssm get-parameters --names prod.app2.user-name --no-with-decryption</code><br/>
<code><result>$APP2_WITHOUT_ENCRYPTION</result></code><br/>
</p>
<p><em>Thanks for visiting</em></p>
</body>
</html>
EOF
Step 4:テストクラスタを作成する
最新のECS AMIとECSエージェントを持つ新しいECSテストクラスタを作成することをお勧めします。次のフィールド値を使用します。
- クラスタ名:access-test
- EC2インスタンスタイプ:t2.micro
- インスタンス数:1
- キーペア:インスタンスにSSHして実行中のコンテナを操作したい場合を除き、EC2キーペアは必要ありません。
- VPC:デフォルトVPCを選択します。わからない場合は、Amazon VPCコンソールで172.31.0.0/16のIPレンジのVPCのIDを探してください。
- サブネット:デフォルトVPC内のサブネットを選択します。
- セキュリティグループ:CIDRブロック0.0.0.0/0からのポート80のインバウンドアクセス用の新しいセキュリティグループを作成します。
他のフィールドはデフォルト設定のままにします。
パブリックNGINXコンテナとapp1用に作成したロールに依存するシンプルなタスク定義を作成します。利用可能なコンテナリソースやポートマッピングなどのプロパティを指定します。commandオプションは、コンテナにテストスクリプトのダウンロードおよび実行に利用されます。このテストスクリプトはAWS CLIをコンテナにインストールし、いくつかのget-parameterコマンドを実行し、結果を含むHTMLファイルを作成します。
次のコマンドの<account-id>はアカウントIDに、<your-S3-URI>はStep 3で作成したS3オブジェクトのリンクに置き換えてください。
aws ecs register-task-definition --family access-test --task-role-arn "arn:aws:iam::<account-id>:role/prod-app1" --container-definitions name="access-test",image="nginx",portMappings="[{containerPort=80,hostPort=80,protocol=tcp}]",readonlyRootFilesystem=false,cpu=512,memory=490,essential=true,entryPoint="sh,-c",command="\"/bin/sh -c \\\"apt-get update ; apt-get -y install curl ; curl -O <your-S3-URI> ; chmod +x access-test.sh ; ./access-test.sh ; nginx -g 'daemon off;'\\\"\"" --region us-east-1
aws ecs run-task --cluster access-test --task-definition access-test --count 1 --region us-east-1
アクセスの確認
タスクが実行状態になったら、インスタンスのパブリックDNS名を確認し、次のページに移動します。
http://<ec2-instance-public-DNS-name>/ecs.html
コンテナからアクセステストを実行した結果が表示されます。
テスト結果がすぐに表示されない場合は、数秒待ってからページを更新してください。
ポート80のインバウンドトラフィックがインスタンスにアタッチされたセキュリティグループで許可されていることを確認してください。
静的HTMLページに表示される結果は、コンテナから次のコマンドを実行する場合と同じものになります。
prod.app1.key1
aws ssm get-parameters --names prod.app1.db-pass --with-decryption --region us-east-1
aws ssm get-parameters --names prod.app1.db-pass --no-with-decryption --region us-east-1
必要なKMSキーとパラメータにアクセスできるポリシーがあるため、この2つのコマンドはどちらも実行可能なはずです。
general.license-code
aws ssm get-parameters --names general.license-code --no-with-decryption --region us-east-1
aws ssm get-parameters --names general.license-code --with-decryption --region us-east-1
“no-with-decryption”パラメータを持つ最初のコマンドだけが動作するはずです。指定したポリシーでは、パラメータストアのパラメータへのアクセスは許可されていますが、KMSキーへのアクセスは許可されていません。 2番目のコマンドは、アクセス拒否エラーで失敗するはずです。
prod.app2.user-name
aws ssm get-parameters --names prod.app2.user-name –no-with-decryption --region us-east-1
prod.app2の名前空間に関連付けられている権限がないため、このコマンドはアクセス拒否エラーで失敗するはずです。
仕上げ
料金が発生しないように、すべてのリソース(KMSキーやEC2インスタンスなど)を削除することを忘れないでください。
まとめ
集約秘密情報管理は、コンテナ化された環境のセキュリティにとって重要な機能です。
ユーザーは、パラメータストアとタスクIAMロールを使用することで、アプリケーションが必要なキーのみにアクセスすることができるKMSキーと統合されたアクセスレイヤーおよび集約秘密情報ストアを構築することができます。
秘密情報管理レイヤはパラメータストア、Amazon S3、Amazon DynamoDB、またはVaultやKeyWhizなど、どんなソリューションで実装されている場合でも、秘密情報の管理・アクセスのプロセスに不可欠なものです。
(翻訳はSA千葉が担当しました。原文はこちらです。)