[アップデート]AWS Organizationsの各種リソースをCloudFormationで管理できるようになりました

アカウントをCFnでチョチョイと作成できるようになったよ
2022.11.20

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

AWS OrganizationsがCloudFormation(以下CFn)をサポートしました。

以下リソースをCFnで管理できるようになりました。

  • AWSアカウント
  • organizational units (OUs)
  • ポリシー
    • 4種類あります。代表的なのはSCPですね。
      • SCP (Service control policy)
      • Artificial Intelligence (AI) services opt-out policy
      • Backup policy
      • Tag policy

それぞれ使ってみます。

Organization作成

最初にそもそものOraganizationsを作成する必要があります。この部分はCFn未対応です。なので、以下ページを参考にマネジメントコンソールかCLIで作成しましょう。お使いのアカウントがOraganizationのマネジメントアカウントになります。

※ TerraformだとこのOrganization作成部分もIaC化できます。

ルート直下にアカウントを作成する

以下テンプレートでCFnスタックを作成してみます。Oraganizationのルート直下に1つアカウントを作成します。ルート直下なのでParentIdsプロパティにはr-から始まるルートIDを設定します。(後から知ったのですが、ルート直下にアカウントを作成する場合はこのParentIdsプロパティは省略可能だそうです)

AWSTemplateFormatVersion: 2010-09-09
Description: AWS Organizations supports CloudFormations
Resources:
  TestAccountMadeWithCfn:
    Type: AWS::Organizations::Account
    Properties:
      AccountName: made-with-cfn
      Email: sample+made-with-cfn@gmail.com
      ParentIds:
        - r-xxxx

made-with-cfnアカウントが無事作成できました。(他に以前作成したOUがあります。) 20221120-create-account

複数AWSアカウントの同時作成する

アカウント作成に使うCFnリソースAWS::Organizations::Accountのドキュメントに以下記述があります。

If you include multiple accounts in a single template, you must use the DependsOn attribute on each account resource type so that the accounts are created sequentially. If you create multiple accounts at the same time, Organizations returns an error and the stack operation fails.

複数アカウントを同時に作成するとエラーになるよ、だからDependsOnを使って順次作成してね、とのことです。本当にエラーになるのか、以下のようにDependsOnを使わずに2アカウント追加してみました。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - r-xxxx
+  MultipleCreation1:
+    Type: AWS::Organizations::Account
+    Properties:
+      AccountName: made-with-cfn2
+      Email: sample+made-with-cfn2@gmail.com
+      ParentIds:
+        - r-xxxx
+  MultipleCreation2:
+    Type: AWS::Organizations::Account
+    Properties:
+      AccountName: made-with-cfn3
+      Email: sample+made-with-cfn3@gmail.com
+      ParentIds:
+        - r-xxxx

予想に反してこのスタック更新は成功しました。CreateAccount APIのリファレンスには

Using CreateAccount to create multiple temporary accounts isn't recommended.

と「おすすめしない」とだけ書かれていたので、必ず複数同時作成が失敗するわけでも無いようですね。とはいえ本番運用する際には念の為DependsOn句を書いておいた方が良さそうです。

OUを作成する

OUに関してはParentIdプロパティは必須項目で、省略はできません。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - r-xxxx
   MultipleCreation1:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
   MultipleCreation2:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn3
       Email: sample+made-with-cfn3@gmail.com
       ParentIds:
         - r-xxxx
+  TestOu:
+    Type: AWS::Organizations::OrganizationalUnit
+    Properties:
+      Name: test-ou
+      ParentId: r-xxxx

作成成功しました。

20221120-create-ou

OU配下にアカウントを移動する

OUリソースの論理名をRefするとOUのIDが取得できます。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
+        - !Ref TestOu
-        - r-xxxx
   MultipleCreation1:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
   MultipleCreation2:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn3
       Email: sample+made-with-cfn3@gmail.com
       ParentIds:
         - r-xxxx
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx

アカウントを移動させることができました。 20221120-move-ou

SCPを作成する

前述の通りポリシーは4種類ありますが、ここでは一番良く使うであろうSCPを作成してみたいと思います。以下ドキュメントを参考に、特定のタグを付与していない場合はEC2インスタンス作成が失敗するようにしてみます。

