AWS Control Tower でアカウント作成する際の AWS SSO への権限セット割り当てを自動化してみた

AWS Control Tower のアカウント発行イベントを利用して、AWS SSO 権限セットの自動割り当てをやってみました。
2021.10.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、大前です。

AWS Control Tower を利用してマルチアカウント環境を管理している場合は AWS SSO もセットで利用する事になりますが、Control Tower が自動的に割り当てる権限セットやグループの他に独自の権限セットやグループを割り当てたい場合、現状は AWS アカウントが追加されるたびに手動で実施する必要があります。

AWS アカウント毎に割り当てる権限が異なる部分については都度手動作業が必須とは思いますが、例えば全アカウントに対して閲覧権限をもつ権限セット/グループを適用したい場合などは、AWS アカウントが作成されたタイミングで自動で割り当て出来ると嬉しそうです。

アカウントが作成されたタイミングで任意の権限セットなどを割り当てる機能は現状ありませんが、AWS SSO には CreateAccountAssignment という権限セットやグループをアカウントに関連付ける為の API が用意されています。

また、Control Tower にはライフサイクルイベントというものがあり、これを利用する事で Control Tower のアクションによって発生するイベントを取得する事ができます。例えば、アカウント発行イベントを取得したい場合には CreateManagedAccount が発行されます。

上記より、AWS SSO の CreateAccountAssignment API と、Control Tower のライフサイクルイベントを利用する事で、アカウントが作成されたタイミングで任意の権限セット/グループの自動割り当てが実現できそうです。早速試していきます。

構成

今回は以下の構成を作成します。

イベントを取得して API を叩くだけですので、以前であればよくある EventBridge + Lambda 構成で実現する形になったかと思いますが、最近あった Step Functions のアップデートによって Step Functions から直接 AWS SSO の API を呼び出せる様になったので、これを活用した構成としています。

Lambda が不要になるので、API を呼び出す為のソースコードを書かなくて良いのは非常に嬉しいですね。

やってみた

事前に必要な情報の準備

AWS SSO の API を叩くために事前に確認しておく情報がいくつかあるので、先にそれらを確認・準備します。

AWS SSO インスタンス ARN

AWS SSO コンソール → 「設定」から確認出来るものをメモしておきます。

自動で割り当てたい AWS SSO グループ

自動で割り当てたい AWS SSO グループを作成し、グループ ID をメモしておきます。今回は「AssignmentTestGroup」というグループを作成しました。

自動で割り当てたい AWS SSO 権限セットの ARN

自動で割り当てたい AWS SSO 権限セットの ARN をメモしておきます。新しく作っても良いですが、今回は Control Tower を利用していると存在する「AWSReadOnlyAccess」を利用する事にしました。

Step Functions State Machine の作成

事前に必要な情報が揃ったら、Step Functions State Machine を作成していきます。

今回は以下定義の State Machine を作成しました。

