AWSのControl Towerマスターアカウントからセルフマネージド型StackSetsを展開してみた

# 追記（2021.1.23）

　本記事ではStackSetsのセルフサービスアクセス許可を利用する際に管理者・ターゲットアカウントにIAMロール・ポリシーを作成していますが、Control Towerにより作成されているIAMロールにより代用できることに気がつきました。
　そのため、管理者・ターゲットアカウントで新規にIAMロール・ポリシーを作成する必要はありません。

　Control Towerにより作成されているロールは、管理者アカウントに`AWSControlTowerStackSetRole`、Control Towerにより作成されたアカウントに`AWSControlTowerExecution`があります。
　ポリシーと信頼関係は以下の通りです。

　`AWSControlTowerStackSetRole`は、`AWSControlTowerStackSetRolePolicy`という管理ポリシーがアタッチされ、CloudFormationに対して信頼関係があります。
　この記事で管理者アカウントで作成したIAMロールとの違いは、スイッチロールできるロールの名前だけで、実質的な権限は一緒です。

```json:AWSControlTowerStackSetRole
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/AWSControlTowerExecution"
            ],
            "Effect": "Allow"
        }
    ]
}

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudformation.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

　`AWSControlTowerExecution`は、インラインポリシーで管理者権限が与えられ、Contorl Towerの管理アカウントに対して信頼関係があります。
　この記事のターゲットアカウントで作成したIAMロールとの違いは、Control Towerで作成したIAMロールは管理者権限があることです。
　強力な管理者権限が設定されているとはいえ、Control Towerで既に作成されているものですし、プリンシパルを管理者アカウントに指定しているので、こちらを使用しても問題ないと思います。

```json:AWSControlTowerExecution
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::管理者アカウントID:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
　StackSetの作成時のIAMロールの指定は以下のように行い、無事に作成されました。

![スクリーンショット 2021-01-23 8.58.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/f575d381-00d2-c137-7022-a311fa055357.png)


# 構成

　前回、[AWSのControl Towerマスターアカウントからサービスマネージド型StackSetsを展開してみた](https://qiita.com/kazu_kazu/items/51f97632d1dc40c314e5)の記事を作成しましたが、いくつかデメリットがあったのでセルフマネージド型StackSetsバージョンとしてこちらの記事を執筆します。
　今回の構成はこちら。

![StackSetsSelfService.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/fdb3400e-ec9d-8f98-c0a5-bb8c90b57675.png)

　アカウント払い出し時に、BudgetsとSNSをアカウントに展開した上で払い出します。
　セルフマネージド型StackSetsで、OUではなくアカウント指定でBudgets,SNSを展開します。
　セルフマネージド型StackSetsを使用するには、以下のロールが必要です。

|アカウント|ロール|
|---|---|
|管理アカウント|AWSCloudFormationStackSetAdministrationRole|
|各ターゲットアカウント|AWSCloudFormationStackSetExecutionRole|

　StackSetsの展開先が増える度に、ターゲットアカウントにサインインしてIAMロールを作成するのは面倒なので、各ターゲットアカウントの展開は親アカウントからサービスマネージド型StackSetsで展開します。

|StackSets|展開するサービス|
|---|---|
|サービスマネージド型|IAMロール、ポリシー|
|セルフマネージド型|Budgets,SNS|

　セルフマネージド型のStackSetsを展開するために、事前にサービスマネージド型のStackSetsでIAMロールを展開します。

# なぜBudgets等をセルフマネージド型で作成するのか

　冒頭でも述べましたが、サービスマネージド型で展開すると今回の場合ではデメリットの方が大きいと判断したからです。
　なお、サービスマネージド型に一般的な欠陥があると言うわけではありません。あくまで私がしたいことと合わなかっただけです。

　デメリットは二つあり、
　　　・無駄なサブスクリプションの確認が生じる
　　　・メールアドレス変更時に一から設定をし直す必要がある
ことです。

　構成図の通り、各アカウントのBudgets等はアカウントごとに設定が異なります。
　そのため、サービスマネージド型でターゲットをOUで指定すると、OU内のアカウント全てにデフォルトのメールアドレスをエンドポイントとしたSNSが作成されます。
　また、OU内にアカウントが追加されると自動的にStackSetsを展開する自動デプロイ機能も有効化するので、そのときもデフォルトのメールアドレスをエンドポイントとしたSNSが作成されます。

　SNSが作成されると、SNSサブスクリプションの確認メールが通知先に飛びます。
　リンクをクリックするだけの作業ですが、できればしたくありません。
　デフォルトのメールアドレスは実際の通知先として使わないからです。
　後からアカウントごとに通知先を変更します。
　使用しないサブスクリプションの確認メールをクリックすること自体は意味がありません。
　これが一つ目のデメリットです。

　二つ目のデメリットがセルフサービス型に決めた理由です。
　諸事情によりデフォルトのメールアドレスを更新する場合、ターゲットOU内の全アカウントのSNS通知先が、変更した新しいメールアドレスになるからです。
　当然各アカウントが通知したいメールアドレスは新しいメールアドレスではないので、再度設定を上書きし直す必要があります。
　結構な労力になるので、これを避けるためにセルフサービス型で展開します。

　なお、なぜ実際に通知先として使用しない無駄なデフォルトのメールアドレスを設定していたかと言うと、各アカウントで同じ内容のSNSを作成しないからです。
　StackSetsはOU単位で展開するので、複数アカウントで展開します。
　Aアカウントで実際に通知先として使用するメールアドレスで展開しても、BアカウントやCアカウントでは通知先として使用しないので、BやCにとっては後で変更する無駄な通知先となります。
　どのメールアドレスを使用しても、いずれかのアカウントでは無駄な通知先となるので、自分が使用するメールアドレスをデフォルトのメールアドレスとしていました。

# 管理者アカウントでIAMロール作成

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/27772f10-8a3e-a34b-b773-668bb0170ad0.png)