AWS::Organizations::AccountのRefを使ってみたかったので、ターゲットはOraganization全体でもOUでもなくアカウントにしています。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - !Ref TestOu
   MultipleCreation1:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
   MultipleCreation2:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn3
       Email: sample+made-with-cfn3@gmail.com
       ParentIds:
         - r-xxxx
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx
+  TestSCP:
+    Type: AWS::Organizations::Policy
+    Properties:
+      Type: SERVICE_CONTROL_POLICY
+      TargetIds:
+        - !Ref TestAccountMadeWithCfn
+      Name: test-scp
+      Description: Require project tag to create EC2 instances
+      Content: |
+        {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Sid": "DenyRunInstanceWithNoProjectTag",
+              "Effect": "Deny",
+              "Action": "ec2:RunInstances",
+              "Resource": [
+                "arn:aws:ec2:*:*:instance/*",
+                "arn:aws:ec2:*:*:volume/*"
+              ],
+              "Condition": {
+                "Null": {
+                  "aws:RequestTag/Project": "true"
+                }
+              }
+            }
+          ]
+        }

こちらのテンプレートでスタック更新したところ、以下エラーになりました。 20221120-error

Resource handler returned message: "This operation can be performed only for enabled policy types. (Service: Organizations, Status Code: 400, Request ID: xxxx)" (RequestToken: yyyy, HandlerErrorCode: InvalidRequest)

調べたところ、ポリシーは4種類それぞれオプトインする必要があるんですね。Organizationsのコンソールで確認したところ、どれも有効化されていませんでした。

20221120-org-policies

SCPを有効化して、再度スタック更新を実行したところ成功しました。Organizationsコンソールでポリシーが作成できていることも確認できました。 20221120-scp-desc ターゲットもアカウント1つだけです。 20221120-scp-target

SCPをテストしてみます。このアカウントにログインしてEC2インスタンスをProjectタグ無しで作成してみました。

狙い通りエラーになりました。 20221120-launch-error エラーメッセージがエンコードされていて内容がわかりません。以下ドキュメントを参考にエラーメッセージをデコードしてみました。

実行したコマンド

aws sts decode-authorization-message --encoded-message (コンソールからエンコードメッセージをコピペ) | jq .DecodedMessage --raw-output | jq .

コマンド結果

