AWS SSO のアカウント割り当てを特定ユーザーやグループに委任する

## はじめに
AWS SSO で管理する AWS アカウントが増えてくると、アクセス制御設定を行う管理者の作業負荷も高くなっていきます。以下の AWS Security Blog でアクセス権限セットの作成とアカウントの割り当ての制御を特定のユーザーやグループに委任する方法が紹介されていました。

https://aws.amazon.com/jp/blogs/security/how-to-delegate-management-of-identity-in-aws-single-sign-on/

上記の Post では権限セットに付与するカスタム権限ポリシーのタグと Condition 要素を使用して以下の 3 つのパターンで制御を行う方法が記載されています。

* アカウント ID ベースの委任モデル 
* 権限セットベースの委任モデル
* OU ベースの委任モデル

本記事では OU ベースの委任モデルについて検証した内容やその際の補足手順等を交えて記載しています。前提として AWS SSO の設定そのものについては詳しく記載しません。また参考として記載する権限セットのカスタムアクセス権限ポリシーは外部 IdP と連携した環境を想定しています。

## OU ベースの委任モデルの注意点
前述のとおり、あくまで権限セットに付与するカスタム権限ポリシーのタグと Condition 要素を組み合わせた手法であるため、AWS Organizations の OU-ID (ou-xxxx-yyyyyyyy) を使用した制御ではないことに注意してください。詳細は後述しますが、カスタムアクセス権限ポリシーにアカウント ID をリストする必要があるため、OU に新規アカウントが追加される都度、ポリシーを更新する必要があります。


## 委任用権限セットの作成
最初に特定のユーザー、グループへ権限セットの作成やアカウント割り当てを委任するための権限セットを作成します。ここでは Sandbox という OU の配下にあるアカウントに対するアカウント割り当て権限を委任する例を考えます。今回は CloudFormation で権限セットを作成したため、最終的なテンプレートは以下のようになりました。アクセス権限ポリシーは AWS Security Blog に記載の内容から若干カスタマイズしています。

```template.yaml
Parameters: 
  pAwsSsoInsanceArn: 
    Type: String
    Default: arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx
    Description: No change required.

Resources:
  rPermissionSetSandboxDelegatedAdmin:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: !Ref pAwsSsoInsanceArn
      Name: SandboxDelegatedAdmin
      Description: "Sandbox Delegated Administrator"
      ManagedPolicies:
        - arn:aws:iam::aws:policy/AWSSSOReadOnly
      RelayStateType: https://ap-northeast-1.console.aws.amazon.com/singlesignon/home?region=ap-northeast-1#/accounts/organization 
      SessionDuration: "PT1H"
      Tags:
        - Key: "OU"
          Value: "None"
      InlinePolicy:
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "DelegatedOUAdmin",
                    "Effect": "Allow",
                    "Action": [
                        "sso:CreatePermissionSet",
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:ProvisionPermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:DeleteInlinePolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment",
                        "sso:TagResource"
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Sandbox"
                            ]
                        }
                    }
                },
                {
                    "Sid": "Instance",
                    "Effect": "Allow",
                    "Action": [
                        "sso:CreatePermissionSet",
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:ProvisionPermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:DeleteInlinePolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment",
                        "sso:TagResource"
                    ],
                    "Resource": [
                        "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx",
                        "arn:aws:sso:::account/123456789012",
                        "arn:aws:sso:::account/111111222222"
                    ]
                },
               {
                    "Sid": "AllowRootPermissionSet",
                    "Effect": "Allow",
                    "Action": [
                        "sso:ProvisionPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment"
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Root"
                            ]
                        }
                    }
                },
                {
                    "Sid": "DenyRootPermissionSet",
                    "Effect": "Deny",
                    "Action": [
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Root"
                            ]
                        }
                    }
                },
                {
                    "Sid": "AllowSsoDirectory",
                    "Effect": "Allow",
                    "Action": [
                        "sso-directory:DescribeDirectory",
                        "sso-directory:DescribeGroups",
                        "sso-directory:DescribeUsers",
                        "sso-directory:ListGroupsForUser",
                        "sso-directory:ListMembersInGroup",
                        "sso-directory:SearchGroups",
                        "sso-directory:SearchUsers"
                    ],
                    "Resource": "*"
                }
            ]
        }
```

以降、部分毎に補足します。

### InstanceArn
InstanceArn には SSO インスタンスの ARN を指定します。

