【AWS】アクセスポリシーの書き方（JSON）

# 目的
AWSで権限コントロールを行うための代表的なサービスとして、IAMのアイデンティティベースのポリシーと、各サービスのリソースに定義するリソースベースのポリシーがあります。

このポリシーはJSON形式で記述しますが、そんなに頻繁に書き直すものでもないので、作成・編集しよう思った時に定義したい書き方を忘れて「なんだけ？」となり、あちこち公式を探し回って面倒だなーとなるので、備忘録として投稿します。

## 前提と各種公式資料

基本的な書き方や、ポリシーについて既に理解がある方向けとなります。
詳細をご覧になりたい方は、[公式サイト](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_identity-vs-resource.html "アイデンティティベースおよびリソースベースのポリシー")をご覧ください。


一応、[公式サイトでもポリシーの例](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_examples.html "IAM アイデンティティベースのポリシーの例")がいくつか掲載されています。
こちらユースケースに沿って記載されています。

また、ポリシー要素のリファレンスについては[こちら](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements.html "IAM JSON ポリシー要素のリファレンス")に掲載されています。

私としては色々なケースを組み合わせて使い回せるようにしたいので、（個人的に）よく使うケースを以下にまとめます。

## Principal（プリンシパル）
アクセス権限の要求元です。IAMでは、ロールの信頼ポリシーで指定します。
つまり記述したアクセス権限を試行できる（AWSで言うところの、ロールを引き受けることができる）アカウントやサービスを指定します。

**例**
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348322/852f2e63-48a9-8c8f-b41e-71ffc2480e24.png">

また、リソースベースのポリシーにおいても有効な要素で、記述したアクセス権限が適用されるアカウントやサービスを指定します。

**例**
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348322/bf9e2722-6119-65f2-2c61-2953e42000e5.png">

### Principal一覧
| 要素 | 書き方 |
|:---|:---|
| AWSアカウント  | "AWS": "arn:aws:iam::<b><i>account-id</i></b>:root"  |
| AWSアカウント(短縮版)  | "AWS": "<b><i>account-id</i></b>"  |
| IAMロール  | "AWS": "arn:aws:iam::<b><i>account-id</b></i>:role/<b><i>role-name</b></i>"  |
| IAMユーザー  | "AWS": "arn:aws:iam::<b><i>account-id</b></i>:user/<b><i>user-name</b></i>"   |
| AWSサービス  | "Service": "<b><i>ServiceNamespace</b></i>.amazonaws.com"  |
| すべてのプリンシパル  | "AWS" : "*"  |
| すべてのプリンシパル(短縮版)  | "*"  |

### サービス抜粋
サービス名について基本的には直感的に分かりやすい名前（lambdaやs3など）になっていますが、ちょっと変化球なサービス名がありますので備忘の一覧です。
| ServiceName | ServiceNamespace |
|:---|:---|
| CloudWatch  | monitoring  |
| CloudWatchLogs  | log  |
| OpenSearch Service  | es  |
| Systems Manager  | ssm  |
| Cognito User Pools（ユーザープール）  | cognito-idp  |
| Cognito Identity（IDプール）  | cognito-identity  |
| EventBridge  | events  |
| VPC | ec2 |
| VPC IP Address Manager | ec2 |
| EBS | ec2 |


#### 分からないやつをCloudTrailから探す
サービスの数は年々増えており、全てを網羅するのはほぼ不可能でしょう。
特定のサービスのサービス名を手っ取り早く調べたい時は、検証環境などでコンソールから調べたいサービスに対して適当な操作を実施すれば、CloudTrailのイベント履歴からイベントソースとして記録されます（90日間デフォルトで取得され無料）。
イベント名として実施したアクションも記録されるので、AWS上でどの様な操作がされているのか見ることもできるので大変便利です。


#### 分からないやつをコマンドで探す
こちらの記事を参考にさせていただきました。
https://dev.classmethod.jp/articles/all-aws-services-onliner

私はWindows11PCのPowershellでコマンドを実行したかったので、以下のようにしました。
```powershell
> $result = aws iam generate-service-last-accessed-details --arn arn:aws:iam::aws:policy/AdministratorAccess --output text
> aws iam get-service-last-accessed-details --job-id ${result} --max-items 1000  | jq -c '.ServicesLastAccessed[] | [.ServiceName,.ServiceNamespace]'
```
実行結果はこんな感じです。ServiceName と ServiceNamespace がセットで出力されます。
たくさん出力されるので、一部抜粋します。
**例**
```powershell
["AWS App2Container","a2c"]
["Alexa for Business","a4b"]
["AWS IAM Access Analyzer","access-analyzer"]
["AWS Account Management","account"]
["AWS Certificate Manager","acm"]
["AWS Private Certificate Authority","acm-pca"]
["AWS Activate","activate"]
...続く
```

