[アップデート]AWS Control TowerにOUで有効化されているコントロールへタグ付けできるAPIが追加されました。

2023.11.12

あしざわです。

本日、AWS Control TowerのAPIにアップデートがあり、OUで有効化されているコントロールへタグ付けできる新しいAPIが追加されました。

追加されたAPIは以下3つです。

  • ListTagsForResource
  • TagResource
  • UntagResource

これまでに公開されていたControl Tower APIは以下の5つだったので、今回のアップデートによって合計で8つになりました。

  • DisableControl
  • EnableControl
  • GetControlOperation
  • GetEnabledControl
  • ListEnabledControls

GetEnabledControlは直近で追加されたAPIで、以下ブログで紹介されています。

新規のAPIは公式ドキュメントのAPIリファレンスにも追加されていますが、現時点でAWS CLI(v2)への反映はされていませんでした(後述します)

このブログでは、AWSマネジメントコンソール および AWS CLIで追加されたAPIの動作を検証してみます。

4行まとめ

  • タグ付与できるコントロールはOUに対して有効化(関連付け)されているもののみ。
  • Control Towerのマネジメントコンソール > コントロールライブラリの各コントロールの詳細画面から、コントロールが有効化されているOU単位でタグ追加/削除/タグ確認ができる
  • AWS CLIでも同様にタグ追加/削除/タグ確認ができる(ただし現時点ではAWS CLIv2ではサポートされておらず、AWS CLIv1から実行する必要がある。)
  • Control Towerはタグエディタのサポート範囲外なので、タグの一括検索やタグポリシーなどタグの管理機能が利用できない。

AWSマネジメントコンソールでの検証

Control Towerのマネジメントコンソールのコントロールライブラリから、[AWS-GR_LOG_GROUP_POLICY] AWS Control Tower によって設定された Amazon CloudWatch Logs ロググループの変更を許可しない』を検索し、コントロールの詳細画面を確認します。

このコントロールは必須コントロールと呼ばれる無効化できないコントロールで、ランディングゾーンが有効なすべてのOUで有効化されているものです。

コントロールの詳細画面 「OUは有効です」タブの 設定 > View tags からタグを確認できるようです。

デフォルトでは何も設定されていないので Edit tags でタグを追加してみます。

Key: Status, Value: Required タグを設定して、Save chagesをクリックします。

再度View tagsを確認すると、Statusタグが追加されていました。

追加したタグはRemoveから削除できます。

Undoもできるようです。謝って削除してしまっても安心です。

再度削除して、Save chagesをクリックした後、View tagsを確認するとタグが削除されていました。

AWS CLIでの検証

続いて、AWS CLIから試してみます。

2023年11月11日未明時点では、AWS CLI(v2)で当該APIは未サポートの状態だったので、AWS CLI(v1)で検証を行います。

※以下プルリクエストがマージされ次第、v2でもサポートされるはずです

[v2] Merge model updates and develop branch to v2 branch by cli-v2-merge · Pull Request #8319 · aws/aws-cli

検証はCloudShell環境にて実施します。CloudShellではデフォルトでAWS CLI(v1)が導入されていなかったため、以下ブログを参考にAWS CLIのv1、v2がどちらも利用できる環境を準備しました。

ブログ執筆時とはpythonのバージョンが異なるため、インストールコマンド実行時にpython3を明示的に指定してください。

python3 ./awscli-bundle/install -i ~/.local/aws-cli-v1 -b ~/.local/bin/aws-v1

以下コマンドでエラーが出なければ準備完了です。

aws-v1 --version

今回追加されたコントロールへのタグ付け関連のAPI実行時のパラメータとして、いずれかのOUで有効化されているコントロールのARNを渡す必要があります。

このARNをtargetIdentifierと呼びますが、似たような識別子にcontrolIdentifierがあります。

これらの違いは以下です (Control TowerのAWS CLI(v1)のコマンドリファレンスに記載あり)

  • OUに関連付けられた状態のコントロール(targetIdentifier)
  • OUに関連付けられていない状態のコントロール(controlIdentifier)