```yaml
Parameters: 
  pAwsSsoInsanceArn: 
    Type: String
    Default: arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx
    Description: No change required.

Resources:
  rPermissionSetSandboxDelegatedAdmin:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: !Ref pAwsSsoInsanceArn
      Name: SandboxDelegatedAdmin
      Description: "Sandbox Delegated Administrator"
```

AWS SSO コンソールの設定画面から確認するか、sso-admin の list-instances から取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/a2a35a6a-1590-0418-53b1-ccb8fc11f42a.png)

```shell-session
$ aws sso-admin list-instances
{
    "Instances": [
        {
            "InstanceArn": "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx",
            "IdentityStoreId": "d-xxxxxxxxxx"
        }
    ]
}
```

### ManagedPolicies
委任するユーザー、グループに AWS SSO コンソールへの読み取りアクセスを提供するため、AWS 管理ポリシーの `AWSSSOReadOnly` をアタッチしています。特定の設定レベルで参照を許可したい場合は権限が大きすぎる場合もあるかと思いますので、適宜カスタムポリシーをご使用ください。

```yaml
      ManagedPolicies:
        - arn:aws:iam::aws:policy/AWSSSOReadOnly
```

### RelayStateType
RelayStateType では SSO による認証後にユーザーをリダイレクトする URL を指定できます。今回のようなケースでは AWS SSO の URL を指定することでユーザーはすぐにアカウント割り当て操作を始めることができます。

```yaml
      RelayStateType: https://ap-northeast-1.console.aws.amazon.com/singlesignon/home?region=ap-northeast-1#/accounts/organization
```

### Tags
タグに指定された OU の値で、委任されたユーザー、グループが対象の権限セットをアカウントに割り当てできるかを制御します。この権限セットは自由に割り当ててほしくないため、OU 名ではない別の値 (ここでは None) を指定しています。


```yaml
     Tags:
        - Key: "OU"
          Value: "None"
```


### InlinePolicy
カスタムアクセス権限ポリシーを記載しています。
長いので更に 4 項目に分けて補足します。

* OU 固有の権限セットをフィルタリングするポリシー
* アカウントをフィルタリングするポリシー
* 組織で共有する権限セットをフィルタリングするポリシー
* AWS SSO Directory にアクセスを許可するポリシー


以下のステートメントは OU 固有の権限セットをフィルタリングするポリシーです。Resource および Condition 要素を使用して、OU タグに Sandbox とつけられた権限セットにのみ書き込み権限および、アカウントへのアクセス割り当てを許可します。

```json
           {
                    "Sid": "DelegatedOUAdmin",
                    "Effect": "Allow",
                    "Action": [
                        "sso:CreatePermissionSet",
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:ProvisionPermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:DeleteInlinePolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment",
                        "sso:TagResource"
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Sandbox"
                            ]
                        }
                    }
                }
```

以下のステートメントは AWS アカウントをフィルタリングするためのポリシーです。Resources 要素を使用して SSO Instance と Sandbox OU に所属するアカウントをリストします。明示的にアカウント ID をリストしているため、**このポリシーは OU に属するアカウントに変更が発生する度に編集する必要があります。**

```json
                {
                    "Sid": "Instance",
                    "Effect": "Allow",
                    "Action": [
                        "sso:CreatePermissionSet",
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:ProvisionPermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:DeleteInlinePolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment",
                        "sso:TagResource"
                    ],
                    "Resource": [
                        "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx",
                        "arn:aws:sso:::account/123456789012",
                        "arn:aws:sso:::account/111111222222"
                    ]
                },
```

以下のステートメントは組織内で共有する権限セットをフィルタリングするポリシーです。AdministratorAccess や ViewOnlyAccess などの既存の職務機能ポリシーを使用する権限セットに関しては特定 OU ではなく、組織全体で使用するケースが考えられます。そのような共通の権限セットには OU タグに Root (任意の値でOK) を設定しておき、アカウント割り当てを許可します。ただし共有の権限セットに対する編集権限は Deny します。

