CloudFormationテンプレート内でAWSアカウント名を取得したい

2023.10.30

CloudFormationで作るリソースの名前にAWSアカウント名を使いたいことがありました。

擬似パラメータには該当するものがなかったため、カスタムリソースを使ってCloudFormation内でAWSアカウント名を取得してみました。

カスタムリソース - AWS CloudFormation

カスタムリソースとは

カスタムリソースは、CloudFormationがネイティブにサポートしていないカスタム処理を可能にする機能です。

カスタム処理をLambdaで作成して、CloudFormation実行時にLambdaが実行されます。

カスタムリソースについては、以下を確認するとイメージがつくかと思います。

AWSアカウント名

AWS アカウント名は、AWS マネジメントコンソールのアカウント ID の横に表示される名前です。この名前は最初にアカウントを作成するときに選択するものですが、 AWS 請求管理コンソールから更新することができます。

AWSアカウントのエイリアスとは別物のため、注意してください。

アカウントエイリアスはIAMの画面で設定変更可能で、サインインURLになります。

AWSアカウント名はサインインURLとして使用されません。また、マネジメントコンソールから確認する際は、ルートユーザーでのログインが必要です。

今回は、AWSアカウント名の取得を行います。

やってみた

カスタムリソースのLambda上でAWS SDKを実行して、AWSアカウント名を取得します。

注意点

この方法では、AWS SDKでAWS OrganizationsのAPIを利用します。

AWS Organizationsが有効になっていない場合スタック作成に失敗するため、ご注意ください。

CloudFormationテンプレート

テンプレートは以下のとおりです。

Resources:
  GetAccountName:
    Type: Custom::GetAccountName
    Properties:
      ServiceToken: !GetAtt GetAccountNameLambda.Arn
  GetAccountNameLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          def lambda_handler(event, context):
              try:
                  # Get AWS account ID
                  sts = boto3.client('sts')
                  account_id = sts.get_caller_identity().get('Account')

                  # Get AWS account name
                  organizations = boto3.client('organizations')
                  response_value = organizations.describe_account(AccountId=account_id)
                  account_name = response_value['Account']['Name']

                  responseData = {
                      "AccountName": account_name,
                  }
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})
      Handler: index.lambda_handler
      Role: !GetAtt GetAccountNameLambdaRole.Arn
      Runtime: python3.11
  GetAccountNameLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: GetAccountName
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - sts:GetCallerIdentity
                  - organizations:DescribeAccount
                Resource: '*'
        - PolicyName: Logs
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
Outputs:
  AccountName:
    Value: !GetAtt GetAccountName.AccountName

GetAccountNameというカスタムリソースを定義しています。

カスタムリソースで利用するLambda関数やLambda関数用のIAMロールを定義しています。

Lambda関数の中身はシンプルで、以下の処理を行います。

  • AWS SDKを使ってAWSアカウントIDからAWSアカウント名を取得
  • cfn-response モジュール を使ってAWSアカウント名をカスタムリソースに送信

カスタムリソースに送信されたAWSアカウント名は、今回のコード上では!GetAtt GetAccountName.AccountNameで取得することができます。

動作確認

まずは現在のAWSアカウント名を確認します。

$ aws organizations describe-account --account-id 893022333407 --query Account.Name
<AWSアカウント名>

準備したテンプレートをデプロイしてみましょう。

aws cloudformation create-stack --stack-name GetAwsAccountTest --template-body file://templates.yaml \
--capabilities CAPABILITY_NAMED_IAM

デプロイが完了したら以下のコマンドでOutputを確認します。

AWSアカウント名の箇所が先程コマンドで確認したものと同じであることが確認できます。

$ aws cloudformation describe-stacks --stack-name GetAwsAccountTest --query 'Stacks[].Outputs'      
[
    [
        {
            "OutputKey": "AccountName",
            "OutputValue": "<AWSアカウント名>"
        }
    ]
]

おわりに

カスタムリソースを使って、AWSアカウント名を取得する方法でした。

今回はCloudFormaitonのどのステータスで実行されても問題ないため、リクエストタイプごとの処理を設定していませんが、必要に応じてリクエストタイプごとの処理も実装したほうがよさそうです。

以上、AWS事業本部の佐藤(@chari7311)でした。