【AWS】アクセス制限されたECRでAppRunnerを立ち上げる

AppRunnerでサービスを立ち上げるときに、ECRのプライベートリポジトリのイメージを指定することができます。
今回は、指定先のECRリポジトリにアクセス制限されている場合の設定方法を中心に、AppRunnerの使い方を説明したいと思います。

# ターゲット

- AppRunnerを使ってみたい
- ポリシーでアクセス制限が設定されたECRリポジトリを使用したい
- AppRunnerでサービスを立ち上げようとすると、イメージのpullで失敗する

# 要約

- AppRunnerのサービスロールは、AWS CLIからのみ作成できる(2021年7月8日現在)
- イメージのpullで失敗するときは、ECRリポジトリのポリシーでサービスロールが拒否されていないか確認する
- サービスロールの許可は、ロールを委任したセッションを許可する必要がある

# 準備

AppRunnerのサービス作成のために必要な資材や環境を準備します。

## プログラムによるアクセスが可能なIAMユーザーを作成

AWSマネジメントコンソールより、「プログラムからのアクセス」が可能かつIAMとECRにアクセスできるIAMユーザーを作成してください。

ユーザー名を良しなに決めて「プログラムからのアクセス」にチェックして、「次のステップ：アクセス制限」に進みます。
![スクリーンショット 2021-07-06 221247_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/c227f7ff-e51f-763f-f333-2b48548904bd.png)

CLIからロールの作成やユーザーID・ロールIDの確認を行うので、`IAMFullAccess`をアタッチします。
また、ECRを操作するために`AmazonEC2ContainerRegistryPowerUser`をアタッチします。
その後は、「次のステップ：タグ」を飛ばして「次のステップ：確認」まで進みます。
![スクリーンショット 2021-07-06 221903_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/0d555a29-d503-9e7b-78d1-24063c6ff0a9.png)

![スクリーンショット 2021-07-06 221954_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/100713df-746c-f59d-a1aa-fe55c30005e1.png)

赤枠の条件を確認してください。
間違いがなければ「ユーザーを作成」をしてください。
![スクリーンショット 2021-07-06 222308_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/6a89ee0f-3b0e-67eb-3a0a-6ff26be5943e.png)

AWS CLIからのログインにアクセスキーとシークレットキーが必要なので、csvをダウンロードしてください。
**※シークレットキーは後から確認できませんので、必ず保管してください。**
![スクリーンショット 2021-07-06 223936_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/8061488f-1a49-fcac-94d2-e71eba224454.png)

## ECRの準備

### ECRでプライベートリポジトリ作成

AppRunnerで使用するコンテナイメージのために、ECRにプライベートリポジトリを作成してください。
例では、東京リージョン(ap-northeast-1)に作成しています。
![スクリーンショット 2021-07-06 231302_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/1ccc111a-896b-8a9f-97ae-7ea804462c48.png)

![スクリーンショット 2021-07-06 231802_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/ed153ad0-f589-c5ad-1e4e-45c3ccb3c19d.png)

### ECRリポジトリにアクセス制限を設定(ポリシー)

作成したリポジトリにポリシーを設定することで、アクセス制限を行います。
以下の例では、先ほど作成したIAMユーザー以外は明示的に拒否するポリシーを設定します。

ポリシーを設定するリポジトリ名を押下します。
![スクリーンショット 2021-07-07 113201_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/c4bbc0ba-2152-de04-d41d-f90e6e3a7ad4.png)

左のナビゲーションペインから「Permissions」を押下します。
![スクリーンショット 2021-07-07 114137_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/ba36039b-0471-4836-ebea-eab20dc90aca.png)

「ポリシーJOSNの編集」を押下して、以下のポリシーを設定してください。
<アカウントID>と<ユーザ名>を置き換えて使用してください。
**※rootユーザは必ず含めてください。**

![スクリーンショット 2021-07-07 115115_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/5d465327-8a3c-33d4-4eaa-fb57a96d92ee.png)

![スクリーンショット 2021-07-07 133126_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/5c20695f-8bcb-f0b3-432c-fcda2a70756e.png)


<details><summary>アカウントIDの確認方法</summary>
マネジメントコンソールからIAMを開いて、赤枠の部分にある数字12桁がアカウントIDです。
![スクリーンショット 2021-07-07 120432_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/7e2dbb04-37c4-2d1c-f172-d2d02c96a715.png)
</details>

```json:ECRのリポジトリポリシー
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOthers",
      "Effect": "Deny",
      "NotPrincipal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:root",
          "arn:aws:iam::<アカウントID>:user/<ユーザ名>"
        ]
      },
      "Action": "ecr:*"
    }
  ]
}
```

