メタップスアドベントカレンダー二日目の記事です。
今回はAWS IAM Identity Centerで一時的な権限昇格フローを作るということで、Slack + AWS Chatbot + SSM AutomationでPIMっぽい仕組みの作成方法をご紹介します。
はじめに
AWS の運用をしていると、こんな悩みがありました。
- 普段は ReadOnly / 開発者権限で十分だけど、たまにだけ管理者権限がほしい
- とはいえ、常にAdmin権限を付けっぱなしにするのは怖い
- 「誰がいつ権限を上げたのか」もちゃんと残しておきたい
本記事では、AWS IAM Identity Center(以下 IIC)を使って、
- 通常時は弱い権限だけ
- 申請 → 承認 → 一定時間だけ強い権限を付与
- 時間が来たら自動で権限を剥がす
という、いわゆる一時的な権限昇格(PIMっぽい仕組み)を実現した方法を紹介します。
もともとは Systems Manager Change Manager を使っていましたが、サービス終了に伴い、現在は
- Slack ワークフロー
- AWS Chatbot
- AWS Systems Manager Automation
の組み合わせで同じ体験を再現しています。
ゴール
この記事のゴールは下記です。
- IICのPermission Set/グループを使って「一時管理者」権限を用意する
- SSM Automation で「追加 → 待機 → 削除」の一連の流れを自動化する
- Slackから「申請 → 承認 → 自動実行」までを流せる
アーキテクチャ概要
全体アーキテクチャ図
- 普段は
Developersグループだけ - 権限昇格中だけ
IIC-Admin-Tempにも入るイメージ
Change Manager時代との比較
この記事では現行のSlack + AWS Chatbot + SSM Automation構成を中心に紹介していますが、もともとはAWS Systems Manager Change Managerを使っていました。
ざっくりいうと
- やっていること(IIC のグループに一時的に入れる / 外す)は同じ
- 「承認フローの UI と実行トリガー」だけをChange ManagerからSlackに置き換えた
という形になります。
ざっくり比較表
| 項目 | Change Manager 時代 | 現行(Slack + Chatbot + Automation) |
|---|---|---|
| 申請 UI | Change Request画面から入力 | Slackワークフローのフォーム |
| 承認 UI | Change Managerの承認画面 | Slackメッセージの「承認」ボタン |
| 承認ロジック | Change Templateで承認ステップを定義 | Slackワークフロー側で承認者を限定 |
| 実行トリガー | 承認後に Change Manager が Document 実行 | 承認後にChatbot経由で Automation 実行 |
| 実行フロー本体 | SSM Document(権限昇格フロー)は同じ | SSM Document(権限昇格フロー)は同じ |
| 通知 | Change Managerの通知設定 | Slackメッセージ/Chatbotのレスポンス |
| スケジュール | Change Templateで実行時間を指定可能 | 必要なら別途EventBridgeなどで実装 |
| 仕組み自体の変更管理 | Change Manager自体の変更も承認制 | 管理者のみ変更履歴を確認可 |
「Change Managerのテンプレートが担っていた仕事を、SlackワークフローとChatbotにバラして移植した」イメージです。
申請はSlackの方がしやすいですが、仕組み自体の変更管理はChange Managerの方がガチガチに組めました。
Change Manager版の構成イメージ
当時の構成は、おおよそ次のような流れでした。
Change Templateに
- 実行する Document(=一時権限昇格フロー)
- 承認ステップ(承認者)
- 申請時の通知
- 実行スケジュール
をまとめて定義しておき、Change Requestからそれを呼び出す形でした。
実装ステップ
Step 1. IAM Identity Centerの準備
-
一時管理者用 Permission Setを作成
-
コンソールから IIC →「Permission sets」 →「Create」
-
ベースの AWS マネージドポリシーとして
AdministratorAccessなどを選択- 実運用では、必要な操作だけに絞ったカスタムポリシーにするのがおすすめ
-
名前例:
Temporary-AdministratorAccess
-
-
一時管理者用グループを作成
- IIC の「Groups」から
IIC-Admin-Tempのようなグループを作成 - さきほどの
Temporary-AdministratorAccessPermission Setを、このグループに紐付け
- IIC の「Groups」から
-
対象アカウントへの割り当て
- 権限昇格したいAWSアカウントに対して、
IIC-Admin-Tempグループを割り当て - これで、グループにユーザーを追加すると、そのユーザーが対象アカウントで一時管理者権限を使える状態になります
- 権限昇格したいAWSアカウントに対して、
Step 2. SSM Automation Documentの作成
次に、ユーザーをグループへ追加するAutomation Documentを作成します。
やりたいことは、
- ユーザーをグループに追加
- 指定時間スリープ
- ユーザーをグループから削除
という 3 ステップです。
YAML の骨組み
schemaVersion: '0.3'
description: ''
parameters:
username:
type: String
groupname:
type: String
sleeptime:
type: String
assumeRole: arn:aws:iam::123456789012:role/testrole
mainSteps:
- name: AddUserFromGroup
action: aws:executeScript
nextStep: Sleep
isEnd: false
inputs:
Runtime: python3.11
Handler: script_handler
Script: |
import boto3
import os
identitystore = boto3.client('identitystore')
IDENTITY_STORE_ID = 'd-*****'
def script_handler(event, context):
username = event['username'] # 例: user@example.com
group_name = event['group_name'] # 例: "AdminGroup"
# ユーザーの ID を取得
user_response = identitystore.list_users(
IdentityStoreId=IDENTITY_STORE_ID,
Filters=[{
'AttributePath': 'UserName',
'AttributeValue': username
}]
)
if not user_response['Users']:
return {"status": "error", "message": f"User '{username}' not found."}
user_id = user_response['Users'][0]['UserId']
# グループの ID を取得
group_response = identitystore.list_groups(
IdentityStoreId=IDENTITY_STORE_ID,
Filters=[{
'AttributePath': 'DisplayName',
'AttributeValue': group_name
}]
)
if not group_response['Groups']:
return {"status": "error", "message": f"Group '{group_name}' not found."}
group_id = group_response['Groups'][0]['GroupId']
# すでにメンバーかチェック(冪等性を保つ)
existing_memberships = identitystore.list_group_memberships(
IdentityStoreId=IDENTITY_STORE_ID,
GroupId=group_id
)
for membership in existing_memberships.get('GroupMemberships', []):
if membership['MemberId']['UserId'] == user_id:
return {"status": "ok", "message": f"User '{username}' is already in group '{group_name}'."}
# グループにユーザーを追加
identitystore.create_group_membership(
IdentityStoreId=IDENTITY_STORE_ID,
GroupId=group_id,
MemberId={'UserId': user_id}
)
return {"status": "success", "message": f"User '{username}' added to group '{group_name}'."}
InputPayload:
username: '{{ username }}'
group_name: '{{ groupname }}'
- name: Sleep
action: aws:sleep
nextStep: DeleteUserFromGroup
isEnd: false
inputs:
Duration: PT{{ sleeptime }}M
- name: DeleteUserFromGroup
action: aws:executeScript
isEnd: true
inputs:
Runtime: python3.11
Handler: script_handler
Script: |
import boto3
import os
identitystore = boto3.client('identitystore')
IDENTITY_STORE_ID = 'd-******'
def script_handler(event, context):
username = event['username'] # 例: user@example.com
group_name = event['group_name'] # 例: AdminGroup
# ユーザーの ID を取得
user_response = identitystore.list_users(
IdentityStoreId=IDENTITY_STORE_ID,
Filters=[{
'AttributePath': 'UserName',
'AttributeValue': username
}]
)
if not user_response['Users']:
return {"status": "error", "message": f"User '{username}' not found."}
user_id = user_response['Users'][0]['UserId']
# グループの ID を取得
group_response = identitystore.list_groups(
IdentityStoreId=IDENTITY_STORE_ID,
Filters=[{
'AttributePath': 'DisplayName',
'AttributeValue': group_name
}]
)
if not group_response['Groups']:
return {"status": "error", "message": f"Group '{group_name}' not found."}
group_id = group_response['Groups'][0]['GroupId']
# グループメンバーシップを検索
memberships = identitystore.list_group_memberships(
IdentityStoreId=IDENTITY_STORE_ID,
GroupId=group_id
)
for membership in memberships.get('GroupMemberships', []):
if membership['MemberId']['UserId'] == user_id:
identitystore.delete_group_membership(
IdentityStoreId=IDENTITY_STORE_ID,
MembershipId=membership['MembershipId']
)
return {"status": "success", "message": f"User '{username}' removed from group '{group_name}'."}
return {"status": "ok", "message": f"User '{username}' is not a member of group '{group_name}'."}
InputPayload:
username: '{{ username }}'
group_name: '{{ groupname }}'
outputs:
- AddUserFromGroup.OutputPayload
- DeleteUserFromGroup.OutputPayload
Documentsの実行ロールには、少なくとも以下のような権限が必要です。
identitystore:ListUsersidentitystore:ListGroupMembershipsidentitystore:CreateGroupMembershipidentitystore:DeleteGroupMembership
Step 3. Slackワークフロー + AWS Chatbot
Change Managerが使えていた頃は、Change TemplateにAutomationをぶら下げて承認フローを実現していました。
現在は、同じ思想をSlack ワークフロー + Chatbotで組み直しています。
3-1. 全体フロー(シーケンス図)
3-2. Slackワークフローの設計例
Slack Workflow Builderで、次のようなフローを作成します。
-
トリガー
- 「ショートカット」や「チャンネルトリガー」など、運用しやすい形で
-
入力フォーム
- 対象ユーザー名
- 付与時間(分)
- 理由
- どの権限(グループ)を付与したいか
-
承認メッセージ
- 専用チャンネル(例:
#aws-privilege-request)に、申請内容を投稿 - Block Kit などで「承認」「却下」ボタン付きのメッセージにする
- 専用チャンネル(例:
-
承認ボタン押下時のアクション
- AWS Chatbotへ、以下のようなコマンドを送信(パラメータを埋める)
概念的なイメージ:
@Amazon Q ssm start-automation-execution --document-name <Document名> --parameters {“username”:[<入力>],“groupname”:[<入力>],“sleeptime”:[<入力>]} --region ap-northeast-1
※事前にSlackチャンネルにAmazon Qを連携させておいてください。
4-3. AWS Chatbotの設定ポイント
-
対象の Slackワークスペース/チャンネルにChatbotを紐付ける
-
ChatbotのIAMロール(ガードレール)に以下の権限を付与
ssm:StartAutomationExecutionssm:GetAutomationExecution- 必要に応じて CloudWatch Logs など
Chatbotの権限が不足していると、コマンドは送れているのに Amazon Qからエラーが返ってきてしまうので最初にしっかり確認しておくと安心です。
動作確認の流れ
- 申請者がSlackワークフローから申請を送る
- 承認チャンネルに申請内容が投稿される
- 承認者が内容を確認し、「承認」ボタンを押す
- Chatbot経由でSSM Automationが起動する
- IICアクセスポータルを開き、一時管理者用Permission Setが選択できることを確認
- 設定した時間を過ぎたら、自動的にアクセス権が消えることを確認
※ IIC のセッション反映の都合で、一度ログアウト/再ログインが必要になる場合がある点には注意してください。
これでSlackワークフローを使って権限の付与、削除を自動で行うことができました。
まとめ
- 常時管理者権限を持たせるのではなく、
「申請 → 承認 → 一時的な昇格 → 自動剥奪」のフローにすることでリスクを下げられる - IAM Identity Centerのグループにユーザーを出し入れする仕組みを作ると、
Permission Setの設計を使い回せてシンプル - Systems Manager Documentsで、
「追加 → 待機 → 削除」の一連の流れをコードに閉じ込められる