権限が足りているかをAPIを呼ばずに確かめる

IAMユーザーやロールの権限が足りているかは、実際にAPIを呼んでAccessDeniedが返るかどうかで確かめる場合が多い。
ただしリソースの作成や削除を伴うAPIでは、権限確認のためだけに呼ぶわけにはいかない。

aws iam simulate-principal-policyは、指定したIAMエンティティにアタッチされたポリシーを評価し、そのアクションが許可されるかどうかだけを返す。
APIそのものは実行されないため、s3:DeleteBucketのような操作でも安全に確認できる。

基本的な使い方

--policy-source-arnに評価したいIAMユーザー、グループ、ロールのARNを指定し、--action-namesに確認したいアクションを並べる。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject
{
    "EvaluationResults": [
        {
            "EvalActionName": "s3:GetObject",
            "EvalResourceName": "*",
            "EvalDecision": "allowed",
            "MatchedStatements": [
                {
                    "SourcePolicyId": "AllowS3Read",
                    "SourcePolicyType": "user",
                    "StartPosition": {
                        "Line": 3,
                        "Column": 19
                    },
                    "EndPosition": {
                        "Line": 9,
                        "Column": 10
                    }
                }
            ],
            "MissingContextValues": []
        }
    ]
}

--policy-source-arnにIAMユーザーを指定した場合、そのユーザーが所属するグループのポリシーも評価対象に含まれる。
--action-namess3:GetObjectのようにサービスプレフィックスを含めた形式で指定し、複数のアクションはスペース区切りで並べる。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject s3:PutObject s3:DeleteObject

判定結果の意味

EvalDecisionは次の3種類を返す。

意味
allowed許可されている
explicitDenyDenyステートメントで明示的に拒否されている
implicitDeny許可するポリシーが無いため拒否されている

explicitDenyimplicitDenyはどちらもアクセスできない点は同じだが、対処方法は異なる。
implicitDenyはAllowを追加すれば解決する。
explicitDenyはDenyが優先されるため、Allowを追加しても変わらない。Denyを書いているポリシーそのものを直す必要がある。

MatchedStatementsには判定の根拠となったステートメントが入る。
SourcePolicyIdでどのポリシーが効いたのか、StartPositionEndPositionでポリシードキュメントの何行目のステートメントかが分かる。
implicitDenyの場合は一致したステートメントが存在しないため、MatchedStatementsは空になる。

結果だけを一覧する

JSONをそのまま読むと情報量が多いため、アクション名と判定だけを取り出すと確認しやすい。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject s3:PutObject s3:DeleteBucket iam:CreateUser \
    --query 'EvaluationResults[].[EvalActionName,EvalDecision]' \
    --output text
s3:GetObject    allowed
s3:PutObject    allowed
s3:DeleteBucket implicitDeny
iam:CreateUser  implicitDeny

拒否されたアクションだけを抜き出す場合は--queryで絞り込む。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject s3:PutObject s3:DeleteBucket \
    --query "EvaluationResults[?EvalDecision!='allowed'].EvalActionName" \
    --output text
s3:DeleteBucket

リソースを指定する

--resource-arnsを省略すると、リソースは*として評価される。
ポリシーが特定のバケットだけを許可している場合、*での評価は実態と合わない。対象リソースのARNを指定する。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject \
    --resource-arns arn:aws:s3:::example-bucket/data.csv

存在しないリソースのARNも指定できるため、これから作るリソースに対する権限も事前に確認できる。

複数のリソースを指定した場合、リソースごとの判定はResourceSpecificResultsに入る。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject \
    --resource-arns arn:aws:s3:::example-bucket/data.csv arn:aws:s3:::other-bucket/data.csv \
    --query 'EvaluationResults[].ResourceSpecificResults[].[EvalResourceName,EvalResourceDecision]' \
    --output text
arn:aws:s3:::example-bucket/data.csv     allowed
arn:aws:s3:::other-bucket/data.csv       implicitDeny

S3のようにバケットとオブジェクトでARNが分かれるサービスでは、アクションごとに必要なARNの形式が異なる。
s3:ListBucketはバケットのARN、s3:GetObjectはオブジェクトのARNを指定する。

条件キーの値を与える