{
  "allowed": false,
  "explicitDeny": true,
  "matchedStatements": {
    "items": [
      {
        "statementId": "DenyRunInstanceWithNoProjectTag",
        "effect": "DENY",
        "principals": {
          "items": [
            {
              "value": "AROAWWZIGWOTQSBVXYNIX"
            }
          ]
        },
        "principalGroups": {
          "items": []
        },
        "actions": {
          "items": [
            {
              "value": "ec2:RunInstances"
            }
          ]
        },
        "resources": {
          "items": [
            {
              "value": "arn:aws:ec2:*:*:instance/*"
            },
            {
              "value": "arn:aws:ec2:*:*:volume/*"
            }
          ]
        },
        "conditions": {
          "items": [
            {
              "key": "aws:RequestTag/Project",
              "values": {
                "items": [
                  {
                    "value": "true"
                  }
                ]
              }
            }
          ]
        }
      }
    ]
  },
  "failures": {
    "items": []
  },
  "context": {
    "principal": {
      "id": "AROAWWZIGWOTQSBVXYNIX:xxxxxuser",
      "arn": "arn:aws:sts::012345678901:assumed-role/AWSReservedSSO_AdministratorAccess_132ab909823e04ea/xxxxxuser"
    },
    "action": "ec2:RunInstances",
    "resource": "arn:aws:ec2:ap-northeast-1:012345678901:instance/*",
    "conditions": {
      "items": [
        {
          "key": "ec2:InstanceMarketType",
          "values": {
            "items": [
              {
                "value": "on-demand"
              }
            ]
          }
        },
        {
          "key": "aws:Resource",
          "values": {
            "items": [
              {
                "value": "instance/*"
              }
            ]
          }
        },
        {
          "key": "aws:Account",
          "values": {
            "items": [
              {
                "value": "012345678901"
              }
            ]
          }
        },
        {
          "key": "ec2:AvailabilityZone",
          "values": {
            "items": [
              {
                "value": "ap-northeast-1c"
              }
            ]
          }
        },
        {
          "key": "ec2:ebsOptimized",
          "values": {
            "items": [
              {
                "value": "false"
              }
            ]
          }
        },
        {
          "key": "ec2:IsLaunchTemplateResource",
          "values": {
            "items": [
              {
                "value": "false"
              }
            ]
          }
        },
        {
          "key": "ec2:InstanceType",
          "values": {
            "items": [
              {
                "value": "t2.micro"
              }
            ]
          }
        },
        {
          "key": "ec2:RootDeviceType",
          "values": {
            "items": [
              {
                "value": "ebs"
              }
            ]
          }
        },
        {
          "key": "aws:Region",
          "values": {
            "items": [
              {
                "value": "ap-northeast-1"
              }
            ]
          }
        },
        {
          "key": "aws:Service",
          "values": {
            "items": [
              {
                "value": "ec2"
              }
            ]
          }
        },
        {
          "key": "ec2:InstanceID",
          "values": {
            "items": [
              {
                "value": "*"
              }
            ]
          }
        },
        {
          "key": "aws:Type",
          "values": {
            "items": [
              {
                "value": "instance"
              }
            ]
          }
        },
        {
          "key": "ec2:Tenancy",
          "values": {
            "items": [
              {
                "value": "default"
              }
            ]
          }
        },
        {
          "key": "ec2:Region",
          "values": {
            "items": [
              {
                "value": "ap-northeast-1"
              }
            ]
          }
        },
        {
          "key": "aws:ARN",
          "values": {
            "items": [
              {
                "value": "arn:aws:ec2:ap-northeast-1:012345678901:instance/*"
              }
            ]
          }
        }
      ]
    }
  }
}

設定したSCPで弾かれていることがわかりますね。

では今度はタグを付けて作成してみます。SCPに"value": "arn:aws:ec2:*:*:volume/*"も書いていたのでボリュームにもタグ付けが必要です。 20221120-add-tag 今度は成功しました! 20221120-suceed-running

アカウントを削除する

以下のようにAWS::Organizations::Accountリソースをテンプレートから削除して更新しても、アカウントは削除されずただCFn管理下から外れるだけです。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - !Ref TestOu
   MultipleCreation1:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
-  MultipleCreation2:
-    Type: AWS::Organizations::Account
-    Properties:
-      AccountName: made-with-cfn3
-      Email: sample+made-with-cfn3@gmail.com
-      ParentIds:
-        - r-xxxx
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx
   TestSCP:
     Type: AWS::Organizations::Policy
     Properties:
       Type: SERVICE_CONTROL_POLICY
       TargetIds:
         - !Ref TestAccountMadeWithCfn
       Name: test-scp
       Description: Require project tag to create EC2 instances
       Content: |
         {
           "Version": "2012-10-17",
           "Statement": [
             {
               "Sid": "DenyRunInstanceWithNoProjectTag",
               "Effect": "Deny",
               "Action": "ec2:RunInstances",
               "Resource": [
                 "arn:aws:ec2:*:*:instance/*",
                 "arn:aws:ec2:*:*:volume/*"
               ],
               "Condition": {
                 "Null": {
                   "aws:RequestTag/Project": "true"
                 }
               }
             }
           ]
         }

DELETE_SKIPPEDになっていますね。

これはデフォルトのDeletionPolicy値がRetainだからです。アカウントを削除したい場合はDeletionPolicyを明示的に指定する必要があります。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - !Ref TestOu
   MultipleCreation1:
     Type: AWS::Organizations::Account
+    DeletionPolicy: Delete
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx
   TestSCP:
     Type: AWS::Organizations::Policy
     Properties:
       Type: SERVICE_CONTROL_POLICY
       TargetIds:
         - !Ref TestAccountMadeWithCfn
       Name: test-scp
       Description: Require project tag to create EC2 instances
       Content: |
         {
           "Version": "2012-10-17",
           "Statement": [
             {
               "Sid": "DenyRunInstanceWithNoProjectTag",
               "Effect": "Deny",
               "Action": "ec2:RunInstances",
               "Resource": [
                 "arn:aws:ec2:*:*:instance/*",
                 "arn:aws:ec2:*:*:volume/*"
               ],
               "Condition": {
                 "Null": {
                   "aws:RequestTag/Project": "true"
                 }
               }
             }
           ]
         }

