この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、森田です。
こちらの記事では、タイトルの通り別アカウントからのCloudFormationでS3バケットの作成を行います。
カスタムリソースの Lambdaに対して権限委任を行い、スタック作成・削除に伴いS3バケットの作成・削除をBoto3を用いて行います。
やりたいこと
AWSアカウントA のCloudFormationから AWSアカウントB でS3バケットの作成を行います。
上記を実現させるために、AWSアカウントA のロールに権限委任を行います。
構成は以下のようになります。
上記を実現させるため、
- カスタムリソースのLambdaのロール作成
- AWSアカウントAへ権限委任するロール作成
- カスタムリソースのLambdaからS3を作成する
のCloudFormationテンプレートを作成します。
やってみる
CloudFormationテンプレートの作成
カスタムリソースのLambdaのロール作成
custom_resource_role.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
RoleName:
Type: String
Description: RoleName
Default: "AssumeRole-Lambda"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref RoleName
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub ${RoleName}-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource: "*"
Outputs:
SwitchLambdaRole:
Value: !GetAtt LambdaExecutionRole.Arn
Export:
Name: SwitchLambdaRoleARN
AWSアカウントAへ権限委任するロール作成
assume_role.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
RoleName:
Type: String
Description: RoleName
Default: "AssumeRole-Lambda"
AccountNumber:
Type: String
Description: External AWS Account ID
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref RoleName
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${AccountNumber}:role/${RoleName}
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub ${RoleName}-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:CreateBucket
- s3:DeleteBucket
Resource: "*"
カスタムリソースのLambdaからS3を作成する
cfn.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
BucketName:
Type: String
Description: Create Bucket Name
RoleName:
Type: String
Description: External AWS Account Role
Default: "AssumeRole-Lambda"
AccountNumber:
Type: String
Description: External AWS Account ID
Resources:
SwitchHandler:
Type: Custom::SwitchHandler
Properties:
ServiceToken: !GetAtt "LambdaFunction.Arn"
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName : !Join ['-', [!Sub '${AWS::StackName}-Lambda', !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]]]]
Role: !ImportValue SwitchLambdaRoleARN
Runtime: "python3.8"
Handler: index.lambda_handler
Timeout: "300"
Environment:
Variables:
BucketName : !Ref BucketName
RoleName: !Ref RoleName
AccountNumber: !Ref AccountNumber
Code:
ZipFile: |
import cfnresponse
import sys
import os
import boto3
import time
from boto3.session import Session
def assume_role(account_number, role_name):
sts_client = boto3.client('sts')
my_info = sts_client.get_caller_identity()
partition = my_info['Arn'].split(":")[1]
role_arn = 'arn:{}:iam::{}:role/{}'.format(partition, account_number, role_name)
response = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName='Session-CFn-User',
)
session = boto3.Session(
aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken']
)
return session
def create_bucket(session, bucket_name):
s3_client = session.client("s3")
s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'})
def delete_bucket(session, bucket_name):
s3_client = session.client("s3")
s3_client.delete_bucket(Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'})
def lambda_handler(event, context):
if 'RequestType' not in event:
return "Overwrite"
if event['RequestType'] == 'Create':
bucket_name = os.getenv('BucketName')
account_number = os.getenv('AccountNumber')
role_name = os.getenv('RoleName')
session = assume_role(account_number, role_name)
create_bucket(session, bucket_name)
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
if event['RequestType'] == 'Delete':
bucket_name = os.getenv('BucketName')
account_number = os.getenv('AccountNumber')
role_name = os.getenv('RoleName')
session = assume_role(account_number, role_name)
delete_bucket(session, bucket_name)
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
if event['RequestType'] == 'Update':
print('Update')
cfnresponse.send(event, context, cfnresponse.SUCCESS,
{'Response': 'Success'})
CloudFormationの実行・スタック作成
まずは、以下のようにカスタムリソースのLambdaのロール作成の CloudFormation を AWSアカウントA で行います。
続いて、AWSアカウントBでAWSアカウントAへ権限委任するロール作成の CloudFormation を実行します。
そして、最後に AWSアカウントA でCloudFormationを実行し、カスタムリソースのLambdaからS3を作成を行います。
スタック作成後、アカウントBで作成したS3バケットが確認できます。
S3バケットの削除も確認するため、スタックの削除を行います。
スタック削除後、アカウントBからS3バケットが削除されていることが確認できます。
最後に
今回は、CloudFormationのカスタムリソースで別のアカウントのリソース(S3バケット)の操作を行いました。
1つ2つくらいのアカウントでのスタック作成であれば、スイッチロールをしての操作とかでも良いですが、それ以上のアカウントでスタックを作成する場合にはロールの切り替えは少々面倒です。
今回のように事前にロールを作成しておき、カスタムリソースでAssumeRoleするとロール切り替えがなくなるので、作業の簡略化ができそうです。