前者のtargetIdentifierの情報を得るために、以下の順で特定のOUで有効化されているコントロールの一覧を取得します。

  1. OrganizationsのRootOUのIDを調べる
  2. 特定のOU名(SecurityOUなど)のARNを調べる
  3. そのOUで有効化されているコントロールの一覧を取得する

こちらのスクリプトを実行すると1〜3をまとめてチェックでき、SecurityOUで有効化されているコントロールの一覧がJSON形式で表示されます。.Name == "SecurityOU"の箇所を任意のOU名に書き換えることで、他のOUにも対応できます。

## RootOUのIDを取得する
parentsid=$(aws-v1 organizations list-roots | jq -r '.Roots[0].Id')

## Nameに入力したOUのARNを取得する
ouarn=$(aws-v1 organizations list-organizational-units-for-parent --parent-id $parentsid | jq -r '.OrganizationalUnits[] | select(.Name == "SecurityOU") | .Arn')

## OUで有効化されているコントロールの一覧を取得
aws-v1 controltower list-enabled-controls --target-identifier $ouarn

出力結果の抜粋がこちらです。

ハイライト箇所ののパラメータがtargetIdentifierです。こちらをメモしておきましょう。

・出力例(抜粋)

{
    "enabledControls": [
				〜略〜
        {
            "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO957KTEXOXWC",
            "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_LOG_GROUP_POLICY",
            "driftStatusSummary": {
                "driftStatus": "NOT_CHECKING"
            },
            "statusSummary": {
                "status": "SUCCEEDED"
            },
            "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
        },
        {
            "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/49ZG5EDWJK64GCJ6",
            "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_REGION_DENY",
            "driftStatusSummary": {
                "driftStatus": "NOT_CHECKING"
            },
         }
				〜略〜
    ]
}
出力結果の全体は長いのでトグルに
    {
        "enabledControls": [
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/49ZG5BUZNKOH963E",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_AUDIT_BUCKET_DELETION_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/4T8DO7RO0BV1MXSQ",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_AUDIT_BUCKET_PUBLIC_READ_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/24ZQ2V3TS0RD5TWE",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_AUDIT_BUCKET_PUBLIC_WRITE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/49ZG5BZJ7KS0WSX1",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CLOUDTRAIL_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/9MGRC0BGGHFI9O4O",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CLOUDTRAIL_CLOUDWATCH_LOGS_ENABLED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/4T8DO0B4WBYWJTDN",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CLOUDTRAIL_ENABLED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/4T8DO0G74SMHBU5Y",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CLOUDTRAIL_VALIDATION_ENABLED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/49ZG5CHTDSBEUFQK",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CLOUDWATCH_EVENTS_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/9MGRC1F2OVBHTCPL",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CONFIG_AGGREGATION_AUTHORIZATION_POLICY",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/24ZQ2ODLKW44ARF5",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CONFIG_AGGREGATION_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO3Y4G2RXZIAS",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CONFIG_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO4JI8QQJLADT",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CONFIG_ENABLED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/17B3F0B8CE7IWLDP",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CONFIG_RULE_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/8JYWAQFH1CMR4PDD",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CT_AUDIT_BUCKET_ENCRYPTION_CHANGES_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO61XCBXG6VCP",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CT_AUDIT_BUCKET_LIFECYCLE_CONFIGURATION_CHANGES_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/9MGRC3ACWD6U6SG8",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CT_AUDIT_BUCKET_LOGGING_CONFIGURATION_CHANGES_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO762OAKTJH8J",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_CT_AUDIT_BUCKET_POLICY_CHANGES_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/2EM6UC4UODS8SWI7",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_DETECT_CLOUDTRAIL_ENABLED_ON_SHARED_ACCOUNTS",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/8JYWARI9Z4RMJKTP",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_IAM_ROLE_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO8JC023MDCC1",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_LAMBDA_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO957KTEXOXWC",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_LOG_GROUP_POLICY",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/49ZG5EDWJK64GCJ6",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_REGION_DENY",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/LNJPIAU43NUAMNME",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_SNS_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            },
            {
                "arn": "arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/2EM6U19V8W7WPTLE",
                "controlIdentifier": "arn:aws:controltower:ap-northeast-1::control/AWS-GR_SNS_SUBSCRIPTION_CHANGE_PROHIBITED",
                "driftStatusSummary": {
                    "driftStatus": "NOT_CHECKING"
                },
                "statusSummary": {
                    "status": "SUCCEEDED"
                },
                "targetIdentifier": "arn:aws:organizations::123456789012:ou/o-1vz68cxxak/ou-zbrf-rcjy07p1"
            }
        ]
    }


