AWS Identity Centerの権限付与の課題と解決

<!-- ================================================= -->
# AWS Identity Centerの権限付与の課題
<!-- ================================================= -->
Identity Centerの権限付与には以下のセキュリティ上の大きな課題があり、そのままでは運用が困難となる。

1. 許可セットに対し、アタッチするマネージドポリシーを制限できないため、例えば**AdministratorAccess**のような強い権限のポリシーをアタッチ出来てしまう
1. インラインポリシーの作成権限を付与してしまうと、そのポリシー内容は制限できないため、**AdministratorAccess**のポリシー内容をコピーしてインラインポリシーを作成することで権限拡大が出来てしまう

詳細は以下のブログでも記載している。
[AWS Identity Centerの備忘録](https://qiita.com/Ishikawa_Kazuyoshi/items/c9bc9bb2c3a9acdc4261)

これは、**Identity Center**系の権限のみを許可した作業者(つまり、**IAM**系の権限は許可してしない)であっても**AdministratorAccess**やそれに準じた権限へ昇格を行えてしまうことになる。つまり、**Identity Center**系の運用作業を他社へ業務委託するような場合に特に大きな問題となる。

以下に、それを解決する策を検討してみる。
(以下、作業者とは**Identity Center**で許可セットを作成する者を指す)

1. 作業者にインラインポリシーの作成権限を付与しない
作業者に対し**sso:PutInlinePolicyToPermissionSet**の権限を付与しなければインラインポリシーの作成を禁止することは可能である。
しかし、AWSマネージドポリシーでは定義できないカスタム権限を許可セットへ付与したい場合、都度カスタマーマネージドポリシーを事前に作成しておく必要が出てくる。
そのため運用上許可セットを作成していく最中に**IAM**系の作業が必須となり、**Identity Center**系の作業だけを業務委託することができない。
また、この方法ではAWSマネージドポリシーで**AdministratorAccess**のような強い権限を付与出来てしまうことは防止できない。

1. 何らかのプログラムを介して許可セットを作成するようにし、許可セットへ付与するポリシーの自由度を制限する
作業者には許可セットを作成する権限である**sso:CreatePermissionSet**を許可しないようにして、EC2上のAWS SDK、Lambda、SSM AutomationのDocumentといった環境上に配置したスクリプトを実行し、許可セットの作成を行うようにする(作業者はスクリプトの修正が出来ないように別途権限制御を行う)。つまりAWSマネージドポリシーやカスタマーマネージドポリシーで付与可能なポリシーをスクリプト内で制限したり、インラインポリシーの内容を制限したりといったことを処理実装することで実現する。

後者の方法で実装するとして、許可セットに付与する権限をどのような形にするかがスクリプトの複雑さに影響するため、スクリプトを簡略化するために以下のように取り決める。

表1
|No | ポリシー種別                | 許可セットへの割り当て         |
|--:|:---------------------------|:------------------------------|
|1  |AWSマネージドポリシー        |割り当て不可、もしくは特定のポリシーのみ付与 |
|2  |カスタマーマネージドポリシー |同上                      |
|3  |インラインポリシー           |特定の権限のみ作成可能    |
|4  |許可の境界                  |固定のポリシーを必須で付与 |

ここで発想を転換し、事前に必要なカスタム権限を持った**IAM**ロールを作成しておき、許可セットに設定するインラインポリシーで**IAM**ロールへ**sts:AssumeRole** 出来るようにする権限のみを許可するようにすれば、

- **SSO**ログインするユーザーはログイン後適切な**IAM**ロールへスイッチロールすることで必要な権限を得られる
- 作業者は**SSO**ログインするユーザーへ「どの**IAM**ロールへスイッチロール可能か」のみをインラインポリシーで制御する(スクリプトで作成する許可セットのインラインポリシーをそれだけに限定する)
これによってSSOログインするユーザーに割り当てる権限を間接的に制御できる
- **IAM**系の管理を行っている側は、事前にIAMロールを用意しておけば良いため、**Identity Center**側の作業とは独立して作業でき、かつ**IAM**ロール側で実際の権限を制御できる

ことになる。

<!-- ================================================= -->
# 許可セットを作成するSSM AutomationのDocument
<!-- ================================================= -->
- Automationの構成

許可セットの作成をAutomationのDocumentで行うには、AutomationのStepを1つ**aws:executeScript**で作成し、必要なスクリプトを配置すれば良い。
スクリプトの構成としては以下の流れになる。
(変数初期化とか入力値チェック等は除外)

なお、注意事項が何点かあり、詳細は

注意事項:[Systems Manager Automation(SSM Automation)の備忘録](https://qiita.com/Ishikawa_Kazuyoshi/items/862bc12523978ba2e1fd)

のブログに記載があるが、以下の表2のようにAutomationのDocumentに権限を付与し、スクリプトを実装しても**実行できない。**

表2
|No | 機能                                  | 必要な権限                 |
|--:|:--------------------------------------|:--------------------------|
|1  | 既存の許可セット情報の取得(同名のチェック) |**sso:ListPermissionSets** **sso:DescribePermissionSet** |
|2  | 許可セットの作成                       |**sso:CreatePermissionSet** |
|3  | インラインポリシーの付与               |**sso:PutInlinePolicyToPermissionSet** |
|4  | 許可の境界の付与                       |**sso:PutPermissionsBoundaryToPermissionSet** |

理由は注意事項のブログを参照。
要は、**sso:PutPermissionsBoundaryToPermissionSet**に該当する**boto3**のメソッドが**SSM Automationの環境では未実装** のため、AttibuteErrorとなってしまうため、別の実装手段を考慮する必要がある。
回避策として、以下のようにStep1を**aws:executeScript**、Step2を**aws:executeAwsApi**として組み合わせることで可能となる。

表3
|Step | 処理内容                 |
|:----|:-------------------------|
|Step1| 既存の許可セット情報の取得(同名のチェック)、 許可セットの作成、インラインポリシーの付与 |
|Step2| 許可の境界の付与 |

上記では、Step1のスクリプトで作成した許可セットのID(ps-xxxxxxxx)を出力値とし、Step2の入力値にする必要がある。手法についての詳細は注意事項を参照。

- Automationの権限
含まれるStepの中に**aws:executeScript**が含まれるため、Documentの想定ロールには**IAM**ロールを必須で指定する必要がある。
この**IAM**ロールにはAWS管理ポリシーとして**AmazonSSMAutomationRole**、および以下の権限が必要となる。

```json:許可セットを作成するために追加で必要な権限
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringLikeIfExists": {
                    "iam:PassedToService": [
                        "ssm.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Sid": "Logs",
            "Effect": "Allow",
            "Action": [
                "logs:*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "sso:ListPermissionSets",
                "sso:DescribePermissionSet",
                "sso:CreatePermissionSet",
                "sso:PutInlinePolicyToPermissionSet",
                "sso:PutPermissionsBoundaryToPermissionSet"
            ],
            "Resource": [
                "arn:aws:sso:::instance/*",
                "arn:aws:sso:::permissionSet/*/*"
            ]
        }
    ]
}
```

- 作業者の権限
以下に必要な権限のポリシーを記述する。**iam:GetSAMLProvider**、**ssm:StartAutomationExecution**、**ssm:StopAutomationExecution**のResourceの部分のアカウントIDはIdentity Centerの委任管理アカウントのIDに置換が必要、SSO_CreatePermissionSetはこのAutomation自体の名称となる。

<details><summary>必要なポリシー(長いので折り畳み)</summary><div>

```json:作業者に必要な権限のIAMポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ds:DescribeDirectories",
                "ds:DescribeTrusts",
                "iam:ListPolicies",
                "access-analyzer:ValidatePolicy",
                "cloudwatch:DescribeAlarms",
                "organizations:DescribeOrganization",
                "organizations:DescribeOrganizationalUnit",
                "organizations:DescribeAccount",
                "organizations:ListRoots",
                "organizations:ListParents",
                "organizations:ListChildren",
                "organizations:ListAccounts",
                "organizations:ListAccountsForParent",
                "organizations:ListOrganizationalUnitsForParent",
                "sso-directory:DescribeDirectory",
                "sso-directory:SearchUsers",
                "sso-directory:SearchGroups",
                "sso-directory:DescribeUser*",
                "sso-directory:DescribeGroup*",
                "sso-directory:DescribeProvisioningTenant",
                "sso-directory:IsMemberInGroup",
                "sso-directory:ListGroupsForMember",
                "sso-directory:ListGroupsForUser",
                "sso-directory:ListMembersInGroup",
                "sso-directory:ListMfaDevicesForUser",
                "sso-directory:ListProvisioningTenants",
                "sso-directory:DisableUser",
                "sso-directory:EnableUser",
                "sso-directory:StartVirtualMfaDeviceRegistration",
                "sso-directory:CompleteVirtualMfaDeviceRegistration",
                "sso-directory:DeleteMfaDeviceForUser",
                "sso-directory:UpdateMfaDeviceForUser"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:GetSAMLProvider"
            ],
            "Resource": "arn:aws:iam::xxxxxxxxxxxx:saml-provider/AWSSSO_*_DO_NOT_DELETE"
        },
        {
            "Effect": "Allow",
            "Action": [
                "sso:ListAccountAssignments",
                "sso:ListAccountAssignmentCreationStatus",
                "sso:ListAccountAssignmentDeletionStatus",
                "sso:ListAccountsForProvisionedPermissionSet",
                "sso:ListCustomerManagedPolicyReferencesInPermissionSet",
                "sso:ListManagedPoliciesInPermissionSet",
                "sso:ListPermissionSets",
                "sso:ListPermissionSetProvisioningStatus",
                "sso:ListPermissionSetsProvisionedToAccount",
                "sso:ListTagsForResource",
                "sso:ListDirectoryAssociations",
                "sso:ListInstances",
                "sso:ListProfileAssociations",
                "sso:ListProfiles",
                "sso:DescribeDirectories",
                "sso:DescribePermissionsPolicies",
                "sso:DescribeRegisteredRegions",
                "sso:DescribeTrusts",
                "sso:GetMfaDeviceManagementForDirectory",
                "sso:GetPermissionSet",
                "sso:GetPermissionsPolicy",
                "sso:GetProfile",
                "sso:GetSSOStatus",
                "sso:GetSharedSsoConfiguration",
                "sso:GetSsoConfiguration",
                "sso:GetTrust",
                "sso:SearchGroups",
                "sso:SearchUsers"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "sso:DescribeAccountAssignment*",
                "sso:DescribeInstanceAccessControlAttributeConfiguration",
                "sso:CreateInstanceAccessControlAttributeConfiguration",
                "sso:UpdateInstanceAccessControlAttributeConfiguration",
                "sso:DescribePermissionSet*",
                "sso:DeletePermissionSet",
                "sso:UpdatePermissionSet",
                "sso:GetInlinePolicyForPermissionSet",
                "sso:GetPermissionsBoundaryForPermissionSet"
            ],
            "Resource": [
                "arn:aws:sso:::instance/*",
                "arn:aws:sso:::permissionSet/*/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "sso:CreateAccountAssignment",
                "sso:DeleteAccountAssignment",
                "sso:ProvisionPermissionSet"
            ],
            "Resource": [
                "arn:aws:sso:::account/*",
                "arn:aws:sso:::instance/*",
                "arn:aws:sso:::permissionSet/*/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "identity-sync:*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:Describe*",
                "ssm:List*",
                "ssm:Get*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:StartAutomationExecution",
                "ssm:StopAutomationExecution"
            ],
            "Resource": [
                "arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:document/SSO_CreatePermissionSet"
            ]
        }
    ]
}
```
</div></details>

- スクリプトの処理内容
Documentの入力値として「スイッチロール先のIAMロールのARN」をStringListで受け取り、それをResourceとして指定した**sts:AssumeRole**のポリシーを組み立て、インラインポリシーとする。

```python:許可セットの作成とインラインポリシーの付与
import re
import json
import boto3
import botocore.exceptions

def script_handler(events, context):
  sso = boto3.client('sso-admin')

  # Documentから入力値を受け取る
  permissionsetname = events['permissionsetname']
  instancearn = events['instancearn']
  assumerolelist = events['assumerolelist']

  # ロールのARNの正規表現
  regp = re.compile('\s*"?(arn:aws:iam::[0-9]{12}:role/[0-9a-zA-Z_\\/-]+)"?,?')
    :(中略)
    # 許可セットを作成(作成した許可セットのARNを保存しておく)
      responsebefore = sso.create_permission_set(
        Name=permissionsetname,
        InstanceArn=instancearn
      )
      permissionsetarn = responsebefore['PermissionSet']['PermissionSetArn']
    :
      # インラインポリシーのJSON表現を組み立てる
      inlinepolicy = dict()
      inlinepolicy['Version'] = '2012-10-17'
      inlinepolicy['Statement'] = list()
      inlinepolicystatement = dict()
      inlinepolicystatement['Effect'] = 'Allow'
      inlinepolicystatement['Action'] = list()
      inlinepolicystatement['Action'].append('sts:AssumeRole')
      inlinepolicystatement['Resource'] = list()
      for assumerole in assumerolelist:
        assumerole = regp.sub(r'\1', assumerole)
        if len(assumerole) > 0:
          inlinepolicystatement['Resource'].append(assumerole)
      inlinepolicy['Statement'].append(inlinepolicystatement)
      # JSON文字列に変換
      inlinejson = json.dumps(inlinepolicy)
      # 許可セットのARNを用いてインラインポリシーを付与
      response = sso.put_inline_policy_to_permission_set(
        InstanceArn=instancearn,
        PermissionSetArn=permissionsetarn,
        InlinePolicy=inlinejson
      )
    :
```

```j:組み立てたインラインポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                ”スイッチロール先1のARN”,
                ”スイッチロール先2のARN”,
                :
            ]
        }
    ]
}
```

<!-- ================================================= -->
# SSOログインまでの流れ
<!-- ================================================= -->
詳細な流れは以下に記載があるが、その中で許可セットを作成する箇所を、SSM AutomationのDocumentを実行して許可セットを作成するようにすれば、作業者に許可セット作成以外の権限を委任しつつ、無秩序な権限を付与されることを(ある程度)防止可能となる。

[AWS Identity Centerの備忘録](https://qiita.com/Ishikawa_Kazuyoshi/items/c9bc9bb2c3a9acdc4261)

<!-- ================================================= -->
## 履歴
<!-- ================================================= -->
- 2023/3/29 初稿