AWS Identity Centerの権限付与の課題
Identity Centerの権限付与には以下のセキュリティ上の大きな課題があり、そのままでは運用が困難となる。
- 許可セットに対し、アタッチするマネージドポリシーを制限できないため、例えばAdministratorAccessのような強い権限のポリシーをアタッチ出来てしまう
- インラインポリシーの作成権限を付与してしまうと、そのポリシー内容は制限できないため、AdministratorAccessのポリシー内容をコピーしてインラインポリシーを作成することで権限拡大が出来てしまう
詳細は以下のブログでも記載している。
AWS Identity Centerの備忘録
これは、Identity Center系の権限のみを許可した作業者(つまり、IAM系の権限は許可してしない)であってもAdministratorAccessやそれに準じた権限へ昇格を行えてしまうことになる。つまり、Identity Center系の運用作業を他社へ業務委託するような場合に特に大きな問題となる。
以下に、それを解決する策を検討してみる。
(以下、作業者とはIdentity Centerで許可セットを作成する者を指す)
-
作業者にインラインポリシーの作成権限を付与しない
作業者に対しsso:PutInlinePolicyToPermissionSetの権限を付与しなければインラインポリシーの作成を禁止することは可能である。
しかし、AWSマネージドポリシーでは定義できないカスタム権限を許可セットへ付与したい場合、都度カスタマーマネージドポリシーを事前に作成しておく必要が出てくる。
そのため運用上許可セットを作成していく最中にIAM系の作業が必須となり、Identity Center系の作業だけを業務委託することができない。
また、この方法ではAWSマネージドポリシーでAdministratorAccessのような強い権限を付与出来てしまうことは防止できない。 -
何らかのプログラムを介して許可セットを作成するようにし、許可セットへ付与するポリシーの自由度を制限する
作業者には許可セットを作成する権限である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)の備忘録
のブログに記載があるが、以下の表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、および以下の権限が必要となる。
{
"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自体の名称となる。
必要なポリシー(長いので折り畳み)
{
"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"
]
}
]
}
- スクリプトの処理内容
Documentの入力値として「スイッチロール先のIAMロールのARN」をStringListで受け取り、それをResourceとして指定したsts:AssumeRoleのポリシーを組み立て、インラインポリシーとする。
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
)
:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
”スイッチロール先1のARN”,
”スイッチロール先2のARN”,
:
]
}
]
}
SSOログインまでの流れ
詳細な流れは以下に記載があるが、その中で許可セットを作成する箇所を、SSM AutomationのDocumentを実行して許可セットを作成するようにすれば、作業者に許可セット作成以外の権限を委任しつつ、無秩序な権限を付与されることを(ある程度)防止可能となる。
履歴
- 2023/3/29 初稿