{
  "Comment": "This is your state machine",
  "StartAt": "CreateAccountAssignment",
  "States": {
    "CreateAccountAssignment": {
      "Type": "Task",
      "End": true,
      "Parameters": {
        "InstanceArn": "<事前に確認した AWS SSO インスタンス ARN>",
        "PermissionSetArn": "<事前に確認した AWS SSO 権限セット ARN>",
        "PrincipalId": "<事前に確認したグループ ID>",
        "PrincipalType": "GROUP",
        "TargetId.$": "$.accountId",
        "TargetType": "AWS_ACCOUNT"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssoadmin:createAccountAssignment",
      "InputPath": "$.detail.serviceEventDetails.createManagedAccountStatus.account"
    }
  }
}

Parameters の値は公式の API リファレンスに従って値を指定していますので、特に問題なく埋められると思います。

ポイントをあげるとすると、Control Tower のアカウント発行イベントからアカウント ID を取得する必要があるため、InputPath と TargetId の指定だけ少し Step Functions 特有の指定を行なっています。詳細は Step Functions の公式ドキュメントを参照してもらえればと思いますが、基本的には発行されるイベントの JSON を辿ってアカウント ID を取得できる様に InputPath を設定しているだけとなります。

Control Tower によるアカウント発行イベントのサンプルを下記に置いておきますので、よければ参考ください。

CreateManagedAccount Sample Event
{
    "version": "0",
    "id": "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
    "detail-type": "AWS Service Event via CloudTrail",
    "source": "aws.controltower",
    "account": "000000000000",
    "time": "2021-09-17T07:27:23Z",
    "region": "ap-northeast-1",
    "resources": [],
    "detail": {
        "eventVersion": "1.08",
        "userIdentity": {
            "accountId": "000000000000",
            "invokedBy": "AWS Internal"
        },
        "eventTime": "2021-09-17T07:27:23Z",
        "eventSource": "controltower.amazonaws.com",
        "eventName": "CreateManagedAccount",
        "awsRegion": "ap-northeast-1",
        "sourceIPAddress": "AWS Internal",
        "userAgent": "AWS Internal",
        "requestParameters": null,
        "responseElements": null,
        "eventID": "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
        "readOnly": false,
        "eventType": "AwsServiceEvent",
        "managementEvent": true,
        "recipientAccountId": "000000000000",
        "serviceEventDetails": {
            "createManagedAccountStatus": {
                "organizationalUnit": {
                    "organizationalUnitName": "Sandbox",
                    "organizationalUnitId": "ou-xxxx-xxxxxxxx"
                },
                "account": {
                    "accountName": "event-test",
                    "accountId": "000000000000"
                },
                "state": "SUCCEEDED",
                "message": "AWS Control Tower successfully created an enrolled account.",
                "requestedTimestamp": "2021-09-17T07:15:13+0000",
                "completedTimestamp": "2021-09-17T07:27:23+0000"
            }
        },
        "eventCategory": "Management"
    }
}

Workflow Studio で確認できる全体像は以下の様になっています。API を 1つ呼ぶだけですので、シンプルな State Machine である事がわかります。

また、State Machine にアタッチする IAM Role には AWS SSO の CreateAccountAssignment API を呼び出す為の権限が必要です。以下にサンプルのポリシーを記載しますので、ご参考までに。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sso:CreateAccountAssignment"
            ],
            "Resource": [
                "<事前に確認した AWS SSO 権限セット ARN>",
                "arn:aws:sso:::account/*",
                "<事前に確認した AWS SSO インスタンス ARN>"
            ],
            "Effect": "Allow"
        }
    ]
}

EventBridge Rule の作成

続いて、作成した Step Functions State Machine を呼び出す為の EventBridge Rule を作成します。

"CreateManagedAccount" をキャッチできれば良いので、イベントパターンとしては以下を定義します。

{
  "source": ["aws.controltower"],
  "detail-type": ["AWS Service Event via CloudTrail"],
  "detail": {
    "eventName": ["CreateManagedAccount"]
  }
}

ターゲットには上記で作成した Step Functions State Machine を指定します。また、State Machine を呼び出す為の IAM Role が必要ですので、サンプルのポリシーを置いておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "states:StartExecution"
            ],
            "Resource": [
                "<作成した State Machine の ARN>"
            ],
            "Effect": "Allow"
        }
    ]
}

試してみる

ここまで設定できたら準備完了です。実際にアカウントを Control Tower アカウントファクトリーから作成し、動作を確認してみましょう。

私は以前ブログを書いた際に EventBridge にイベント記録していた CreateManagedAccount イベントがあったので、これを再生する形で検証しました。

イベント発行後、意図した通りに権限セットとグループが紐づいていれば OK です。

おわりに

AWS Control Tower でアカウントを作成する時に AWS SSO の権限セットとグループを自動割り当てする仕組みを作って試してみました。Step Functions のアップデートもあり、コード管理不要で実現できる為導入しやすいと思いますので是非活用していただけれ幸いです。

以上、AWS 事業本部の大前でした。

付録

今回構築した環境をデプロイする CloudFormation を置いておきます。あくまでご参考までに。