### 書き方の補足
- これはPrincipalに限った事ではありませんが、アカウントやサービスなどのパラメーターを複数定義する場合は、要素内で各パラメーターを配列で記述します。

**例**
```json
"Principal": {
  "AWS": [
    "arn:aws:iam::account-id:user/user-name-1", 
    "arn:aws:iam::account-id:user/user-name-2"
  ]
}
```


## Condition（コンディション）
ポリシーを適用する条件を定義します。
基本的な構文は以下の通りです。

```json
"Condition" : { "{condition-operator}" : { "{condition-key}" : "{condition-value}" }}
```

### condition-operator
条件演算子を指定し、keyとvalueを評価します。
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html

#### 文字列条件演算子
文字列の比較を評価対象とする。
| 条件演算子 | 説明 |
|:---|:---|
|StringEquals|完全一致、大文字と小文字の区別あり。|
|StringNotEquals|符号反転の一致|
|StringEqualsIgnoreCase|完全一致、大文字と小文字の区別なし。|
|StringNotEqualsIgnoreCase|符号反転の一致、大文字と小文字の区別なし。|
|StringLike|大文字と小文字の区別がある一致。値には、複数文字一致のワイルドカード (*) および 1 文字一致のワイルドカード (?) を文字列のどこにでも含めることができます。文字列の部分一致検索を行うには、ワイルドカードを指定する必要があります。|
|StringNotLike|符号反転の一致には、大文字と小文字の区別があります。値には、複数文字一致のワイルドカード (*) または 1 文字一致のワイルドカード (?) を文字列のどこにでも含めることができます。|

**例**
プリンシパルに特定のタグが付いている場合のみポリシーを適用。
```json
"Condition": {"StringEquals": {"aws:PrincipalTag/Key": "Value"}}
```

#### 数値条件演算子
整数または 10 進値の比較を評価対象とする。
| 条件演算子 | 説明 |
|:---|:---|
|NumericEquals|一致|
|NumericNotEquals|符号反転の一致|
|NumericLessThan|「未満」の部分一致|
|NumericLessThanEquals|「未満と等しい」の部分一致|
|NumericGreaterThan|「上回る」の部分一致|
|NumericGreaterThanEquals|「上回るまたは等しい」の部分一致|

#### 日付条件演算子
日付/時刻値の比較を評価対象とする。

:::note warn
ワイルドカードは日付条件演算子では使用できません。
:::
| 条件演算子 | 説明 |
|:---|:---|
|DateEquals|特定の日付との一致|
|DateNotEquals|符号反転の一致|
|DateLessThan|特定の日時よりも前の日時との一致|
|DateLessThanEquals|特定の日時またはそれよりも前の日時との一致|
|DateGreaterThan|特定の日時よりも後の日時との一致|
|DateGreaterThanEquals|特定の日時またはそれよりも後の日時との一致|

**例**
特定の期間内のみポリシーを適用。
```json
"Condition": {
                "DateGreaterThan": {
                    "aws:CurrentTime": "yyyy-mm-ddT00:00:00Z"
                },
                "DateLessThan": {
                    "aws:CurrentTime": "YYYY-MM-DDT00:00:00Z"
                }
```

#### ブール条件演算子
キーと "true (真)" または "false (偽)" の比較を評価対象とする。
| 条件演算子 | 説明 |
|:---|:---|
| Bool  | ブールの一致  |

#### IP アドレス条件演算子
 IPv4 または IPv6 アドレスまたは IP アドレス範囲の比較を評価対象とする。
 この演算子はcondition-keyの「aws:SourceIp」と組み合わせて使う。
| 条件演算子 | 説明 |
|:---|:---|
|IpAddress|所定の IP アドレスまたは範囲|
|NotIpAddress|所定の IP アドレスまたは範囲以外のすべての IP アドレス|

**例**
リクエストの送信元IPが100.20.3.0から100.20.3.255までのIP範囲から送られてきた場合のみポリシーを適用。
```json
"Condition": {"IpAddress": {"aws:SourceIp": "100.20.3.0/24"}}
```

#### Amazon リソースネーム (ARN) の条件演算子
ARN の比較を評価対象とする。
| 条件演算子 | 説明 |
|:---|:---|
|ArnEquals, ArnLike|ARN の大文字と小文字を区別した一致。ARN のコロンで分割された 6 個の各構成要素は個別に確認され、それぞれ複数文字一致のワイルドカード (*) または 1 文字一致のワイルドカード (?) を含むことができます。ArnEquals および ArnLike 条件演算子は、同じように動作します。|
|ArnNotEquals, ArnNotLike|ARN の符号反転の一致。ArnNotEquals および ArnNotLike 条件演算子は、同じように動作します。|

#### ...IfExists
指定した条件が存在する場合に、その条件を評価します。
**例**
 リクエスト元（IAMユーザー等）に「Environment」というタグが存在する場合に「true」を返す