　StackSetsがIAMロールを利用するので、信頼関係はCloudFormationに対して行います。

![スクリーンショット 2021-01-20 19.09.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/6279dbc0-3180-6811-188f-58046fcfdc06.png)

　ターゲット先アカウントで作成したIAMロールを引き受けられるポリシーを作成します。

```JSON:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/AWSCloudFormationStackSetExecutionRole"
            ],
            "Effect": "Allow"
        }
    ]
}
```

![スクリーンショット 2021-01-20 19.10.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/8f71933d-783a-5bde-1938-8f50ee08b9e9.png)

　ポリシーに適当な名前をつけます。

![スクリーンショット 2021-01-20 19.11.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/9aafee5b-b5d6-2a0e-71bb-d13089981757.png)

　作成したポリシーをロールに割り当てます。

![スクリーンショット 2021-01-20 19.11.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/00bc7752-ee3b-6e73-a878-e03bbf5e3509.png)

　ロールの名前を間違えないようにつけます。
　`AWSCloudFormationStackSetAdministrationRole`です。
　管理アカウントでIAMロール作成完了です。


# ターゲットアカウントでIAMロール作成

![スクリーンショット 2021-01-20 19.15.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/1e6c12ca-a0d3-c064-65c3-e0b74489101b.png)

　ターゲットアカウントで、管理アカウントを信頼するIAMロールを作成します。

![スクリーンショット 2021-01-20 19.17.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/3331df19-8e1e-0123-3b59-07a5b0f9b7a3.png)

　ポリシーは最低限のポリシーを作成します。

![スクリーンショット 2021-01-20 19.18.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/a93175cf-f4a0-6f3c-8381-8c1f01006a46.png)

　ポリシーに適当な名前をつけます。

![スクリーンショット 2021-01-20 19.18.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/0cf53bf8-f8f3-ad35-03c5-8202966f8189.png)

　作成したポリシーをロールにアタッチします。
　あとはIAMロールの名前を間違えずに設定しましょう。
　`AWSCloudFormationStackSetExecutionRole`です。

# セルフマネージド型StackSetsでBudgets,SNS展開

　まず、StackSets用にテンプレートをS3にアップロードします。
　いつもは事前にS3にアップロードしませんが、気まぐれでやってみます。

![スクリーンショット 2021-01-20 19.23.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/b218f37e-e487-c6e3-ce18-3f6fd86923a8.png)

![スクリーンショット 2021-01-20 19.22.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/d865b24a-6b72-eb60-b580-faa21fea7e6a.png)

![スクリーンショット 2021-01-20 19.25.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/a57933b1-fcf9-c5ce-8c9b-11820d4a58c7.png)

　S3のアップロード完了しました。
　StackSetsを作成します。

![スクリーンショット 2021-01-20 19.26.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/2a297c97-d8bd-d482-d981-df21ce3c263e.png)

![スクリーンショット 2021-01-20 19.27.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/4926b057-b846-2978-58c8-4818c93eb5fc.png)

　取り合えず予算額は100ドルにします。

![スクリーンショット 2021-01-20 19.27.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/df0a8e45-6f77-1138-20a3-639cabcfe19c.png)

　セルフサービスのアクセス許可から、IAMロールを選択し、実行ロール名はデフォルトのままにします。

![スクリーンショット 2021-01-20 19.28.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/0d8481e4-c532-9f53-d7d4-c17f76654f53.png)

　先ほどIAMロールを作成したターゲットアカウントを指定します。
　StackSetsでBudgets、SNSを展開しました。
　成功しましたが、ターゲットアカウントで展開されているか確認します。

![スクリーンショット 2021-01-20 19.32.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/a8ae1d22-7ab6-a606-c789-74a4b1e6f137.png)

　SNSは作成されているようです。

![スクリーンショット 2021-01-20 19.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/b772042a-fea8-305b-0bdf-36ed1dbfa45b.png)

　Budgetsも100ドルで作成されています。
　リソースは無事に展開されていました。

# サービスマネージド型StackSetsでIAMロール展開

