[AssumeRole] アクセスキーが漏洩しても被害が最小限になるIAMユーザでCloudFormationにデプロイする方法
AWSアクセスキーセキュリティ意識向上委員会って何?
昨今、AWSのアクセスキーを漏洩させてしまうことが原因でアカウントへの侵入を受け、 多額の利用費発生・情報漏洩疑いなど重大なセキュリティ事案が発生するケースが実際に多々起きています。
そこで、アクセスキー運用に関する安全向上の取組みをブログでご紹介する企画をはじめました。
アクセスキーを利用する場合は利用する上でのリスクを正しく理解し、 セキュリティ対策を事前に適用した上で適切にご利用ください。
IAMユーザーのアクセスキー漏洩を少しでも防ぎたい
手元のパソコンやCircleCIなどを使ってAWS(CloudFormation等)にデプロイするとき、強い権限(Create系など)が必要です。
その権限を保持するIAMユーザのアクセスキー (アクセスキーID & シークレットアクセスキー) が漏れてしまったら?と心配する方は多いと思います。 実際に下記のような例も存在します。
そこで、「デプロイ用のIAMユーザ」を少しでも安全に扱う方法を試してみました。 「強い権限を持つIAMユーザのアクセスキーをそのまま使ってデプロイしている状態」をやめる第一歩になればと思います。
目次
- 全体像
- 実際にやってみる
- さいごに
- 参考
全体像
下記の2つを組み合わせます。
- AssumeRole
- CloudFormation用のIAMロール
ポイントは下記です。
- IAMユーザ自身には、AssumeRoleができる権限しか持たせない
- デプロイ用のIAMロールにAssumeRoleして、一時的なアクセスキーを入手する
- 一時的なアクセスキーを用いて、CloudFormationにデプロイする
- デプロイ時にCloudFormationが用いるIAMロールを指定する
AssumeRoleとは?
すごく簡単に表現すれば、「特定のRole(に紐づく権限)を一時的に引き受ける(Assume)」です。
AssumeRoleを活用すると、本来持っていない権限を一時的に得られるため、「デプロイ時のみ特定の権限を得る」ことができます。
今回はデプロイに特化していますが、「複数のAWSアカウントのログイン管理を集約」することもできます。 (ログイン用のIAMユーザを作成し、実際の権限はIAMロールで管理し、ログイン後にIAMロールを切り替えて作業する、など)
CloudFormation用のIAM Roleとは?
CloudFormation操作時に権限が無くても、特定のRole(に紐づく権限)をCloudFormation自体に与えることができます。
これにより、たとえば「Lambdaに対する権限がゼロ」でも、「CloudFormationに与えるロールにLambdaFullAccessの権限がある」なら、Lambdaのデプロイが可能になります。
実際にやってみる
今回は「同じAWSアカウント」にデプロイする想定ですが、「異なるAWSアカウント」にデプロイする場合も、「信頼するAWSアカウントID」を任意に変更し、各種調整をすればOKです。
IAM関連で作成するもの
下記を作成します。
AWS | 名前 | 役割 |
---|---|---|
IAM User | deploy-iam-sample-user | デプロイ用のIAMユーザ |
IAM Poricy | deploy-iam-sample-user-policy | IAMユーザにAssumeRole権限を持つ |
IAM Role | deploy-iam-sample-deploy-role-for-user | CloudFormationとS3に対する権限を持つ |
IAM Role | deploy-iam-sample-deploy-role-for-cloudformation | AWSの各サービスに対する権限を持つ |
IAMユーザ & IAMロールを作成
デプロイ用のIAMユーザと付与するIAMポリシーについて
このユーザ自身に与える権限は、AssumeRoleできる権限のみです。 そのため、万が一このIAMユーザのアクセスキーが流出しても、流出したアクセスキーでは実質何もできません。
デプロイ用のIAMユーザがAssumeRoleするIAMロールについて
「デプロイ用のIAMユーザ」がAssumeRoleする(引き受ける)「IAMロール」です。 今回作成するIAMロールには下記の権限を付与しますが、必要に応じて変更してください。
- S3バケットの作成権限
- S3オブジェクトの作成権限
- お試しデプロイとしてAWS SAMを使うため、S3の権限も付与(Lambdaコードのアップロードをするため)
- CloudFormationのデプロイ準備に必要な権限
- 「CloudFormation用のIAMロール」は
cloudformation:ExecuteChangeSet
実行時に使われるため、スタックやチェンジセットの作成権限はこのIAMロールに必要
- 「CloudFormation用のIAMロール」は
- CloudFormationにIAMロールを渡す権限
また、信頼する相手として「対象となるIAMユーザのAWSアカウント」を指定します。
CloudFormation用のIAMロールについて
CloudFormationが実際のデプロイ(LambdaやDynamoDBの作成等)で使用する権限を付与します。 この権限自体はIAMユーザに紐付いておらず、信頼する相手として「CloudFormation」を指定します。
実際に作成する
iam.yml
を作成します。SamAppStackName
やBucketName
は実際に使用する値をデフォルト値として設定していますが、コマンド実行時に外部から渡すのもアリですね。
※2019/9/10 20:30更新:FullAccess系の権限を使わないようにしました(CloudFormation用のIAMロールを除く)
AWSTemplateFormatVersion: 2010-09-09 Description: Create IAM User and Role for deploy Parameters: SamAppStackName: Type: String Default: "Deploy-Iam-Sample-SAM-App" BucketName: Type: String Default: "cm-fujii.genki-sam-app-sample-bucket" Resources: # デプロイ用のIAMユーザ DeployUser: Type: AWS::IAM::User Properties: UserName: "deploy-iam-sample-user" # デプロイ用のIAMユーザに付与するIAMポリシー(AssumeRoleできる) DeployUserPoricy: Type: AWS::IAM::Policy Properties: PolicyName: "deploy-iam-sample-user-policy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Resource: !GetAtt DeployRoleForUser.Arn Users: - !Ref DeployUser # デプロイ用のIAMユーザがAssumeRoleするIAMロール(CloudFormationとS3に対する権限) DeployRoleForUser: Type: AWS::IAM::Role Properties: RoleName: "deploy-iam-sample-deploy-role-for-user" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: AWS: - !GetAtt DeployUser.Arn Condition: StringEquals: sts:ExternalId: "any-id-hoge-fuga" Policies: - PolicyName: "deploy-iam-sample-deploy-policy-for-user" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "cloudformation:CreateStack" - "cloudformation:CreateChangeSet" - "cloudformation:DeleteChangeSet" - "cloudformation:DescribeChangeSet" - "cloudformation:DescribeStacks" - "cloudformation:ExecuteChangeSet" Resource: - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${SamAppStackName}/*" - Effect: "Allow" Action: - "s3:CreateBucket" Resource: - !Sub "arn:aws:s3:::${BucketName}" - Effect: "Allow" Action: - "s3:PutObject" Resource: - !Sub "arn:aws:s3:::${BucketName}/*" - Effect: "Allow" Action: - "iam:PassRole" Resource: - !GetAtt DeployRoleForCloudFormation.Arn MaxSessionDuration: 3600 # CloudFormation用のIAMロール(AWS各サービスに対する権限) DeployRoleForCloudFormation: Type: AWS::IAM::Role Properties: RoleName: "deploy-iam-sample-deploy-role-for-cloudformation" AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: - "cloudformation.amazonaws.com" # 実際にデプロイする際に必要な権限 ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSCloudFormationFullAccess - arn:aws:iam::aws:policy/AWSLambdaFullAccess - arn:aws:iam::aws:policy/IAMFullAccess - arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator MaxSessionDuration: 3600 Outputs: OutputDeployUser: Description: "IAM User for Deploy" Value: !GetAtt DeployUser.Arn OutputDeployRoleForUser: Description: "IAM Role (AssumeRole) for Deploy User" Value: !GetAtt DeployRoleForUser.Arn OutputDeployRoleForCloudFormation: Description: "IAM Role (AssumeRole) for Deploy CloudFormation" Value: !GetAtt DeployRoleForCloudFormation.Arn
上記のYAMLを作成してデプロイします。この操作自体は、作成権限のあるアカウントで最初に実行します。
$ aws cloudformation deploy \ --template-file iam.yml \ --stack-name Deploy-Iam-Sample-Iam \ --capabilities CAPABILITY_NAMED_IAM
作成したIAMロールのARNが必要になるため、取得しておきます。(下記のOutputValue
です)
$ aws cloudformation describe-stacks \ --stack-name Deploy-Iam-Sample-Iam \ --query 'Stacks[].Outputs' [ [ { "OutputKey": "OutputDeployUser", "OutputValue": "arn:aws:iam::1234567890:user/deploy-iam-sample-user", "Description": "IAM User for Deploy" }, { "OutputKey": "OutputDeployRoleForCloudFormation", "OutputValue": "arn:aws:iam::1234567890:role/deploy-iam-sample-deploy-role-for-cloudformation", "Description": "IAM Role (AssumeRole) for Deploy CloudFormation" }, { "OutputKey": "OutputDeployRoleForUser", "OutputValue": "arn:aws:iam::1234567890:role/deploy-iam-sample-deploy-role-for-user", "Description": "IAM Role (AssumeRole) for Deploy User" } ] ]
デプロイ用のIAMユーザのアクセスキーを取得
下記コマンドで取得します。「デプロイ用のIAMユーザ」に切り替える際に使用します。
$ aws iam create-access-key \ --user-name deploy-iam-sample-user { "AccessKey": { "UserName": "deploy-iam-sample-user", "AccessKeyId": "aaaaaa", "SecretAccessKey": "bbbbbb" } }
デプロイ対象(サーバーレスなアプリ)の準備
AWS SAMでデフォルト作成されるLambdaアプリケーションをそのままデプロイします。
デプロイ時に--role-arn
オプションでIAMロールを指定できます。
CloudFormation(AWS CLI)やAWS CDKでも--role-arn
オプションが同じように使えます。
- create-stack — AWS CLI 1.16.231 Command Reference
- AWS CDK Tools - AWS Cloud Development Kit (AWS CDK)
なお、--role-arn
で指定したIAMロールは、cloudformation:ExecuteChangeSet
実行時に使用されます。
--role-arn (string) The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that AWS CloudFormation assumes when executing the change set. deploy — AWS CLI 1.16.234 Command Reference
AWS SAMプロジェクトを作成
下記コマンドで作成します。
sam init --runtime python3.7 --name AppSample
デプロイ用のIAMユーザに切り替える
あらかじめ取得しておいたアクセスキーを使用し、「デプロイ用のIAMユーザ」に切り替えます。 CircleCIなどでデプロイする場合は、「デプロイ用のIAMユーザのアクセスキー」を使用して以降の流れを再現すればOKですね。
export AWS_ACCESS_KEY_ID=aaaaaa export AWS_SECRET_ACCESS_KEY=bbbbbb export AWS_DEFAULT_REGION=ap-northeast-1
試しにこの状態でCloudFormationのスタック一覧やLambda関数一覧を取得しようとしても、権限が無いため失敗します。
$ aws cloudformation list-stacks An error occurred (AccessDenied) when calling the ListStacks operation: User: arn:aws:iam::1234567890:user/deploy-iam-sample-user is not authorized to perform: cloudformation:ListStacks $ aws lambda list-functions An error occurred (AccessDeniedException) when calling the ListFunctions operation: User: arn:aws:iam::1234567890:user/deploy-iam-sample-user is not authorized to perform: lambda:ListFunctions on resource: *
AssumeRoleする
まずは普通にAssumeRoleする
下記コマンドでAssumeRoleを行い、「デプロイ用のIAMユーザがAssumeRoleするIAMロール」の一時アクセスキーを取得します。1234567890
部分は、各自のAWSアカウントIDを使用します。
$ aws sts assume-role \ --role-arn arn:aws:iam::1234567890:role/deploy-iam-sample-deploy-role-for-user \ --role-session-name deploy-test \ --duration-seconds 900 \ --external-id any-id-hoge-fuga { "Credentials": { "AccessKeyId": "xxxxxx", "SecretAccessKey": "yyyyyy", "SessionToken": "zzzzzz", "Expiration": "2019-09-06T12:31:37Z" }, "AssumedRoleUser": { "AssumedRoleId": "hoge:deploy-test", "Arn": "arn:aws:sts::1234567890:assumed-role/deploy-iam-sample-deploy-role-for-user/deploy-test" } }
下記コマンドでアクセスキーを切り替える(上書きする)ことで、「デプロイ用のIAMユーザがAssumeRoleするIAMロール」として、AWSにアクセスできるようになります。
export AWS_ACCESS_KEY_ID=xxxxxx export AWS_SECRET_ACCESS_KEY=yyyyyy export AWS_SESSION_TOKEN=zzzzzz
めんどくさいのでスクリプトを作る
AssumeRoleする作業がめんどくさいですし、CircleCI等で実行する場合は上記のように手動で作業はできません。
そこで下記のスクリプト(assume_role.sh
)を作成します。
#!/usr/bin/env bash set -xeuo pipefail aws_sts_credentials="$(aws sts assume-role \ --role-arn "$AWS_DEPLOY_IAM_ROLE_ARN" \ --role-session-name "$ROLE_SESSION_NAME" \ --external-id "$AWS_DEPLOY_IAM_ROLE_EXTERNAL_ID" \ --duration-seconds 900 \ --query "Credentials" \ --output "json")" cat <<EOT > "aws-env.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
jq
が無い場合は、インストールします。(下記はMacでインストールする例)
$ brew install jq
あらかじめAssumeRoleするIAMロール名などを環境変数にセットしておきます。
export AWS_DEPLOY_IAM_ROLE_ARN=arn:aws:iam::1234567890:role/deploy-iam-sample-deploy-role-for-user export ROLE_SESSION_NAME=deploy-test export AWS_DEPLOY_IAM_ROLE_EXTERNAL_ID=any-id-hoge-fuga
続いてassume_role.sh
を実行し、生成されたaws_env.sh
を読み込めばOKです。
./assume_role.sh source aws-env.sh
デプロイする
S3バケットの作成
コード等を格納するためのS3バケットを作成します。作成済みの場合は飛ばします。
aws s3 mb s3://cm-fujii.genki-sam-app-sample-bucket
build
下記でビルドします。
sam build
package
コード一式をS3バケットにアップロードします。
sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-sam-app-sample-bucket
deploy
デプロイします。このとき、--role-arn
オプションで「CloudFormation用のIAMロール」を指定します。
sam deploy \ --template-file packaged.yaml \ --stack-name Deploy-Iam-Sample-SAM-App \ --capabilities CAPABILITY_IAM \ --role-arn arn:aws:iam::1234567890:role/deploy-iam-sample-deploy-role-for-cloudformation
ちなみに、--role-arn
オプションを付けない場合は、「IAMロールの作成ができない(権限が無い)」ので失敗します。
API: iam:CreateRole User: arn:aws:sts::1234567890:assumed-role/deploy-iam-sample-deploy-role-for-user/deploy-test is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::1234567890:role/Deploy-Iam-Sample-SAM-App-HelloWorldFunctionRole-USD7R9OSWGWH
動作確認
API Gatewayのアドレスを取得する
$ aws cloudformation describe-stacks \ --stack-name Deploy-Iam-Sample-SAM-App \ --query 'Stacks[].Outputs' [ [ { "OutputKey": "HelloWorldApi", "OutputValue": "https://ttttt.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/", "Description": "API Gateway endpoint URL for Prod stage for Hello World function" } ] ]
WebAPIを叩く
HelloWorldApi
を元に叩きます。
$ curl https://ttttt.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message": "hello world"}
動いてますね!!
さいごに
権限をさらに小さくしたり、細かい調整は必要になると思いますが、「強い権限を持つIAMユーザのアクセスキーをそのまま使ってデプロイしている状態」をやめる第一歩になればと思います。
個人的には、CircleCIと組み合わせて使ってみる予定です。
参考
- AWS SAM/CircleCI/LocalStackを利用した実践的なCI/CD – ClassmethodサーバーレスAdvent Calendar 2017 #serverless #adventcalendar #reinvent | DevelopersIO
- IAMロール徹底理解 〜 AssumeRoleの正体 | DevelopersIO
- いつの間にかCloudFormationがIAM Roleに対応していました! | DevelopersIO
- 【Tips】Admin権限を持っていないIAMユーザでIAM Roleを設定するためのポリシー設定 | DevelopersIO
- 複数AWSアカウントのユーザ管理を、ログイン用AWS環境に集約してみた | DevelopersIO
- AWS リソースへのアクセス権を第三者に付与するときに外部 ID を使用する方法 - AWS Identity and Access Management
- AWS JSON ポリシーの要素:Principal - AWS Identity and Access Management
- 環境変数 - AWS Command Line Interface
- assume-role — AWS CLI Command Reference
- AWS サービスにロールを渡すアクセス許可をユーザーに許可する - AWS Identity and Access Management