```json
               {
                    "Sid": "AllowRootPermissionSet",
                    "Effect": "Allow",
                    "Action": [
                        "sso:ProvisionPermissionSet",
                        "sso:CreateAccountAssignment",
                        "sso:DeleteAccountAssignment"
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Root"
                            ]
                        }
                    }
                },
                {
                    "Sid": "DenyRootPermissionSet",
                    "Effect": "Deny",
                    "Action": [
                        "sso:DeletePermissionSet",
                        "sso:UpdatePermissionSet",
                        "sso:AttachManagedPolicyToPermissionSet",
                        "sso:DetachManagedPolicyFromPermissionSet",
                        "sso:PutInlinePolicyToPermissionSet",
                    ],
                    "Resource": "arn:aws:sso:::permissionSet/*/*",
                    "Condition": {
                        "StringEquals": {
                            "aws:ResourceTag/OU": [
                                "Root"
                            ]
                        }
                    }
                }
```

以下のステートメントは AWS SSO Directory へのアクセスを許可するポリシーです。Managed Policy にアタッチした AWSSSOReadOnly は AWS SSO Directory に対する権限が `sso-directory:DescribeDirectory` のみしか含まれていないため、外部 IdP から SCIM で連携されたユーザー, グループ情報を検索するために必要な権限を追加で付与します。

```json
                {
                    "Sid": "AllowSsoDirectory",
                    "Effect": "Allow",
                    "Action": [
                        "sso-directory:DescribeDirectory",
                        "sso-directory:DescribeGroups",
                        "sso-directory:DescribeUsers",
                        "sso-directory:ListGroupsForUser",
                        "sso-directory:ListMembersInGroup",
                        "sso-directory:SearchGroups",
                        "sso-directory:SearchUsers"
                    ],
                    "Resource": "*"
                }
```

CloudFormation でテンプレートをデプロイし、委任用の権限セット (`SandboxDelegatedAdmin`) を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/88f9cb43-c798-9891-a61d-f00f5e68ce05.png)

## 委任用権限セットを割り当てる
AWS SSO の操作を委任するユーザーまたはグループに AWS SSO が動作する組織の管理アカウントへのアクセスを割り当てを行います。権限セットは先ほど作成した `SandboxDelegatedAdmin` を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/2f6baebf-c148-ff70-1657-304156fe92fc.png)

## 動作確認
AWS SSO にサインインし、割り当てた権限セットが表示されることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/e6eff2e8-41ef-0569-33d8-7fa06334a67b.png)

SandboxDelegatedAdmin でマネジメントコンソールを開くと、RelayStateType の指定に従い、AWS SSO の AWS アカウント画面に遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/12cc8473-39cc-60c3-d276-e8dbc4d766f8.png)

### 権限セットの作成、変更、削除
OU 固有の権限セットをフィルタリングするポリシーにより、OU タグに Sandbox とつけられた権限セットのみ作成、変更、削除が可能になります。例えば OU タグに Sandbox ではなく、Test を指定して権限セットを作成しようとすると以下のように権限エラーとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/9c3ef72b-57ba-3949-2787-f41882d5953b.png)

OU タグに Sandbox を指定すると権限セットの作成に成功します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/78d5dae5-bcd7-ef09-c25c-03d13ba5928c.png)

### アカウント割り当て
アカウントをフィルタリングするポリシーにより、ポリシーにリストされていないアカウント (Sandbox OU 外のアカウント) に対して割り当てを行なうと以下のように権限エラーとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/4be30a25-985a-d1c1-70f8-58f8954aa9d6.png)

アカウントをフィルタリングするポリシーにリストされているアカウント  (Sandbox OU 内のアカウント) であっても、OU タグが Sandbox または Root 以外の権限セットを割り当てようとした場合は権限エラーになります。厳密にはアカウントに対するロールのプロビジョニングは行われますが、実際のユーザー/グループへの割り当てには失敗します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/480af910-1069-8c6a-ec69-367fdf952d08.png)

アカウントをフィルタリングするポリシーにリストしたアカウントに対し、OU タグに Sandbox がセットされた権限セットを指定すると想定通り正常に割り当てることができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/a8c9c401-4778-1cc2-9c1f-34244c2a1fc0.png)

## まとめ
委任用の権限セットに付与するカスタムアクセス権限ポリシーのタグと Condition 要素を組み合わせることで、特定の OU に絞った形で任意のユーザー/グループに権限セットの作成とアカウントの割り当ての制御を委任できることがわかりました。一方でこれを実現するためにはカスタムアクセス権限ポリシーに明示的にアカウント ID をリストする必要があるため、多くの OU が存在する環境や頻繁に新規アカウントが追加されるような環境では、委任用権限セットのメンテナンスにそれなりの運用コストがかかりそうです。

冒頭のブログで記載されてるポリシーには前提となる権限が一部省略されていたりもするので、検証した内容がどなたかの参考になれば幸いです。