　ターゲットアカウントでのIAMロール作成は、コンソールではうまく行きました。
　ターゲットアカウントにいちいちサインインして作成するのも面倒です。
　次はIAMロールをStackSetsで展開してみます。
　IAMロールはOU単位で展開しても問題ないので、サービスマネージド型で展開します。
　テンプレートは最後に載せます。

![スクリーンショット 2021-01-20 19.58.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/7c5bad17-5e7b-b31e-98d8-da56697eb76f.png)

　サービスマネージド型を選択する画面のキャプチャを撮るのを忘れていますが、サービスマネージド型を選択しています。

![スクリーンショット 2021-01-20 20.00.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/c1adb241-bab7-9a1f-626c-6c19a879893c.png)

　IAMロールの展開が成功しました。
　念のため、ちゃんと作成されているかアカウントを移動してみてみます。


![スクリーンショット 2021-01-20 21.23.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/5df4592c-21f3-c4e6-63ad-01afd883847d.png)
![スクリーンショット 2021-01-20 21.23.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/12108a42-77e7-7484-50e3-51e13a9673c4.png)

　ポリシー、信頼関係ともに正常に作成できています。

# セルフマネージド型StackSetsでスタックの追加を行い、新アカウントにBudgets,SNSを展開する

![スクリーンショット 2021-01-20 21.13.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/2382fb57-5496-15b9-7992-01cc198a74a0.png)

　ターゲットOUへのIAMロール展開が終了したので、ターゲットOU内のアカウントを指定して、StackSetsでBudgets、SNSを展開します。

![スクリーンショット 2021-01-20 21.14.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/f7e96449-ec72-7190-162c-28dcdd154459.png)

　上書き指定します。
　写真には載っていませんが、スタックを追加する新しいアカウントのIDを指定して作業をしているので、既存のスタックインスタンスには影響を及ぼしません。
　指定した予算額、メールアドレスになっているか確認します。

![スクリーンショット 2021-01-20 21.21.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/c725bcd6-3849-567c-5f76-34dcc3496732.png)

　予算額は50ドルになっているので合っています。

![スクリーンショット 2021-01-20 21.22.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/4c5e1e99-d2a9-cc9d-6757-8eb35a30dd5a.png)

　SNSも作成され、指定したメールアドレスになっています。
　StackSetsで展開したIAMロールと、StackSetsで展開したBudgets等が正常に機能していることが確認できました。



# テンプレート

```yaml:Budgets,SNS
Description: "Basic Budget"

Parameters:
  Amount:
    Type: String
    Default: 100
    Description: Budgeted amount
  email:
    Type: String
    Description: Notification email1 address user

Resources:
  Budget:
    Type: AWS::Budgets::Budget
    Properties:
      Budget:
        BudgetName: test1
        BudgetLimit:
          Amount: !Ref Amount
          Unit: USD
        TimeUnit: MONTHLY
        BudgetType: COST
      NotificationsWithSubscribers:
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 50
            ThresholdType: PERCENTAGE
          Subscribers:
            - Address: !Ref SNS
              SubscriptionType: SNS
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 100
            ThresholdType: PERCENTAGE
          Subscribers:
            - Address: !Ref SNS
              SubscriptionType: SNS
  SNS:
    Type: AWS::SNS::Topic
    Properties: 
      Subscription: 
        - Endpoint: !Ref email
          Protocol: email
      TopicName: StackSetsBudgets1
  SNSPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Id: MyTopicPolicy
        Version: '2012-10-17'
        Statement:
        - Sid: My-statement-id
          Effect: Allow
          Principal:
            Service: budgets.amazonaws.com
          Action: SNS:Publish
          Resource: !Ref SNS
      Topics:
      - !Ref SNS
```

```yaml:IAMロール・ポリシー
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the AWSCloudFormationStackSetExecutionRole to enable use of your account as a target account in AWS CloudFormation StackSets.

Parameters:
  AdministratorAccountId:
    Type: String
    Description: AWS Account Id of the administrator account (the account in which StackSets will be created).
    MaxLength: 12
    MinLength: 12

Resources:
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AWSCloudFormationStackSetExecutionRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref AdministratorAccountId
            Action:
              - sts:AssumeRole
      Path: /
  ExecuteBudgetsPolicy:
    Type: AWS::IAM::Policy
    Properties: 
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
              - "budgets:*"
              - "sns:*"
            Resource: "*"
      PolicyName: AWSCloudFormationStackSetExecutionRoleBudgetsPolicy
      Roles: 
        - !Ref ExecutionRole
```

# さいごに

　セルフサービス型StackSets作成が初めてだったので結構身構えていましたが、やってみると思ったより簡単であることがわかりました。
　CloudFormationの中にポリシーを入れることに対して苦手意識を持っていましたが、多少改善されたような気がします。
　セルフサービス型にすることで無駄な確認メールが減り、最初から自分が望む設定のリソースを展開できるので結構嬉しいです。
　ただIAMロールをOU単位でばらまくので、使わないアカウントが出る可能性があるのが残念ですが、これは仕方ないですね（たぶん）。
　権限も最低限にしていますし、親アカウントしか使用できないので大丈夫でしょう。