【AWS】ECRへのアクセスをユーザー・ロール・サービス単位で制限する

AWSのAppRunnerというサービスで、アクセス制限されたプライベートなECRリポジトリからイメージ(リソース)を取得する際に、うまくいかず苦戦しました。
今回はECRを例として、ユーザー・ロール・サービス単位でアクセスを制限する方法をまとめたいと思います。(S3のバケットポリシーなどにも応用できる内容となっています。)

# ターゲット

- リソースベースのポリシーについて学びたい
- リソース(ECR,S3など)へのアクセス制限をユーザーやサービス、ロール単位で設定したい

# 基本的な設定方法

まずは、ECRにアクセス制限を設定する時の基本的なポリシーを確認します。
ポリシーの要素の Deny + NotPrincipal または Allow + Principal で条件を指定することができます。

### ユーザーを制限する

ルートユーザー、User1、User2の3つ以外を拒否するポリシーです。(root以外の各ユーザーへの許可は別途必要となります。)
AWSでARNを指定することで、ユーザーのアクセス制限を設定できます。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyExample1",
      "Effect": "Deny",
      "NotPrincipal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:user/User1",
          "arn:aws:iam::<アカウントID>:user/User2",
          "arn:aws:iam::<アカウントID>:user/root"
        ]
      },
      "Action": "ecr:*"
    }
  ]
}
```

### サービスを制限する

以下の例はCodeBuildを許可するポリシーです。
Serviceでサービスの識別子を指定することで、サービスを制限します。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowService",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "codebuild.amazonaws.com"
        ]
      },
      "Action": "ecr:*"
    }
  ]
}
```

# 特殊な設定が必要なパターン

### ロールを制限する (セッションIDが固定できる場合)

Role1以外を拒否するポリシーです。
ユーザーと同じく、AWSでARNを指定することでロールのアクセス制限を設定できます。

**ロールのARN**と**ロールを委任した時のセッションIDを含めたARN**の両方を設定する必要があることに気を付けてください。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyExample2",
      "Effect": "Deny",
      "NotPrincipal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:role/Role1",
          "arn:aws:sts::<アカウントID>:assumed-role/Role1/<セッションID>",
          "arn:aws:iam::<アカウントID>:user/root"
        ]
      },
      "Action": "ecr:*"
    }
  ]
}
```

### ロールを制限する (セッションIDが変わる場合)

AWS CLIなどからユーザーにロールを委任する場合などは、セッションIDを指定することが出来ますので、ポリシーで予め定義できます。しかし、サービスにロールを委任するようなケースでは、セッション毎にIDが変わる場合があります。

今までの例で使用していたPrincipalやNotPrincipalではワイルドカード(?や*)は使用できません。
代わりに、ワイルドカードが使用できる[Condition](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition.html)の[文字列条件演算子](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_String)を用いてロールID[^1]を指定します。
[^1]: ※ロールIDはAWS CLIを用いて[こちら](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/get-role.html)のコマンドで確認できます。(IAMの読み取りとプログラムからのアクセス権限があるアカウントが必要です。)

以下の例は、ロールIDが```AROAxxxxxxxxxxxxxxxxx```であるロールを委任したセッション以外を拒否するポリシーです。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyExample3",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "ecr:*",
      "Condition": {
        "StringNotLike": {
          "aws:userid": "AROAxxxxxxxxxxxxxxxxx:*"
        }
      }
    }
  ]
}
```

# 実用例

### 明示的な拒否(Deny)を用いた例

Denyのステートメントで、NotPrincipalとConditionの両方を用いてユーザーとロールを制限します。

