IAM PolicyのPrincipal、Conditionの理解がふわっとしている

メタップスアドベントカレンダー二日目の記事です。

IAMでポリシーを設定する際、EffectやAction、Resourceは意識して設定しますが、PrincipalやConditionはざっくりとした理解のまま進めることがあると思います。私はそうでした。
そこで、なんとなく理解していたものを情報を整理するためにまとめました。

# IAMについて
IAMは、AWSリソースへのアクセスを安全に管理するためのサービスであり、ユーザーやサービスがどのリソースにどのようにアクセスできるかを制御します。

IAMでは主に以下の3つのリソースを扱います：

User
ヒューマンユーザーやアプリケーションを指します。それぞれ固有の認証情報（パスワードやアクセスキーなど）を持ち、アクセス制御の対象となります。

Group
共通のアクセス権限を設定するためのユーザーの集合体です。管理が簡素化され、ユーザーをグループに追加するだけで権限を継承させることができます。

Role
特定の操作を一時的に許可するためのアクセス権限セットです。他のAWSサービスや外部のエンティティに一時的にアクセスを許可する際に使用されます。

これらを制御する中心的な仕組みが IAMポリシー です。ポリシーはJSON形式で記述され、アクセス可能なリソース（Resource）や許可される操作（Action）、適用元対象（Principal）、条件（Condition）といった要素を詳細に定義します。

# Principal、Conditionの特異な性質
Principal、Conditionには他の要素にはない下記の特徴があります。
- Principalは全許可（`"*"`）を除いてワイルドカードを使用できない
- Principal、Conditionは入れ子構造で定義する

## Principalは全許可（`"*"`）を除いてワイルドカードを使用できない
Principalはポリシーを設定する際、Principalで指定した対象が存在するか、対象が一意に定まるかを確認するため、ワイルドカードを使用するとエラーになります。
また、設定した対象が存在しない場合でもエラーになります。

IAM RoleやS3・KMS等のポリシーの作成順序を間違えてエラーになってしまうミスをよくしてしまいますよね？
（私はそれで原因がわからず苦戦しました）

他の要素は当たり前のようにワイルドカードを使用するのでこの仕様が落とし穴になる可能性は大きいと思います。気をつけたいですね。

ここで困るのが、ワイルドカードを使用したい場合です。
特定の名前のパターンのUserやRoleをまとめて許可したい場合などはPrincipalでは設定できません。
その場合はより柔軟な設定ができるConditionを使います。


## Principal、Conditionは入れ子構造で定義する
Principal、Conditionは、全許可（`"*"`）を除いて設定が入れ子構造になっています。
特にConditionは設定できる項目が多いためより細かな設定が可能です。

### 設定できる項目
#### Principal
Principalの要素には"AWS"と"Service"、"Federated"、"CanonicalUser"(S3のみ)があります。

AWS要素は下記のPrincipalを設定できます。
- AWSアカウント プリンシパル
- IAMロールプリンシパル
- ロールセッションプリンシパル
- IAMユーザープリンシパル
- IAMIdentity Center のプリンシパル
- AWS STS フェデレーティッドユーザーセッションプリンシパル

例
```json:AWSアカウント
"Principal" : { 
  "AWS": [ 
    "123456789012",
    "555555555555" 
    ]
  }
```
```json:IAM Role
"Principal" : { 
  "AWS": [ 
    "arn:aws:iam::<AWS-account-ID>:role/<role-name>" 
    ]
  }
```

Service要素は下記のPrincipalを設定できます
- AWSサービス
（`<service-name>.amazonaws.com`の形で表す）

例
```json
"Principal" : { 
  "Service": [ 
    "ecs.amazonaws.com" 
    ]
  }
```

Federated要素は下記のPrincipalを設定できます
- OIDC セッションプリンシパル
（SAMLプロバイダー、OIDCプロバイダー）

例
```json
"Principal": {
  "Federated": "arn:aws:iam::<AWS-account-ID>:saml-provider/<provider-name>"
}
```

CanonicalUserはS3にのみ使用でき、下記のPrincipalを設定できます
- AWSアカウントのCanonical User ID

例
```json
"Principal": {
  "CanonicalUser": "xxxxxxxxxxxxxxxxxxxxxxxx"
}
```

#### Condition
Conditionは条件演算子と条件キー、条件の値をセットで設定します。

例
```json
"Condition": {
  "StringEquals": {
    "aws:username": "admin"
  }
}
```

条件演算子とは、「等しい」や、「より小さい」などどのような条件なのかを設定できます。
下記は主な条件演算子です。
|演算子|説明|
|----|----|
StringEquals|値が完全一致する場合に条件が適合します。
StringLike|ワイルドカード（*や?）を使用して部分一致を許可します。
NumericEquals|数値が一致する場合に条件が適合します。
DateEquals|日付が一致する場合に条件が適合します。
Bool|条件がtrueまたはfalseであるかを評価します。
IpAddress|特定のIPアドレス範囲（CIDR表記）内である場合に条件が適合します。
ArnEquals|ARN（Amazon Resource Name）が一致する場合に条件が適合します。
Null|キーが存在しない、または値がnullである場合に条件が適合します。

Conditionの条件キーは膨大なので一部を紹介します。

`aws:username`
現在のユーザー名。
```json
"Condition": {
  "StringEquals": {
    "aws:username": "test-user"
  }
}
```
`aws:SourceIp`
リクエストの送信元IPアドレス。

```json
"Condition": {
  "IpAddress": {
    "aws:SourceIp": "203.0.113.0/24"
  }
}
```
`aws:RequestTag/TagKey`
リクエストに含まれるタグ。

```json
"Condition": {
  "StringEquals": {
    "aws:RequestTag/Environment": "production"
  }
}
```
`aws:PrincipalArn`
呼び出し元のARN。

```json
"Condition": {
  "ArnEquals": {
    "aws:PrincipalArn": "arn:aws:iam::123456789012:role/Admin"
  }
}
```
こちらはPrincipal要素と同じ働きをします。が、ワイルドカードを使ってより柔軟に値を設定できます。

**複数条件の組み合わせ**

Conditionは複数の条件を論理的に組み合わせることができます。

例: AND条件
```json
"Condition": {
  "StringEquals": {
    "aws:username": "admin"
  },
  "IpAddress": {
    "aws:SourceIp": "203.0.113.0/24"
  }
}
```

例: OR条件
```json
"Condition": {
  "StringLike": {
    "aws:username": ["admin", "developer"]
  }
}
```

# Principalの内容ってConditionでも設定できるのでは…？
結論を言うと設定できます。
Conditionの方がより柔軟に設定できる（ワイルドカードを使用できたり）ので便利ではありますが、Principalの方が構造が比較的単純ではあるのでどちらが良いかは場合によると思います。

# まとめ
今回はIAM Policyの要素PrincipalとConditionについて、他の要素との違いをまとめました。
- Principalは適用元対象、Conditionは条件を定義する
- Principalにはワイルドカードを使用できない
- PrincipalとConditionは入れ子構造で設定する

IAMの設定は初歩的なことではありますが、奥が深くてたまに沼にハマるので気をつけたいですね。