共通ロールのAssumeRoleで特定ユーザーだけに権限を足す方法(SourceIdentity)
2026年7月18日 に投稿 • 3 分 で読めます • 451 語ログインアカウントからプロジェクトアカウントにAssumeRoleする構成で、切り替え先の共通ロールは1本のまま、リードエンジニア数名にだけ iam:PassRole を許可したくなりました。sts:SourceIdentity で解決できたので備忘録兼同じことをする人に向けて残しておきます。地味にハマりどころが多かったです。
やりたかったこと
前提はよくあるSSO未導入なAWSマルチアカウントの構成です。
- ログインアカウント:IAMユーザーがいるログイン用アカウント。
- プロジェクトアカウント:実際の作業先。ログイン側から共通ロール(ここでは
developer_roleとします)にAssumeRoleして入る。
この共通ロールは開発者全員が使います。で、そのうちリード数名にだけ特定の権限を追加で許可したい。
(今回はECS RunTask実行時にECSタスクロールを渡すためのiam:PassRoleを追加付与したかった)
ロールは分けず共通ロール1本のまま、ユーザー単位で権限に差を付けたいという贅沢な要望を実現するのが目標です。
はいそこ、素直にロール分ければいいだろとか言わないでください。
大量のプロジェクトを共通の権限基盤で管理してるとこに個別対応入れるのめんd大変なんだから。
(あとユーザーグループの作成上限のハードリミット500に迫ってて余裕がない……)
というわけで悪あがきしたお話です。
やり方としては、拡張用のロールを別に切る・セッションタグ(ABAC)で admin/standard を属性管理する、といった選択肢もあるようですが、今回は対象がリード数名と少なく異動も頻繁じゃないので、プロジェクト側で名前でベタ書きするだけで管理できる SourceIdentity が一番お手軽シンプルなためこれで進めました。
(ログインアカウント側で個々のユーザーのタグまで管理するのがしんどいという本音もあったりしないこともない)
aws:username では縛れない
真っ先に検討して撃沈したのが誰しも考えそうなシンプルにユーザー名で縛る方法です。
プロジェクト側で「呼んできたユーザー名」を条件にすればこんなの解決じゃんと思いますが、aws:username はIAMユーザーのときだけ有効で、AssumeRoleしたロールセッションでは空になり使えません。
aws:userid のほうはロールセッションだと <ロールのユニークID>:<セッション名> になりますが、セッション名は呼び出し側が好きに付けられる=詐称し放題なので認可には使えません。(自分も普段は識別用に好き勝手設定してたりする)
つまり、AssumeRole先に渡せるパラメータで、元のユーザーが誰かを信頼できる形で使うには専用の仕組みが要ります。それが今回主役の SourceIdentity です。
設計:SourceIdentityで本人性を運ぶ
aws:SourceIdentity は「このセッションの後ろにいる人間は誰か」を運ぶためのキーです。AssumeRole時に確定して、あとから変えられません。この変更不可という性質のおかげで、切り替え先でも認可や監査にも使えます。
重要なのは 「ログイン側で、SourceIdentityがユーザー名と一致するときだけ許可する」 ことです。
ここを縛らないと、普通のユーザーが勝手に他人のSourceIdentityを名乗って昇格できてしまいます。
詐称されたらこの方式は終了なのでSourceIdentityを設定するならユーザー名一致強制は必須です。
いじるポリシーは3か所です。
1. ログインアカウント側(IAMユーザーのアイデンティティベース)
AssumeRoleとSetSourceIdentityの両方をActionに入れて、SourceIdentityが自分のユーザー名と一致することを条件にします。
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole",
"sts:SetSourceIdentity"
],
"Resource": "arn:aws:iam::PROJECT_ACCOUNT_ID:role/developer_role",
"Condition": {
"StringLikeIfExists": {
"sts:SourceIdentity": "${aws:username}"
}
}
}ここは注意で、Action に sts:SetSourceIdentity を入れるのと、Condition に sts:SourceIdentity を書くのは別物です。Conditionはあくまで「許可されたActionの絞り込み」であって、それ単体でSetSourceIdentityというActionを許可してはくれません。両方書きます。
ちなみに演算子は
StringLike(SourceIdentity必須)でもStringLikeIfExists(付けるなら一致必須/付けなくてもOK)でもいいです。前者は「必ず名乗らせる」、後者は「特権を使わないセッションはSourceIdentityなしでも通す」という設計になります。どちらにしても特権行使は後段のPassRole条件で弾かれるので、全員に設定強制しない場合はStringLikeIfExistsにした方が追加付与したい権限を使わない人が巻き込まれないのでオヌヌメ。
2. プロジェクトアカウント側(信頼ポリシー)
当たり前ですがsts:SetSourceIdentity を許可しておかないと、--source-identity 付きのAssumeRoleがそもそも弾かれます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::LOGIN_ACCOUNT_ID:root"
},
"Action": [
"sts:AssumeRole",
"sts:SetSourceIdentity"
]
}
]
}3. プロジェクトアカウント側(権限ポリシー)
ベース権限は全員に無条件、追加のPassRoleだけSourceIdentityで開けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Base",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"ec2:Describe*"
],
"Resource": "*"
},
{
"Sid": "PassRoleForLeadEngineers",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::PROJECT_ACCOUNT_ID:role/EcsTaskRole",
"Condition": {
"StringEquals": {
"aws:SourceIdentity": [
"alice",
"bob"
],
"iam:PassedToService": "ecs-tasks.amazonaws.com"
}
}
}
]
}PassRoleは権限昇格の入口になりやすいので、Resource を * にしない(渡していいロールARNを列挙する)、iam:PassedToService で渡し先サービスを絞る、の2つはちゃんとやります。(この辺は今回の本題ではないので適宜制限してください)
AWS_PROFILE運用のハマりどころ
普段 AWS_PROFILE でプロファイルを切り替えて使っている人は、ここが一番のトラップになります。
~/.aws/config の普通のロール切り替え(role_arn + source_profile)には、SourceIdentityを渡す仕組みがありません。
試しに source_identity = alice と書いてもエラーにならず、しれっと無視されました。
結果、SourceIdentityが空のままAssumeRoleされて、PassRole条件にマッチせず AccessDenied。しかも黙って無視されるだけなので特定にも時間がかかりました。
AWS CLIのドキュメントにそんなパラメータ見当たらないですが一応botocoreの実装も追ってみました。
が、
botocore/credentials.py を読むと、configから読んでいるのは role_session_name / external_id / mfa_serial / duration_seconds の4つだけで、source_identity はどこにも出てきません。ないものは当然読めません。
というわけで、プロファイル設定だけでSourceIdentityを乗せる方法は存在しません。面倒ですがcredential_process でパラメータをコネコネすることにしました。
credential_process の実装
credential_process がやることは「Version:1 の決まった形式のJSONをstdoutに吐く」だけです。
AWS CLIの --query(JMESPath)でキー名を合わせてしまえば、整形用の別コマンドを挟まずCLI単体で完結します。
~/.aws/config:
[profile project-alice]
credential_process = /usr/local/bin/assume-with-identity.sh alice arn:aws:iam::PROJECT_ACCOUNT_ID:role/developer_role
region = ap-northeast-1assume-with-identity.sh:
#!/usr/bin/env bash
set -euo pipefail
# Usage: assume-with-identity.sh <identity> <role-arn> [source-profile]
# identity : SourceIdentity と RoleSessionName に使う値(通常は IAM ユーザー名)
# role-arn : AssumeRole 先のロール ARN
# source-profile: assume 元のプロファイル名(省略時は "default")
IDENTITY="${1:?usage: assume-with-identity.sh <identity> <role-arn> [source-profile]}"
ROLE_ARN="${2:?usage: assume-with-identity.sh <identity> <role-arn> [source-profile]}"
SOURCE_PROFILE="${3:-default}"
aws sts assume-role \
--profile "$SOURCE_PROFILE" \
--role-arn "$ROLE_ARN" \
--role-session-name "$IDENTITY" \
--source-identity "$IDENTITY" \
--query '{Version: `1`, AccessKeyId: Credentials.AccessKeyId, SecretAccessKey: Credentials.SecretAccessKey, SessionToken: Credentials.SessionToken, Expiration: Credentials.Expiration}' \
--output jsonミソは --query の中の `1` です。バッククォートで囲むとJMESPathのリテラル数値になって、Version が整数の 1 で出ます(credential_processはVersionを整数で欲しがります)。
出力がこの形になっていればOKです。
{
"Version": 1,
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"SessionToken": "...",
"Expiration": "2026-07-15T12:34:56+00:00"
}あとは AWS_PROFILE=project-alice に切り替えるだけで、勝手に --source-identity が乗ります。使う側の操作は今までどおりです。
SourceIdentityが乗っているか確認する方法
CloudTrailからAssumeRole イベントを見ると、想定どおりのコンテキストが乗っているか事実確認できます。
userIdentity.type… 呼び出し主体がちゃんとIAMUserかrequestParameters.sourceIdentity… SourceIdentityが実際に飛んでいるか
get-caller-identity にはSourceIdentityが出ないので、乗っているかの確認はCloudTrailで見るのが確実です。
セキュリティ・運用で意識していること
最後に、この構成を組む上で注意すべき点をメモしておきます。
- 本人性の担保はログイン側の
sts:SourceIdentity == ${aws:username}強制に全乗っかり。 このロールへのAssumeRole経路を増やさない前提なので複数経路から使用されるロールへの設定は慎重に行なってください。(ログインアカウントに絶対の統制が効いてる場合のみ使える手段です) - PassRoleなどのスコープはプロジェクト側の責任。 AssumeRole先で引き受けるロールはログインアカウントから手出し出来ないのでどんな権限を付与するかから全ての責任をプロジェクト側で負うことになります。
- ユーザー名のリストはポリシー内ベタ書き配列。 数名なら管理もそこまで手間にはならないですが、退職・異動時の削除もプロジェクト側の責任で行うことになります。人数が増えたり中央管理に寄せられる場合はセッションタグ(ABAC)でadmin/standardを属性管理する方式への移行を検討した方がいいでしょう。(そもそもその時は複雑な制御入れずに素直にロール自体分割すべきだとも思うけど)
まとめ
- 切り替え先で「元のユーザーが誰か」を信頼できる形で使うなら
sts:SourceIdentity。変更不可なので認可にも監査にも使える。 - 本人性の担保は「ログイン側で
sts:SourceIdentity == ${aws:username}を強制」がキモ。 .aws/configはSourceIdentityを運べない。credential_processを使う。整形は--queryの`1`リテラルでCLI単体で済む。