これまでに、特定のOUで有効化されているコントロールの中からタグ関連APIの実行対象になるコントロールのARNを取得しました。ここからは本題のタグ関連のAPIをAWS CLIで検証します。

当該のAPIはAWS CLIの以下コマンドで実行できます。

  • aws controltower list-tags-for-resource
  • aws controltower tag-resource
  • aws controltower untag-resource

list-tags-for-resource(タグの一覧表示)

一覧表示したコントロールから、"arn"の値を取得して、以下list-tags-for-resourceコマンドの< >箇所と置き換えて実行してください。

タグが設定されていない時は空の配列が出力されます。

aws-v1 controltower list-tags-for-resource \
	--resource-arn <OUで有効化済みのコントロールのarn>
・出力結果例

{
    "tags": {}
}

tag-resource(タグの付与)

以下tag-resourceコマンドを実行して、タグを付与します。

"Env": "Prod"の組み合わせのタグを設定してみます。

aws-v1 controltower tag-resource \
	--resource-arn <OUで有効化済みのコントロールのarn> \
	--tags '{"<タグ1のキー>": "<タグ1のバリュー>"}'
・出力結果例

なし

再度list-tags-for-resourceコマンドを実行して、結果を確認します。

Envタグが追加されていますね。

[cloudshell-user@ip-10-2-40-152]$ aws-v1 controltower list-tags-for-resource \
	--resource-arn arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO957KTEXOXWC

{
    "tags": {
        "Env": "Prod"
    }
}

タグを2つ以上付与するときは、このようにカンマ区切りで続けてください。

aws-v1 controltower tag-resource \
	--resource-arn <OUで有効化済みのコントロールのarn> \
	--tags '{"<タグ1のキー>": "<タグ1のバリュー>", "<タグ2のキー>": "<タグ2のバリュー>"}'

untag-resource(タグの削除)

以下untag-resourceコマンドを実行して、タグを削除します。

aws-v1 controltower untag-resource \
	--resource-arn <OUで有効化済みのコントロールのarn> \
	--tag-keys '<タグのキー>'
・出力結果例

なし

list-tags-for-resourceコマンドを実行して、結果を確認します。

Envタグが削除されていることが確認できました。

[cloudshell-user@ip-10-2-40-152]$ aws-v1 controltower untag-resource \
	--resource-arn arn:aws:controltower:ap-northeast-1:123456789012:enabledcontrol/J8XIO957KTEXOXWC \
	--tag-keys 'Env'

{
    "tags": {}
}

タグを2つ以上まとめて削除する時は、このようにキーをスペース区切りで続けてください。

aws-v1 controltower untag-resource \
	--resource-arn <OUで有効化済みのコントロールのarn> \
	--tag-keys '<タグ1のキー>' '<タグ2のキー>'

検証は以上です。

その他

Control Towerがタグ付けをサポートしたのでもしかして...と期待して、タグエディタがサポートするリソースの詳細が記載された以下公式ドキュメントを確認したところ、Control Towerサービスの追加はありませんでした。残念。

タグエディタをコンソール上で確認しても想定通り、リソースタイプにControl Towerは表示されませんでした。今後のアップデートに期待したいです。

最後に

今回はAWS Control Towerに新規追加されたタグ付けに関連する3つのAPIについて紹介しました。

これまでは、Control Towerの管理はマネジメントコンソール上で行うことがほとんどだったと思います。今後もより便利なAPIが出て運用を楽にしてくれると嬉しいなと思った筆者でした。

APIの検証を通じて改めてControl TowerのコンソールやAPIには伸び代がたくさんあるな、と感じました。これからもアップデートにキャッチアップしながらControl Towerの未来について考えていきたいです。

以上です。