```json
"Condition": {
                "BoolIfExists": {
                    "aws:PrincipalTag/Environment": "true"
                }
```
指定された条件が存在しない場合には「false」として評価されます。

### condition-key
何のリソースに対して条件を定義するか指定します。
つまりValueに指定するリソースの種別（リクエスト元、タグ、日時など）を定義するイメージです。

こちらもたくさんありますので、詳細は[公式サイト](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_condition-keys.html "AWS global condition context keys")を確認することとし、（個人的に）よく使うものを抜粋します。

- aws:PrincipalArn
特定のARNを持つリクエスト元にのみポリシーを適用する。
Valueには、AWSアカウントのrootやIAMユーザー、IAMロールなどを指定する。
**例**
```json
"Condition": {
        "ArnEquals": {
          "aws:PrincipalArn": "arn:aws:iam::account-id:user/user-name"
        }
```

- aws:PrincipalTag
特定のタグを持つリクエスト元にのみポリシーを適用する。
**例**
```json
"Condition": {
        "StringEquals": {
          "aws:PrincipalTag/department": "hr"
        }
      }
```
- aws:MultiFactorAuthPresent
AWSマルチファクタ認証（MFA）の状態を評価してポリシーを適用する。
**例**
MFAが有効でない場合にポリシーを適用する。
```json
"Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
```

- codecommit:References
CodeCommitサービスでのリポジトリの特定のブランチへの操作を制御する。
**例**
master ブランチ及びfeature ブランチのすべてのサブブランチにポリシーを適用する。
```json
"Condition": {
        "StringEquals": {
          "codecommit:References": [
            "refs/heads/master",
            "refs/heads/feature/*"
          ]
        }
      }
```
- aws:CurrentTime
リクエストの日時と、ポリシーで指定した日時を比較して評価する。

- Null
特定のリソースの条件がnullかどうかを評価する。
**例**
「Environment」タグがNull（存在しない）場合にポリシーを適用する。
```json
"Condition": {
        "Null": {
          "aws:PrincipalTag/Environment": "true"
        }
      }
```
- aws:Referer
HTTP ヘッダーの Referer フィールドを評価してポリシーを適用する。
**例**
```json
"Condition": {
                "StringEquals": {
                    "aws:Referer": "https://example.com"
                }
```
- aws:RequestedRegion
リクエストが特定のリージョンから行われた場合のみポリシーを適用。
**例**
Lambdaの作成を東京リージョンでのみ許可する。
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:CreateFunction",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "ap-northeast-1"
        }
      }
    }
  ]
}
```
## Action（アクション）
こちらは種類が多すぎて（細かすぎて）、特定の「コレ！」というのはなかなかないので、サービス毎に認可を記載した[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html "AWS のサービスのアクション、リソース、および条件キー")で地道に探す他ありません。
実はこれが一番大変だと思ってます。CloudTrailのアクション履歴も活用するなどして、最小権限の法則に則れるよう設計する必要があります。
コンソール上ではワンクリックの操作でも、裏で関連するアクションがたくさん実行されていることが多いです。
どうしても「*」で回避したくなる時もありますが・・・。

## Resource（リソース）
こちらは基本的に各サービスに割り当てられたARNを記載することがほとんどかと思いますが、1点だけ備忘として残します。
S3のリソースベースポリシーで、他のサービスからバケットのオブジェクトリストを取得したい場合は、
```json
 "Resource": "arn:aws:s3:::{BacketName}"
```
バケット中のオブジェクトを操作したい場合は、
```json
 "Resource": "arn:aws:s3:::{BacketName}/*"
```

のように記述する必要があります。

## ポリシー変数
Resource と Condition の要素にはあらかじめ用意された変数が使用できます。
一部抜粋します。
| 変数 | 説明 |
|:---|:---|
|${aws:username}|リクエストを行う IAM ユーザーのユーザー名|
|${aws:userid}|リクエストを行う IAM ユーザーのユーザーID|
|${aws:PrincipalTag/tagName}|アクションを実行する主体の指定したタグの値|


**例**
IAMユーザーが s3:GetObject アクションを実行することを、そのユーザー専用のS3バケット内のオブジェクトに限定したい場合。
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/${aws:username}/*"
    }
  ]
}
```
**例**
「team」というタグキーに設定されたvalue（値）がバケット名に含まれる場合のみ s3:GetObject を許可する。
IAMユーザーに「team」タグが付与され「hr」というvalueが設定されていた場合、バケットが「DOC-EXAMPLE-BUCKET-hr」という名前のバケットに対して許可が得られる。
また、IAMユーザーに「team」タグがなければアクセスは拒否される。
```json
{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET-${aws:PrincipalTag/team}/"
  }
}
```

# 最後に
1つ1つの項目の記事内容は、公式ドキュメントをよく見れば書いてあることではありますが、探しにくかったり、リンクをたどってグルグル回ったりして調べるのに時間がかかったので、まとめ資料として作成しました。

どなたかのお役に立てると嬉しいです。