ポリシーのConditionで参照している条件キーは、シミュレーション時に値を渡さなければ評価できない。
値が不足している条件キーはMissingContextValuesに並ぶ。

{
    "EvaluationResults": [
        {
            "EvalActionName": "s3:GetObject",
            "EvalResourceName": "arn:aws:s3:::example-bucket/data.csv",
            "EvalDecision": "implicitDeny",
            "MatchedStatements": [],
            "MissingContextValues": [
                "aws:SourceIp"
            ]
        }
    ]
}

MissingContextValuesが空でない結果は、条件を満たせなかったのではなく判定に必要な材料が足りていない状態である。
--context-entriesで値を渡して評価し直す。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject \
    --resource-arns arn:aws:s3:::example-bucket/data.csv \
    --context-entries ContextKeyName=aws:SourceIp,ContextKeyValues=203.0.113.10,ContextKeyType=ip

ContextKeyTypeにはキーのデータ型を指定する。stringnumericbooleanipdatebinaryと、それぞれの複数値版であるstringListなどが指定できる。
IPアドレスならip、MFAの有無ならboolean、時刻ならdateを使う。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject \
    --resource-arns arn:aws:s3:::example-bucket/data.csv \
    --context-entries \
        ContextKeyName=aws:SourceIp,ContextKeyValues=203.0.113.10,ContextKeyType=ip \
        ContextKeyName=aws:MultiFactorAuthPresent,ContextKeyValues=true,ContextKeyType=boolean

条件キーの値を変えながら実行すると、社内IPからのみ許可するポリシーが意図どおりに効いているかを確認できる。

必要な条件キーを調べる

渡すべき条件キーはaws iam get-context-keys-for-principal-policyで一覧できる。
アタッチされたポリシー内で参照されている条件キーがすべて返る。

$ aws iam get-context-keys-for-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice
{
    "ContextKeyNames": [
        "aws:SourceIp",
        "aws:MultiFactorAuthPresent"
    ]
}

なおaws:PrincipalAccountaws:PrincipalOrgIDaws:UserNameのようなプリンシパルと組織に関する条件キーは、シミュレーターが自動で値を埋める。
これらの値を--context-entriesで渡す必要はない。

ロールの権限を確認する

ロールもそのままARNを指定できる。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:role/example-role \
    --action-names ssm:SendCommand

指定するのはロールのARNである。
aws sts get-caller-identityが返すのはarn:aws:sts::123456789012:assumed-role/example-role/session-nameのような引き受け済みセッションのARNのため、ロール名を取り出してarn:aws:iam::123456789012:role/example-roleの形式に組み立てる。

シミュレーターが評価するのはロールにアタッチされた権限ポリシーである。
ロールを引き受けられるかどうかを決める信頼ポリシーは評価対象に含まれない。

アタッチしていないポリシーを試す

--policy-input-listに渡したポリシーは、そのエンティティにアタッチされているものとして評価される。
実際にアタッチされるわけではないため、アカウントの状態は変わらない。

権限が不足している場合に、追加するポリシーで解決するかを事前に確認できる。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:DeleteBucket \
    --policy-input-list '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:DeleteBucket","Resource":"*"}]}'

ポリシーが長い場合はファイルから読み込む。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:DeleteBucket \
    --policy-input-list "$(cat policy.json)"

--policy-input-listはリスト型のパラメータであるため、file://policy.jsonのようにポリシードキュメントのファイルを直接指定してはいけない。
AWS CLIはファイルの内容をJSONの配列として解釈できず、文字列として扱って1文字ずつリストの要素に分解して送信する。
--debugを付けると、次のように分解された状態が確認できる。

'PolicyInputList.member.1': '{', 'PolicyInputList.member.2': '"', 'PolicyInputList.member.3': 'V', ...

file://で渡す場合は、ファイル自体を文字列の配列にする。

["{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"s3:DeleteBucket\",\"Resource\":\"*\"}]}"]

ポリシードキュメントをそのまま置いたファイルを使い回すなら、"$(cat policy.json)"のほうが扱いやすい。

