Claude Codeでdecode-authorization-messageを使いAccessDeniedを読む
AWSの拒否に付くEncoded authorization failure messageを、decode-authorization-messageで復号してClaude Codeに読ませる手順と権限設定です。
AWS CLIの実行結果にEncoded authorization failure message:で始まる長い文字列が付いていたら、それはaws sts decode-authorization-messageで復号できます。復号すると、拒否の種類(明示的なDenyか、許可の不在か)、呼び出した主体、操作、リソース、条件キーの値が読めるようになります。
Claude Codeに任せるときの要点は3つあります。復号結果を渡す前にコマンドを許可ルールに入れておくこと、復号する権限(sts:DecodeAuthorizationMessage)を誰に付けるかを決めること、そして復号結果だけでは拒否したポリシーの種類が決まらない場面を知っておくことです。認証そのものが通らない場合はAWS authentication failedの切り分けが別の入口になります。ここで扱うのは、認証は通ったのに操作だけが拒否される場面です。
エンコードされた拒否メッセージが付く場面
AWSのドキュメントによると、権限のないユーザーが操作を要求するとClient.UnauthorizedOperation(HTTP 403)が返り、一部のAWS操作はそれに加えて、拒否の詳細を含むエンコード済みメッセージを返します。すべての操作に付くわけではありません。付くかどうかは、各操作のドキュメントに書かれています。
エンコードされている理由は、認可状況の詳細には、要求したユーザーに見せるべきでない特権的な情報が含まれうるためです。そのため復号には、IAMポリシーでsts:DecodeAuthorizationMessageを許可された主体が必要です。
復号結果に含まれるのは次の5種類です。
- 拒否が明示的なDenyによるものか、明示的なAllowの不在によるものか
- リクエストした主体
- 要求された操作
- 要求されたリソース
- リクエストコンテキストの条件キーの値
復号の手順
コマンドは1つです。--encoded-messageが必須で、長さは1〜10240文字です。
aws sts decode-authorization-message \
--encoded-message "$(cat encoded.txt)" \
--query DecodedMessage --output textencoded.txtには、エラー出力からEncoded authorization failure message:以降の文字列だけを入れます。ターミナルの折り返しで改行が混ざると、復号は失敗します。APIリファレンスによれば、InvalidAuthorizationMessage(HTTP 400)は、トークンに改行のような不正な文字が含まれる場合や、メッセージが期限切れの場合に返されます。改行を除くならtr -d '\n\r ' < raw.txt > encoded.txtのようにします。期限の長さはAPIリファレンスに記載がありません。拒否を受けたらすぐ復号するのが安全です。
出力のDecodedMessageはJSON文字列です。CLIリファレンスの例では、allowedがfalse、explicitDenyがtrueで、matchedStatementsにstatementIdがVisualEditor0、effectがDENYの文が入っています。contextには呼び出し元のARN(arn:aws:sts::111122223333:assumed-role/Developer/Ana)、action(RunInstances)、resource、条件キーの一覧が並びます。人が読むときは、jqに通すと整形されます。
aws sts decode-authorization-message \
--encoded-message "$(cat encoded.txt)" \
--query DecodedMessage --output text | jq .Claude Codeに実行させる権限ルール
Claude Codeの権限ルールは、deny、ask、allowの順に評価され、最初に一致したものが結果を決めます。権限ページには、Bash(aws *)のような広いdenyルールは、Bash(aws s3 ls)のような狭いallowルールがあっても全部ブロックすると書かれています。allowでdenyの例外は作れません。AWSを広くdenyしている環境では、復号コマンドだけ通そうとしてもallowは効かず、denyの粒度を見直すことになります。
広いdenyが無ければ、decode-authorization-messageだけを許可するルールを足します。*はサブコマンドの後ろに置くのが権限ページの作法です。
{
"permissions": {
"allow": [
"Bash(aws sts decode-authorization-message *)",
"Bash(jq *)"
]
}
}jqを足す理由はパイプにあります。権限ページによれば、複合コマンドは区切り(|を含む)ごとに評価され、すべての部分が許可されて初めて確認なしで動きます。aws sts decode-authorization-message ... | jq .の片側だけを許可していると、確認が出ます。
このコマンドはAPIを1回呼ぶだけで、リソースを変えません。それでも組み込みの読み取り専用コマンドの一覧(ls、cat、grepなど)には載っていないので、ルールがなければManualモードでは毎回確認されます。
復号する権限を誰に付けるか
復号には、呼び出す主体に次のIAMポリシーが要ります。APIリファレンスの例は、アクションがsts:DecodeAuthorizationMessage、Resourceが*です。
{
"Effect": "Allow",
"Action": "sts:DecodeAuthorizationMessage",
"Resource": "*"
}Claude Codeに使わせるロールへ付けるか、人が使うデバッグ用のロールにだけ付けるかは設計の分かれ目です。復号が制限されている理由は、情報が特権的だからです。Claude Codeの実行ロールに付ければ作業は速くなりますが、復号結果にはアカウントID、ロール名、リソースのARN、条件キーの値が含まれるので、モデルに渡るのはそれらです。後者にするなら、拒否が出たら人間が復号して、結果から必要な部分だけを貼る運用になります。どちらが合うかは、そのアカウントでモデルに見せてよい情報の範囲で決まります。
最小権限を作る側の話はAccess Analyzerから最小権限を作る手順にあります。ここで付ける権限は、読むだけの調査用と割り切る使い方もあります。
復号結果から拒否の層を切り分ける
復号結果をClaudeに読ませるときは、まず判定の骨格を決めておきます。読む順番は次のとおりです。
復号結果の読み方
- QexplicitDenyがfalseか?答えがtrue(明示的Deny)答えがはい
matchedStatementsのDeny文を探します。statementIdとeffectが手がかりです。どのポリシーに属する文かは、AccessDenied本文のwith an explicit deny in ...の部分と突き合わせます。
許可の不在は、必要な層のどこかでAllowが欠けたという意味
matchedStatementsは空になります。ポリシー評価は層ごとの積集合なので、どの層で欠けたかは別途調べます。
ポリシー評価ロジックのページでは、アイデンティティベースのポリシー、パーミッションバウンダリー、SCPの組み合わせは、結果の権限が積集合になります。つまり、操作はそれぞれの層で許可されている必要があります。どれかの層で明示的なDenyがあれば、Allowは上書きされます。したがって「許可の不在」の拒否は、層が1つに決まりません。
CLIとAPIの復号結果の例には、拒否したポリシーの種類(SCPなのかバウンダリーなのか)を示すフィールドが載っていません。ただし、AccessDeniedの本文自体が手がかりになる場合があります。IAMのトラブルシュートのページによれば、多くのエラーメッセージはUser: <ARN> is not authorized to perform: <action> on resource: <ARN> because <context>の形式で、contextにポリシーの種類が入り、場合によってはポリシーのARNも入ります。ただし、このメッセージ形式に対応しないサービスもあり、内容はサービスによって違います。
| 本文のフレーズ | 疑う層 | 次に見るもの |
|---|---|---|
because no identity-based policy allows the ... action | 疑う層アイデンティティベースのポリシー(許可の不在) | 次に見るもの主体に付いたポリシーにAllowを足す |
with an explicit deny in an identity-based policy | 疑う層アイデンティティベースのポリシー(明示的Deny) | 次に見るものARNがあればそのポリシーのDeny文 |
because no service control policy allows the ... action | 疑う層SCP(許可の不在) | 次に見るもの組織のSCPにAllowがあるか |
with an explicit deny in a service control policy | 疑う層SCP(明示的Deny) | 次に見るものARNがあればそのSCPのDeny文 |
because no permissions boundary allows the ... action | 疑う層パーミッションバウンダリー(許可の不在) | 次に見るものバウンダリーのポリシーにAllowがあるか |
with an explicit deny in a permissions boundary | 疑う層パーミッションバウンダリー(明示的Deny) | 次に見るものバウンダリーのDeny文 |
表は3つの層に絞った抜粋で、同じページにはセッションポリシー、リソースベースのポリシー、ロールの信頼ポリシー、VPCエンドポイントポリシー、リソースコントロールポリシー(RCP)の例もあります。
Claudeに判定させるときの渡し方
材料は2つあります。復号したJSONと、元のAccessDenied本文(because以降が残っていること)です。エンコード済み文字列は長いので、プロンプトに貼るより、ファイルで渡すほうが扱いやすくなります。
decoded.json は aws sts decode-authorization-message の結果です。
error.txt は元のエラー本文(Encoded authorization failure message の行を除く)です。
次の順で答えてください。
1. explicitDeny と matchedStatements から、明示的Denyか許可の不在かを判定する。
2. error.txt の because / with an explicit deny の語句から、拒否した層の候補を挙げる。
3. 層を断定できない場合は、断定せず、確認するコマンドを列挙する(実行はしない)。3番目が肝心です。復号結果だけで層が決まらないときに、Claudeが筋の通った説明を作ってしまうのを防ぎます。確認コマンドの候補は次のとおりです。
| 調べたいこと | コマンド | ドキュメントの説明 |
|---|---|---|
| ロールのバウンダリー設定 | コマンドaws iam get-role --role-name <name> | ドキュメントの説明出力にPermissionsBoundary(型とARN)が含まれる |
| ロールに付いた管理ポリシー | コマンドaws iam list-attached-role-policies --role-name <name> | ドキュメントの説明ロールにアタッチされた管理ポリシーを返す |
| アカウントに効くSCP | コマンドaws organizations list-policies-for-target --filter SERVICE_CONTROL_POLICY --target-id <account-id> | ドキュメントの説明対象に直接アタッチされたポリシーを返す |
SCPの調査は組織側の仕事になりがちです。ポリシー評価ロジックのページにも、SCPが特定のリクエストを拒否しているかを知るには、AWS Organizationsの管理者へ連絡するよう書かれています。またlist-policies-for-targetは、対象に直接付いたポリシーだけを返す点にも注意が必要です。上位のOUやルートに付いたSCPは、この1回では見えません。
これらの調査コマンドもdescribeやlist、get系です。権限を絞る考え方はdescribe系だけ許可して削除はTerraformへ回す構成と同じで、読み取りだけをallowに入れます。
つまずきやすい点
落とし穴
復号に失敗して400が返る
InvalidAuthorizationMessageは、不正な文字(改行など)か期限切れで起きます。端末幅で折り返された出力をそのままコピーすると改行が入ります。ファイルに保存し、改行を取ってから渡します。
落とし穴
復号コマンド自体が拒否される
復号にはsts:DecodeAuthorizationMessageの許可が要ります。元の操作と同じ主体で復号する場合、その主体に権限がなければ復号もできません。別のプロファイルを--profileで指定して実行する方法があります。
- 復号結果の
contextに出てくるのは、拒否されたその呼び出しの条件キーです。ポリシーに書いた条件の定義そのものではありません。条件が原因の拒否では、キーの値と、ポリシー側の条件の両方を見比べます。 - Claudeに調査用のロールを使わせるときは、
--profileをコマンドごとに明示させます。実行ロールが拒否された元の操作と、復号用のロールを取り違えないためです。 - 付くのはあくまで「一部の操作」です。エンコード済みメッセージの無い拒否は、この方法では読めません。その場合は本文の
because以降が主な手がかりになります。
拒否の原因を見つけたあとにポリシーを書き直すなら、プラグインでIAMのskillsを使う構成をaws-coreプラグインの記事で扱っています。
まとめ
復号はaws sts decode-authorization-messageの1コマンドで済み、難しいのはその周囲の判断です。許可ルールはBash(aws sts decode-authorization-message *)と、パイプを使うならjqのぶんまで。復号権限を実行ロールに置くか人の側に置くかで、モデルに渡る情報の範囲が変わります。そして復号結果のexplicitDenyがfalseなら、層は決まっていません。そのときはAccessDenied本文のbecause以降と、get-roleやlist-policies-for-targetの結果を合わせて、積集合のどこが欠けているかを確かめます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- docs.aws.amazon.com/STS/latest/APIReference/API_DecodeAuthorizationMessage.html
- docs.aws.amazon.com/cli/latest/reference/sts/decode-authorization-message.html
- docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_access-denied.html
- docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.h…
- code.claude.com/docs/en/permissions