今回はCircleCIでAssume RoleをしてReactのアプリケーションをS3にデプロイしてみました。
背景
これまでAWS外のサービスからAWSへのアクセスをする際にアクセスを許可するRoleをIAM Userにアタッチし、IAM UserのCredentialsキーを外部サービス(CircleCIなど)に保管していたが、外部サービスで何かしらのトラブルが生じキーが流出すると他者にAWSのアカウントにアクセスする権限を委ねてしまう可能性があるため、Assume Roleを利用しそのセキュリティリスクを回避してみる検証をしました。
以下はかなり参考にさせてもらった記事です。
事前準備(主にアカウント周り)
・AWSアカウント
・CircleCI
・Github
・ホスティング用のS3バケット
やること
- アプリケーションをホスティングするS3バケットを作成する。
2. Reactプロジェクトを作成
3. AWSアカウントにAssume RoleするUserとRoleをcfnで作成
4. CircleCIにてbuild、AssumeRoleしS3へアプリケーションを転送
5. ホスティングされているURLへアクセスして確認
#1 S3バケットを作成する
aws cliで作成するなら以下のコマンドを、コンソールマネジメントでは作成から手動で作成する。
$ aws s3 mb s3://assume-role-sample-app
ホスティングをそのまま公開するためポリシーは以下で設定する
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForGetBucketObjects",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::バケット名/*"
]
}
]
}
#2 Reactアプリケーションを作成する
create-react-app
コマンドでReactの環境を作成する
$ create-react-app assume-role-sample-app
無事作成ができればgithubでコードを管理します。
#3 AWSアカウントにAssume RoleするUserとRoleを作成
Assume RoleするためにCircleCI用のUserとRoleを作成する。
以下はcfnで作成する
雛形ができれば実際にAWSアカウントに反映させる。
以下コマンドはアクセス権限のあるユーザで実行する
$ aws cloudformation deploy \
--template-file iam.yml \
--stack-name CircleCI-Iam \
--capabilities CAPABILITY_NAMED_IAM
これでIAM UserとRoleをそれぞれCircleCI-Iamというスタックで作成することができました。
作成したスタックのIAMとRoleのARNを確認してみます。
RoleのARNはAssumeRoleする際に使用する。
$ aws cloudformation describe-stacks \
--stack-name CircleCI-Iam \
--query 'Stacks[].Outputs'
[
[
{
"OutputKey": "CircleCIUserName",
"OutputValue": "CIUser"
},
{
"OutputKey": "CircleCIUserArn",
"OutputValue": "arn:aws:iam::[accountID]:user/CIUser"
},
{
"OutputKey": "CircleCIRoleArn",
"OutputValue": "arn:aws:iam::[accountID]:role/CIRole"
}
]
]
4. CircleCIにて設定ファイルを作成する
.circleci/config.yml
を作成し、以下のようにデプロイのワークフローを構築する
Assume Roleする際に #3で作成したIAMユーザのCredentials情報をCircleCIの環境変数に設定する。
その後 ./assume.sh ${AWS_ASSUME_ROLE_ARN}
でAssumeRoleし、S3を操作することができるCredentialsを発行。
再度環境変数に設定した上でS3にビルドファイルを配置する。
#!/usr/bin/env bash
set -xeuo pipefail
DURATION_SECONDS="1800"
aws_sts_credentials="$(aws sts assume-role \
--role-arn $1 \
--role-session-name "circle-ci-session" \
--external-id "00001" \
--duration-seconds "$DURATION_SECONDS" \
--query "Credentials" \
--output "json")"
cat <<EOT > "$(dirname $0)/aws-envs.sh"
export AWS_ACCESS_KEY_ID="$(echo $aws_sts_credentials | jq -r '.AccessKeyId')"
export AWS_SECRET_ACCESS_KEY="$(echo $aws_sts_credentials | jq -r '.SecretAccessKey')"
export AWS_SESSION_TOKEN="$(echo $aws_sts_credentials | jq -r '.SessionToken')"
EOT
AssumeRoleした時のセッション時間はデフォルトで15分ですが上記のようにduration-secondsを指定することで時間を伸ばすことができます。
5. ホスティングされているURLへアクセスして確認
無事ワークフローがPassできれば実際にホスティングしたS3のURLにアクセスしてみます。以下のように表示されていればデプロイができていることが分かります。
以上で、AssumeRoleを用いてCircleCIからS3へデプロイすることができました。外部サービスを利用する上でのリスクを避けるためにもこの方法はとても良さそうです。また、AWSアカウントが複数になっても一つのUserでアカウントのAssumeRoleを行うこともできるのがとても便利に感じました。