以下の例は、```User1```,```User2```,```AROAxxxxxxxxxxxxxxxxN:*```,```AROAxxxxxxxxxxxxxxxx5:*```のいずれにも一致しない場合は拒否するポリシーです。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyExample4",
      "Effect": "Deny",
      "NotPrincipal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:user/User1",
          "arn:aws:iam::<アカウントID>:user/User2",
          "arn:aws:iam::<アカウントID>:user/root"
        ]
      },
      "Action": "ecr:*",
      "Condition": {
        "ForAnyValue:StringNotLike": {
          "aws:userid": [
            "AROAxxxxxxxxxxxxxxxxN:*",
            "AROAxxxxxxxxxxxxxxxx5:*"
          ]
        }
      }
    }
  ]
}
```

複雑なので、少し詳しく説明します。

同一のステートメント内において、異なる要素の条件はANDで評価され、同一要素内はORで評価されます。

まずはNotPrincipalについてです。
NotPrincipalは否定であり、User1とUser2が同一要素(AWS)に指定されているので、
**NOT (```User1``` OR ```User2```)**
となります。

次にConditionについてです。
Condition内のForAnyValue:StringNotLikeは否定であり、二つの文字列が同一要素(aws:userid)に指定されているので、
**NOT (```AROAxxxxxxxxxxxxxxxxN:*``` OR ```AROAxxxxxxxxxxxxxxxx5:*```)**
となります。

二つの条件は、それぞれ異なる要素に指定されているためANDとなるため、最終的な条件式は以下です。

**{NOT (```User1``` OR ```User2```)} AND {NOT (```AROAxxxxxxxxxxxxxxxxN:*``` OR ```AROAxxxxxxxxxxxxxxxx5:*```)}**
= **NOT (```User1``` OR ```User2``` OR ```AROAxxxxxxxxxxxxxxxxN:*``` OR ```AROAxxxxxxxxxxxxxxxx5:*```)**

この条件Trueの場合に拒否(Deny)となるため、```User1```,```User2```,```AROAxxxxxxxxxxxxxxxxN:*```,```AROAxxxxxxxxxxxxxxxx5:*```のいずれにも一致しない場合は拒否するポリシーとなります。

### 明示的な許可(Allow)を用いた例

Allowのステートメントを2つ用いてユーザーとロールを制限します。

以下の例は、```User1```,```User2```,```AROAxxxxxxxxxxxxxxxxN:*```,```AROAxxxxxxxxxxxxxxxx5:*```を許可するポリシーです。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowUsers",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:user/User1",
          "arn:aws:iam::<アカウントID>:user/User2"
        ]
      },
      "Action": "ecr:*"
    },
    {
      "Sid": "AllowRoles",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "ecr:*",
      "Condition": {
        "ForAnyValue:StringLike": {
          "aws:userid": [
            "AROAxxxxxxxxxxxxxxxxN:*",
            "AROAxxxxxxxxxxxxxxxx5:*"
          ]
        }
      }
    }
  ]
}
```

上記では、ユーザーとロールを別のAllowステートメントとして定義しています。
理由は、同一ステートメントに異なる要素で条件を指定するとANDで評価されてしまい、意図しない条件となってしまうためです。

異なるステートメントの条件はORで判定されるため、
```User1```,```User2```,```AROAxxxxxxxxxxxxxxxxN:*```,```AROAxxxxxxxxxxxxxxxx5:*```のいずれかを許可するポリシーとなっています。

# さいごに

AppRunnerに設定したサービスロールをECR側で許可する方法でつまずいた経験から、ポリシーについて調べたことをまとめてみました。

今回例としてあげたポリシーはECRでしたが、S3のバケットポリシーなどにも応用が可能です。
サービスやロールに対する設定はAppRunnerだけでなく、CloudWatch,CodeBuild,CloudTrailなどを使用する場合にも参考にして頂ければ幸いです。

# 参考サイト

[AWS CLI Command Reference - get-user](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/get-user.html)
[AWS CLI Command Reference - get-role](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/get-role.html)
[AWS公式ドキュメント - IAM 識別子](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids)
[AWS公式ドキュメント - IAM JSON ポリシーの要素: 条件演算子](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_String)
[AWS公式ドキュメント - IAM JSON ポリシーの要素: Condition](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition.html)
[AWS公式ドキュメント - ロールの用語と概念](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_terms-and-concepts.html)
[AWS CLI を使用して IAM ロールを引き受ける方法を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/iam-assume-role-cli/)
[[アップデート] サービスプリンシパルを含む IAM ポリシーの管理を簡素化する AWS グローバル条件キーが追加されました](https://dev.classmethod.jp/articles/iam-global-condition-key-service-principal/)
[特定の IAM ロールのみアクセスできる S3 バケットを実装する際に検討したあれこれ](https://dev.classmethod.jp/articles/s3-bucket-acces-to-a-specific-role/)
[非VPC LambdaでIP制限されたS3バケットにアクセスしたい](https://dev.classmethod.jp/articles/how-to-access-ip-restricted-buckets-with-non-vpc-lambda/)
[[AWS] S3バケットポリシーで、特定のIAMロールだけがバケットにアクセス出来るようにする。](https://qiita.com/n-ishida/items/33cef1d67adc0961625a)