ですが、この更新はエラーになりました。エラーメッセージは以下です。

The submitted information didn't contain changes. Submit different information to create a change set.

DeletionPolicy追加だけでは差分として認識してくれないんですね…ぐぬぬ

以下エントリを参考にタグ追加も加えます。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - !Ref TestOu
   MultipleCreation1:
     Type: AWS::Organizations::Account
+    DeletionPolicy: Delete
     Properties:
       AccountName: made-with-cfn2
       Email: sample+made-with-cfn2@gmail.com
       ParentIds:
         - r-xxxx
+      Tags:
+        - Key: WillBeDeleted
+          Value: True
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx
   TestSCP:
     Type: AWS::Organizations::Policy
     Properties:
       Type: SERVICE_CONTROL_POLICY
       TargetIds:
         - !Ref TestAccountMadeWithCfn
       Name: test-scp
       Description: Require project tag to create EC2 instances
       Content: |
         {
           "Version": "2012-10-17",
           "Statement": [
             {
               "Sid": "DenyRunInstanceWithNoProjectTag",
               "Effect": "Deny",
               "Action": "ec2:RunInstances",
               "Resource": [
                 "arn:aws:ec2:*:*:instance/*",
                 "arn:aws:ec2:*:*:volume/*"
               ],
               "Condition": {
                 "Null": {
                   "aws:RequestTag/Project": "true"
                 }
               }
             }
           ]
         }

今度はスタック更新成功しました。

嬉しくないタグを付けられたアカウントちゃん 20221120-will-be-deleted

あとは該当AWS::Organizations::Accountリソースを削除して再度スタック更新です。

 AWSTemplateFormatVersion: 2010-09-09
 Description: AWS Organizations supports CloudFormations
 Resources:
   TestAccountMadeWithCfn:
     Type: AWS::Organizations::Account
     Properties:
       AccountName: made-with-cfn
       Email: sample+made-with-cfn@gmail.com
       ParentIds:
         - !Ref TestOu
-  MultipleCreation1:
-    Type: AWS::Organizations::Account
-    DeletionPolicy: Delete
-    Properties:
-      AccountName: made-with-cfn2
-      Email: sample+made-with-cfn2@gmail.com
-      ParentIds:
-        - r-xxxx
-      Tags:
-        - Key: WillBeDeleted
-          Value: True
   TestOu:
     Type: AWS::Organizations::OrganizationalUnit
     Properties:
       Name: test-ou
       ParentId: r-xxxx
   TestSCP:
     Type: AWS::Organizations::Policy
     Properties:
       Type: SERVICE_CONTROL_POLICY
       TargetIds:
         - !Ref TestAccountMadeWithCfn
       Name: test-scp
       Description: Require project tag to create EC2 instances
       Content: |
         {
           "Version": "2012-10-17",
           "Statement": [
             {
               "Sid": "DenyRunInstanceWithNoProjectTag",
               "Effect": "Deny",
               "Action": "ec2:RunInstances",
               "Resource": [
                 "arn:aws:ec2:*:*:instance/*",
                 "arn:aws:ec2:*:*:volume/*"
               ],
               "Condition": {
                 "Null": {
                   "aws:RequestTag/Project": "true"
                 }
               }
             }
           ]
         }

結果、該当アカウントのステータスが「停止」になりました。 20221120-account-delete-completed

アカウントは即削除されるわけではないのです。90日の猶予期間があります。この点については以下のエントリをご参照ください。

感想

だいぶ昔にTerraformでOrganizationsまわりを作成してみたことがあったので、「え、まだCFnはサポートしてなかったのか」というのが正直な感想でした。とはいえIaCの選択肢が増えるのは嬉しいですね。OUやSCPはマルチアカウントでAWSを使う際にとても影響範囲の広い重要なリソースですので、IaCで変更履歴を管理するのは重要だと思います。

参考情報