sso-automation-sample.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "A template for Automation of SSO Create Assignment"
Parameters:
  # 作成リソースに付与する接頭語
  Prefix:
    Description: "Prefix of each resource"
    Type: "String"
    Default: "test"
  # AWS SSO のインスタンスARN ※基本的に変更しない
  InstanceArn:
    Description: "InstanceArn of SSO"
    Type: "String"
    Default: "xxxx"
  # 作成されたアカウントに自動割当を行う権限セットの ARN
  PermissionSetArn:
    Description: "PermissionSetArn of assignment target"
    Type: "String"
    Default: "xxxx"
  # 作成されたアカウントに自動割当を行う AWS SSO グループの ID
  PrincipalId:
    Description: "PrincipalId of assignment target"
    Type: "String"
    Default: "xxxx"
  # 自動割当を行う種類を指定(USER|GROUP)
  PrincipalType:
    Description: "PrincipalType of assignment target"
    Type: "String"
    Default: "GROUP"
  # 自動割当を行う対象を指定(作成時点で AWS_ACCOUNT 固定)
  TargetType:
    Description: "TargetType of assignment target"
    Type: "String"
    Default: "AWS_ACCOUNT"

Resources:
  # Step Functions StateMachine
  StateMachine:
    Type: "AWS::StepFunctions::StateMachine"
    Properties:
      StateMachineName: !Sub "${Prefix}-sso-create-account-assignment-statemachine"
      DefinitionString: !Sub |
        {
          "Comment": "This is your state machine",
          "StartAt": "CreateAccountAssignment",
          "States": {
            "CreateAccountAssignment": {
              "Type": "Task",
              "End": true,
              "Parameters": {
                "InstanceArn": "${InstanceArn}",
                "PermissionSetArn": "${PermissionSetArn}",
                "PrincipalId": "${PrincipalId}",
                "PrincipalType": "${PrincipalType}",
                "TargetId.$": "$.accountId",
                "TargetType": "${TargetType}"
              },
              "Resource": "arn:aws:states:::aws-sdk:ssoadmin:createAccountAssignment",
              "InputPath": "$.detail.serviceEventDetails.createManagedAccountStatus.account"
            }
          }
        }
      RoleArn: !GetAtt StateMachineRole.Arn
      Tags:
        -
          Key: "Name"
          Value: !Sub "${Prefix}-sso-create-account-assignment-statemachine"
  ## IAM Role for StateMachie
  StateMachineRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${Prefix}-sso-create-account-assignment-statemachine-role"
      Path: "/service-role/"
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "states.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref CreateAccountAssignmentPolicy
  ## IAM Policy for CreateAccountAssignment
  CreateAccountAssignmentPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      ManagedPolicyName: !Sub "${Prefix}-sso-create-account-assignment-policy"
      Path: "/service-role/"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Resource:
              - !Ref PermissionSetArn
              - "arn:aws:sso:::account/*"
              - !Ref InstanceArn
            Effect: "Allow"
            Action:
              - "sso:CreateAccountAssignment"
  # EventRule for CreateManagedAccount
  EventRule:
    Type: "AWS::Events::Rule"
    Properties:
      Description: "Send CreateManagedAccount event to StateMachine"
      EventPattern: |-
        {
          "source": ["aws.controltower"],
          "detail-type": ["AWS Service Event via CloudTrail"],
          "detail": {
            "eventName": ["CreateManagedAccount"]
          }
        }
      Name: !Sub "${Prefix}-catch-CreateManagedAccount"
      State: "ENABLED"
      Targets:
        - Arn: !Ref StateMachine
          Id: !Sub "${Prefix}-target-sso-create-account-assignment-statemachine"
          RoleArn: !GetAtt EventBridgeRole.Arn
  ## IAM Role for EventBridge
  EventBridgeRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${Prefix}-catch-CreateManagedAccount-role"
      Path: "/service-role/"
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "events.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref InvokeStepFunctionsPolicy
  ## IAM Policy to invoke Step Functions
  InvokeStepFunctionsPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      ManagedPolicyName: !Sub "${Prefix}-invoke-step-functions-from-eventbridge-policy"
      Path: "/service-role/"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Resource:
              - !Ref StateMachine
            Effect: "Allow"
            Action:
              - "states:StartExecution"

参考