パーミッションバウンダリの影響は--permissions-boundary-policy-input-listで試せる。
指定できるのは1つだけで、エンティティに既にバウンダリがアタッチされている場合は、指定したポリシーで置き換えて評価される。
--policy-input-listと同じくリスト型のため、ファイルから読み込む場合の扱いも同様である。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject s3:PutObject \
    --permissions-boundary-policy-input-list "$(cat boundary.json)" \
    --query 'EvaluationResults[].[EvalActionName,EvalDecision,PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundary]' \
    --output text
s3:GetObject    allowed True
s3:PutObject    implicitDeny    False

PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundaryFalseの場合、権限ポリシー側でAllowしていてもバウンダリで頭打ちになっている。

リソースベースポリシーを含める

シミュレーターはリソース側のポリシーを自動では取得しない。
S3のバケットポリシーなどを評価に含めるには--resource-policyで明示的に渡す。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names s3:GetObject \
    --resource-arns arn:aws:s3:::example-bucket/data.csv \
    --resource-policy file://bucket-policy.json \
    --resource-owner 123456789012

現在のバケットポリシーはaws s3api get-bucket-policy で取得できるため、取得した内容をそのまま渡せばよい。

--resource-policyは文字列型のパラメータのため、file://でポリシードキュメントをそのまま渡せる。

リソースベースポリシーのシミュレーションはIAMユーザーのみ対応しており、ロールについてはサポートされていない。
--policy-source-arnにロールを指定して--resource-policyを渡す場合、リソースベースポリシーのPrincipalを評価する対象が必要になるため--caller-arnの指定が必須になる。
それでもロールに対する評価結果は保証されないため、ロールとリソースベースポリシーの組み合わせは実環境での確認が必要である。

--caller-arnは、APIを呼び出すプリンシパルを別のIAMユーザーとして扱うオプションである。
--policy-source-arnにDavid、--caller-arnにBobを指定すると、Davidのポリシーを持ったBobとして呼び出した場合の結果になる。

--resource-ownerにはリソースを所有するアカウントIDを指定する。
S3バケットのようにARNからアカウントIDが読み取れないリソースを、呼び出し元と異なるアカウントが所有している場合に指定する。

SCPの影響

アカウントがAWS Organizationsのメンバーである場合、SCPも評価される。
結果はOrganizationsDecisionDetailで確認できる。

$ aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/alice \
    --action-names ec2:RunInstances \
    --query 'EvaluationResults[].[EvalActionName,EvalDecision,OrganizationsDecisionDetail.AllowedByOrganizations]' \
    --output text
ec2:RunInstances        implicitDeny    False

AllowedByOrganizationsFalseであれば、IAMポリシー側を修正しても解決しない。SCPの見直しが必要である。
セキュリティ上の理由から、SCPについてはMatchedStatementsのような一致したステートメントの情報は返らない。

なおRCP(リソースコントロールポリシー)はシミュレーターの評価対象外である。

simulate-custom-policyとの違い

aws iam simulate-custom-policyは、文字列として渡したポリシーだけを評価する。
アカウント内のエンティティを参照しないため、まだ誰にもアタッチしていないポリシーの検証に向く。

simulate-principal-policyは他人の権限を明らかにできるコマンドであるため、実行権限を広く配ると意図しない情報開示につながる。
既存のエンティティを見る必要がなければsimulate-custom-policyを使うほうがよい。

実環境との差に注意する

シミュレーターの結果は実環境と一致しない場合がある。
特に次のようなケースでは差が出る。

  • VPCエンドポイントポリシー
  • ロールチェーン
  • 1つのリソースに複数のリソースベースポリシーが関わる構成
  • サービスがそのグローバル条件キーをサポートしているかどうかの判定

シミュレーターはIDベースポリシーとリソースベースポリシーの評価において、サービスごとの条件キーのサポート状況までは考慮しない。
allowedと出ても実際には拒否される場合があるため、最終的な確認は実環境で行う。

必要な権限

コマンドの実行にはそれぞれ次の権限が必要である。

コマンド必要なアクション
simulate-principal-policyiam:SimulatePrincipalPolicy
get-context-keys-for-principal-policyiam:GetContextKeysForPrincipalPolicy
simulate-custom-policyiam:SimulateCustomPolicy

Resourcearn:aws:iam::123456789012:user/aliceのように絞れば、シミュレーション対象のエンティティを限定できる。

参考