IAMロール再作成の落とし穴:信頼関係ポリシーのプリンシパルIDが変わるとスイッチロールができなくなる問題
あしざわです。
IaCで管理しているIAMロールを削除した後、同じIaCテンプレートで再デプロイしたら元々できていたはずの他のAWSアカウントへのスイッチロールが急にできなくなってしまった。スイッチ先のIAMロールは変更していないのに。
そんな経験したことがある方はいませんか?
その原因はプリンシパルIDの違いにあるかもしれません。
本記事では、そんな事象を解説・検証します。
概要
同じ設定のIAMロールを作成し直しただけなのに、なぜ以前までできていたスイッチロールができなくなってしまったのでしょうか?
前述した図に表すとこのようになります。
原因は、IAMロールの信頼関係ポリシーの仕様にあります。
ここでいう信頼関係ポリシーは、スイッチ先のIAMロール(target-iam-role)の「信頼されたエンティティ」のことを指します。
このように特定のIAMリソースをPrincipalに設定し、sts:Assumeのアクションのみを許可するJSON定義です。見覚えがある方も多いと思います。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/sample-role"
},
"Action": "sts:AssumeRole"
}
]
}
ここまでを図にしてみました。
信頼関係ポリシーの中の更に1つの要素、Principal
の仕様に「特定の IAM ロール/ユーザーを指し示す ARN が含まれている場合、その ARN をポリシーに保存するときに、IAM がロール/ユーザーの一意のプリンシパル ID に変換」する、というものがあります。
つまり、見た目上はARNの形式でPrincipalを設定しているように見えますが、実際は一意のプリンシパルIDに変換・設定されている のです。
本件は、AWS公式ドキュメントの以下箇所にそれぞれ記載があります。
AWS公式ドキュメントでは、プリンシパルIDについて個別にまとまったページは見つからなかったのですが、そのARNがどちらのIAMリソースなのかによってどんなプリンシパルIDに変換されるか異なるようです。
- (IAMユーザーの場合)
AIDA
で始まる文字列 - (IAMロールの場合)
AROA
で始まる文字列(?確認中)
参考:IAM ロールの「信頼されたエンティティ」に覚えのない IAM ユーザー ID が設定されている場合の対処法 | DevelopersIO
ここまでを図に表すと以下のようになります。
以上のような背景から、同じ名前・同じ設定でIAMロールを再作成しているように見えても、プリンシパルIDの差異によってスイッチロールが失敗してしまっているわけです。
ここまでで主題の件についてはよく理解できたかと思います。以降のセクションでは実際に手を動かして事象を確認してみます。
検証してみる
以下のロールを作成しました。
- スイッチ元IAMロール(source-iam-role)
- 信頼関係ポリシーで任意のIAMユーザーからのsts:AssumeRoleを許可
- AWS管理ポリシーのReadOnlyAccessをアタッチ
- インラインポリシーで、スイッチ先IAMロール(target-iam-role)のsts:AssumeRoleを許可
- スイッチ先IAMロール(target-iam-role)
- 信頼関係ポリシーで、スイッチ元IAMロール(source-iam-role)のsts:AssumeRoleを許可
- AWS管理ポリシーのReadOnlyAccessをアタッチ
作成時に利用したCloudFormationテンプレートはこちらです。
スイッチ元IAMロール(source-iam-role)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template to create IAM role with parameterized trust and inline policies'
Parameters:
SwitchAccountId:
Type: String
Description: 'AWS Account ID for the trust policy (account containing the user that can assume this role)'
AllowedPattern: '^[0-9]{12}$'
ConstraintDescription: 'Must be a valid 12-digit AWS account ID'
TargetAccountId:
Type: String
Description: 'AWS Account ID for the inline policy (account containing the target role that can be assumed)'
AllowedPattern: '^[0-9]{12}$'
ConstraintDescription: 'Must be a valid 12-digit AWS account ID'
UserName:
Type: String
Description: 'Name of the IAM user that can assume this role'
Default: 'cm-ashizawa.hiroaki'
TargetRoleName:
Type: String
Description: 'Name of the target role that can be assumed'
Default: 'target-iam-role'
Resources:
SourceIAMRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: 'source-iam-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
AWS: !Sub 'arn:aws:iam::${SwitchAccountId}:user/${UserName}'
Action: 'sts:AssumeRole'
Condition:
Bool:
'aws:MultiFactorAuthPresent': 'true'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/ReadOnlyAccess'
Policies:
- PolicyName: 'AssumeTargetRolePolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action: 'sts:AssumeRole'
Resource: !Sub 'arn:aws:iam::${TargetAccountId}:role/${TargetRoleName}'
Outputs:
RoleARN:
Description: 'ARN of the created IAM role'
Value: !GetAtt SourceIAMRole.Arn
RoleName:
Description: 'Name of the created IAM role'
Value: !Ref SourceIAMRole
スイッチ先IAMロール(target-iam-role)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template to create target IAM role'
Parameters:
SourceAccountId:
Type: String
Description: 'AWS Account ID where the source-iam-role exists'
AllowedPattern: '^[0-9]{12}$'
ConstraintDescription: 'Must be a valid 12-digit AWS account ID'
SourceRoleName:
Type: String
Description: 'Name of the source IAM role that can assume this role'
Default: 'source-iam-role'
Resources:
TargetIAMRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: 'target-iam-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
AWS: !Sub 'arn:aws:iam::${SourceAccountId}:role/${SourceRoleName}'
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/ReadOnlyAccess'
Outputs:
RoleARN:
Description: 'ARN of the created target IAM role'
Value: !GetAtt TargetIAMRole.Arn
RoleName:
Description: 'Name of the created target IAM role'
Value: !Ref TargetIAMRole```
2つのIAMロールは同じAWSアカウント上に作成しても、異なるAWSアカウント上に作ってもどちらでもOKです。
今回は、同じAWSアカウントにしました。
作業前のtarget-iam-roleの信頼関係ポリシーの状態がこちらです。
(信頼関係ポリシーだけを抜粋)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/source-iam-role"
},
"Action": "sts:AssumeRole"
}
]
}
以降で検証を行います。
事前に準備したsource-iam-roleにスイッチロールしておいてください。
まずはこの時点でsource-iam-roleからtarget-iam-roleへのAssumeRoleが実行できるか確認します。
以下コマンドでAssumeRoleの実行、AWSクレデンシャルの環境変数への設定までワンライナーで実行可能です。
eval $(aws sts assume-role --role-arn arn:aws:iam::123456789012:role/target-iam-role --role-session-name my-session | jq -r '.Credentials | "export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\nexport AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\nexport AWS_SESSION_TOKEN=\(.SessionToken)"')
正しくコマンドが実行できたら何も出力されません。
続いて、以下コマンドを実行して現在使用している AWS 認証情報を確認します。
aws sts get-caller-identity
以下のように、ARN欄にtarget-iam-role
が含まれているARNが表示されたら正しくスイッチロールできたことがわかります。
{
"UserId": "AROAxxxxxxxxxxxx:my-session",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/target-iam-role/my-session"
}
それではsource-iam-roleを削除し、再作成しましょう。私の環境では、CloudFormationスタックの削除・再作成で実施しました。
一旦source-iam-roleにスイッチバックしてから、再度target-iam-roleへスイッチロールしてみましょう。先ほどと同じコマンドを使います。
eval $(aws sts assume-role --role-arn arn:aws:iam::123456789012:role/target-iam-role --role-session-name my-session | jq -r '.Credentials | "export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\nexport AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\nexport AWS_SESSION_TOKEN=\(.SessionToken)"')
コマンドの実行結果例がこちら。スイッチロールできなくなっていますね。
An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::123456789012:assumed-role/source-iam-role is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/target-iam-role
この時点でtarget-iam-roleの信頼関係ポリシーを確認すると、PrincipalがAROA
で始まる文字列に置き換わっています。
この修正がCloudTrailなどの証跡として記録されていないのか気になりますよね。
Configのリソースタイムラインからtarget-iam-roleを確認してみたところ、修正されたようなイベントは見つかりませんでした。CloudTrail等に記録されないAWS InternalのAPIで修正していると思われます。
確認したtarget-iam-roleの信頼関係ポリシーを修正し、AROA
の文字列をsource-iam-roleのARNに差し替えます。
私はマネジメントコンソールから修正しようとしたのですが、修正前の状態でIAMのバリデーションで警告が表示されていました。
Invalid Role Reference: Principal 要素には IAM ロール ID AROAxxxxxxxxxx が含まれています 。代わりにロール ARN を使用することをお勧めします。
ARNに差し替えたところ警告が消え、ポリシーの更新を行いました。
再度AssumeRoleのコマンドを実行すると、何も出力されないはずです。
eval $(aws sts assume-role --role-arn arn:aws:iam::123456789012:role/target-iam-role --role-session-name my-session | jq -r '.Credentials | "export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\nexport AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\nexport AWS_SESSION_TOKEN=\(.SessionToken)"')
AWS認証情報を確認するコマンドで、最初と同じ出力結果になれば、修正が完了です。
> aws sts get-caller-identity
{
"UserId": "AROAxxxxxxxxxxxx:my-session",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/target-iam-role/my-session"
}
最後に
今回は「IAMロール再作成の落とし穴:信頼関係ポリシーのプリンシパルIDが変わるとスイッチロールができなくなる問題」について紹介しました。
このようなケースではIAMロールの削除によって暗黙的な信頼ポリシーの修正が行われることを覚えておくだけで、トラブルシューティングの際に役立ちそうです。
ちなみにIAMユーザーの再作成によってプリンシパルIDの置き換えが発生する件については、既にDevelopersIOブログでいくか執筆されています。
IAMユーザーだけでなくIAMロールでも同じ事象が起きる、ということを改めて覚えておきましょう。
以上です。