以下の通りになっていれば、IAMエンティティに指定されているユーザー以外へのアクセス制限は完了です。
![スクリーンショット 2021-07-07 133838_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/252263c0-1dda-f19b-21ca-bca424eed88e.png)


## コンテナイメージを準備

### コンテナイメージを作成

今回は、AppRunnerを作成時に正しく動作しているかを確認するため、簡単なWebアプリをコンテナ化します。
[Webアプリを構築する](https://qiita.com/Shoma0210/items/47142e21ab552db60feb#web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B) ～ [Dockerfileを作成](https://qiita.com/Shoma0210/items/47142e21ab552db60feb#dockerfile%E3%82%92%E4%BD%9C%E6%88%90)を参考に、Dockerfileを作成しましょう。

### コンテナイメージをECRリポジトリにプッシュ

初めに、ECRリポジトリからプッシュコマンドを確認します。

![スクリーンショット 2021-07-07 143330_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/95c0ce31-009b-80bf-356f-51a424c62fce.png)

![スクリーンショット 2021-07-07 144340.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/66e5f412-e5b8-df28-57be-1bef54a6c78f.png)



IAMユーザーを作成した際にダウンロードしたcsvから、アクセスキーとシークレットキーの情報を使用して、AWS CLIでログインするアカウント情報を設定します。

```bash:アカウント情報を設定
$ aws configure
AWS Access Key ID:<Access key ID>
AWS Secret Access Key:<Secret access key>
Default region name:<リージョン>
Default output format:json
```

プッシュコマンドの`1.`を使用して、ログインを行います。

```bash:ログイン
$ aws ecr get-login-password --region <リージョン> | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com
```

作成したDockerfileをプッシュコマンドの`2.`を使用して、ビルドします。

```bash:Dockerfileのビルド
$ docker build -t <ECRリポジトリ名> .
```

Dockerのコンテナイメージをプッシュコマンドの`3.`を使用して、タグの付け替えをします。

```bash:タグの付け替え
$ docker tag <ECRリポジトリ名>:latest <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECRリポジトリ名>:latest
```

タグ付けしたコンテナイメージをECRリポジトリにプッシュします。

```bash:コンテナイメージをプッシュ
$ docker push <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECRリポジトリ名>:latest
```

以下のように、全てのレイヤーがプッシュされれば成功です。

```bash:プッシュ結果
The push refers to repository [<アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECRリポジトリ名>]
245acaaf78bf: Pushed
6369640f9374: Pushed
8c67849326e8: Pushed
5f70bf18a086: Pushed
b5afd022688e: Pushed
fef4ad87ab4f: Pushed
3286e333a984: Pushed
da654bc8bc80: Pushed
4ef81dc52d99: Pushed
909e93c71745: Pushed
7f03bfe4d6dc: Pushed
latest: digest: sha256:340f985e6c8efbbc2315d6310df7660cf0c641a6e60da04a3e79f28d25b5ae33 size: 2627
```

マネジメントコンソールからも確認できます。

![スクリーンショット 2021-07-07 163646_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/faa83859-dd20-a5f0-5f7c-ec63edf25b82.png)

# AppRunnerのサービスロール作成

AppRunner用のサービスロールは、AWS CLIからのみ作成できます。(2021年7月8日現在)

任意のフォルダに以下の内容でJSONファイルを作成します。
ファイル名は自由です。(次のコマンドでファイル名を使用します。)

```json:信頼ポリシーJSONの作成
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "build.apprunner.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```


JSONファイルと同じ場所で、以下のコマンドを実行してサービスロールを作成します。
<ロール名>は自由です。(例：AppRunnerECRAccessRole)

```bash:サービスロール作成
$ aws iam create-role --role-name <ロール名> --path /service-role/ --assume-role-policy-document file://<JSONファイル名>
```

上記で作成したサービスロールにECRへのアクセス権限を与えるために、ポリシーをアタッチします。
今回は、AWSで予め用意されている`AWSAppRunnerServicePolicyForECRAccess`を使用します。

```bash:サービスロールに権限付与
$ aws iam attach-role-policy --role-name <ロール名> --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess
```

以下の2つのコマンドで、それぞれサービスロールが作成されていることと、適用済みのポリシーを確認できます。

```bash:サービスロールの存在確認
$ aws iam get-role --role-name <ロール名>
{
    "Role": {
        "Path": "/service-role/",
        "RoleName": "<ロール名>",
        "RoleId": "AROAxxxxxxxxxxxxxxxxx",
        "Arn": "arn:aws:iam::<アカウントID>:role/service-role/<ロール名>",
        "CreateDate": "2021-07-08T07:21:46+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "build.apprunner.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
```

```bash:サービスロールの適用済みポリシー確認
$ aws iam list-attached-role-policies --role-name <ロール名>
{
    "AttachedPolicies": [
        {
            "PolicyName": "AWSAppRunnerServicePolicyForECRAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
        }
    ]
}
```

# ECRリポジトリでサービスロールをアクセス制限対象から除外

作成したサービスロールをECRリポジトリにアクセス制限対象から除外します。

[最初にポリシーを設定したとき](#ecrリポジトリにアクセス制限を設定ポリシー)と同様に、ECRリポジトリのPermissionsから「ポリシーJOSNの編集」を押下して、以下のポリシーを設定してください。
**※<ロールID>は、`$ aws iam get-role --role-name <ロール名>`で確認できます。**

```diff_json:サービスロールをアクセス制限から除外
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOthers",
      "Effect": "Deny",
      "NotPrincipal": {
        "AWS": [
          "arn:aws:iam::<アカウントID>:root",
          "arn:aws:iam::<アカウントID>:user/<ユーザ名>"
        ]
      },
      "Action": "ecr:*",
+     "Condition": {
+       "StringNotLike": {
+         "aws:userid": "<ロールID>:*"
+       }
+     }
    }
  ]
}
```

`NotPrincipal`にロールのARNを指定せずに`Condition`を利用する理由は、`aws:userid`で指定している文字列からわかる通り、ロールID以降の文字列が一意に特定できないためです。

その部分はセッションIDとなっています。AppRunnerを立ち上げる際に`build.apprunner.amazonaws.com`にサービスロールが委任されますが、その時のセッションIDはランダムです。そのため、`*`(ワイルドカード)を使用できない`NotPrincipal`では意図した設定を出来ませんので、代わりに`Condition`でロールIDを文字列比較して判定しています。

詳しくは、以下の記事を参照してください。
[【AWS】ECRへのアクセスをユーザー・ロール・サービス単位で制限する](https://qiita.com/Shoma0210/items/dc87748f8d3831e9a537)

# AppRunnerでサービスを作成

いよいよAppRunnerでサービスを作成します。

AppRunnerのサービス画面で、「サービスの作成」を押下します。
![スクリーンショット 2021-07-08 233545_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/34a2903b-351b-c6a5-0bfa-21362cd74ac0.png)

リポジトリタイプは「コンテナレジストリ」、プロバイダーは「Amazon ECR」にチェックします。
コンテナイメージのURIの「参照」を押下して、ECRリポジトリとイメージ(タグ)を選択します。
![スクリーンショット 2021-07-08 234310_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/d65a3c46-20d9-90cf-e5f1-b2a2071b0bd9.png)

イメージリポジトリに作成したリポジトリ名を選択して、イメージタグは一番最新のイメージを示す`latest`を選択してください。(1度しかイメージをプッシュしていない場合はイメージタグが自動で選択されます。)
![スクリーンショット 2021-07-08 234505_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/a2bfda1d-5d34-e9fa-a68b-f75653feb27d.png)

デプロイトリガーで「自動」にチェックします。
また、ECRアクセスロールは、「既存のサービスロールを使用」をチェックして、作成したサービスロール名を選択してください。
完了したら「次へ」を押下します。
![スクリーンショット 2021-07-08 235354_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/c178f0c6-fa05-ea14-18ed-d1c20eef93af.png)

サービス名を自由に設定してください。
ポートは、アプリケーションで使用するポートを指定します。今回の例で使用しているGoのWebアプリでは`8080`を設定します。
完了したら「次へ」を押下します。
![スクリーンショット 2021-07-09 000443_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/fa0d2dfa-06dd-e663-df20-673289dddc90.png)

設定を確認後「作成とデプロイ」を押下して、サービスを作成してください。
![スクリーンショット 2021-07-09 003526_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/3fbd4902-e412-6dda-5a8f-75ffb4d1a13c.png)

サービスが作成されたら、デフォルトドメインを押下して「Hello World!」が表示されることを確認しましょう。
![スクリーンショット 2021-07-09 084109_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/01b23d81-cf8b-9b44-19d1-6ec7a7d41f68.png)
![スクリーンショット 2021-07-09 084720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1166959/470b031f-0152-8bde-bc31-0cc46c9ec41b.png)

# さいごに

AppRunnerを立ち上げることはできましたか？
デプロイ・ロードバランシング・オートスケーリング・証明書管理など、様々な管理を自動で行ってくれる便利なサービスです。
しかし、ECRのアクセス制限の設定に少し複雑な部分があったのでまとめてみました。

コンテナ技術を使用したサービスを手軽に構築する手段として、これからもAppRunnerを使用していきたいと思います。