ECSでのIAMロールについて

ECSでのIAMロールについて

June 11, 2020
Cloud
AWS, Fargate, IAM

ECS で IAM ロール指定して、操作しようとしたらうまくいかなかったので、調べた

TL;DR

ECS のタスクに IAM ロールを割り当てて、コンテナからそのロールを参照しよとすると EC2 の場合と少し異なる手順になる
AWS CLI や AWS SDK を使う場合は、この手順の差を自動で吸収してくれるので、意識しなくても大丈夫です。

EC2 での IAM Role

AWS 使うに置いて、IAM の管理は気をつけないといけないことです。
EC2 内から IAM 使って AWS リソースにアクセスする際も、なるべく IAM Access Key は発行せずに、IAM Role でやるというのがベストプラクティスと言えるでしょう

IAM Role を使う場合、プログラムはどのようにしてクレデンシャル情報が取得されるか

IAM Role をインスタンスに付与すると、インスタンスメタデータという場所に IAM の情報が保存されます。

インスタンスメタデータおよびユーザーデータにはそのインスタンス自体内からのみアクセスできるものの、データは認証または暗号化手法によって保護されていません。インスタンス、そしてインスタンス上で実行される任意のソフトウェアに対して直接アクセス権がある可能性がある人は、メタデータを表示できます。そのため、パスワードまたは存続期間の長い暗号化キーなどの機密データは、ユーザーデータとして保管しないようにしてください。

インスタンス内からのみアクセスできるので、秘匿性が高いです。

インスタンスメタデータに保管されている IAM は以下のようにアクセスします

# 以下で今割り当てられてるIAM Roleを確認できます
$ curl http://169.254.169.254/latest/meta-data/iam/info
{
  "Code" : "Success",
  "LastUpdated" : "2020-06-11T10:15:18Z",
  "InstanceProfileArn" : "arn:aws:iam::<Account ID>:instance-profile/<IAM Role名>",
  "InstanceProfileId" : "...."
}

# 以下でIAM RoleのAccess Keyが取得できます
$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<IAM Role名>
{
  "Code" : "Success",
  "LastUpdated" : "2020-06-11T10:14:39Z",
  "Type" : "...",
  "AccessKeyId" : "...",
  "SecretAccessKey" : "...",
  "Token" : "...",
  "Expiration" : "2020-06-11T16:34:27Z"
}

AWS CLI や AWS SDK では、環境変数でAWS_ACCESS_KEYなどが設定されていない場合, インスタンスメタデータに保存される IAM 情報を取得して処理してくれるので、
ユーザーは IAM Role をインスタンスに割り当てれば、特に意識することなく AWS リソースの操作が行えます.

ECS での IAM ロール

次に ECS での IAM ロールについて見ていきます。

TaskRole と Task 実行 Role

ECS でタスク定義を作成すると 2 つの Role を設定する必要があります。

TaskRole は EC2 で言う所の IAM Role と同じで、Task 内のコンテナが AWS リソースにアクセスする際に利用したい IAM を渡してあげます。
ちなみに既存の IAM ロールを ECS の TaskRole で使うようにするには、IAM の信頼関係の設定でecs-tasks.amazonaws.comからのアクセスを許可してあげると良いです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
+          "ecs-tasks.amazonaws.com",
          "ec2.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Task 実行 Role は,Task を動かすためのロールという感じで
Task が利用するコンテナの取得が例えば ECR からだったら、ECR からの取得権限で使う IAM はここで指定したものになります。
また Task のコンテナが吐き出すログを cloudwatch に吐き出した場合も同様に IAM 権限が必要になりますが、これもここのロールが使われます。

TaskRole を使う

Task Role を使おうとすると EC2 の時と同様にインスタンスメタデータにアクセスすればいいかというと、それではダメです。
アクセスしても何もありません。
ECS では Fargate モードにしろ、EC2 モードにしろ, 1 つのタスク(コンテナ)が 1 つの EC2 を占有するわけではないので単一のインスタンスメタデータに IAM 権限を管理できないのかなと思います。

ではどうするかというと、コンテナごとに専用の保管場所が作成されていて、そこにアクセスすることで取得できます。

Tast 定義で TaskRole を設定し、Task を起動すると立ち上がったコンテナに環境変数でAWS_CONTAINER_CREDENTIALS_RELATIVE_URIという値が注入されます。
この環境変数を使って、以下にアクセスすると、IAM の情報が取得できます

$ curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
{
    "AccessKeyId": "ACCESS_KEY_ID",
    "Expiration": "EXPIRATION_DATE",
    "RoleArn": "TASK_ROLE_ARN",
    "SecretAccessKey": "SECRET_ACCESS_KEY",
    "Token": "SECURITY_TOKEN_STRING"
}

AWS SDK, AWS CLI の挙動

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-iam-roles.html

コンテナインスタンスで、コンテナエージェントのバージョン 1.11.0 以降と、AWS CLI または SDK のサポートされているバージョンが使用されている場合、SDK クライアントによって、AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 変数が使用可能かどうかが確認され、提供された認証情報を使用して AWS API への呼び出しが実行されます。

AWS SDK, CLI では、環境変数AWS_CONTAINER_CREDENTIALS_RELATIVE_URIの有無で判定を行います。
AWS_CONTAINER_CREDENTIALS_RELATIVE_URIが設定されていれば、この情報を使って取りに行こうとし、されていなければ EC2 のインスタンスメタデータに取りに行くような挙動になります。

ECS のコンテナにログインして、実際に確認する

最後に実際に Fargate でコンテナを起動して、中に入って確認してみます。

SSH できる Docker イメージを用意

Fargate で起動する場合は、デフォで中に入ることができないので SSH 可能な Docker イメージを用意します

$ tree
.
├── Dockerfile
├── entrypoint.sh
├── id_rsa
└── id_rsa.pub
FROM ubuntu:18.04

RUN apt-get update && apt-get install -y zip curl openssh-server

## AWS CLIのインストール
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install && \
    rm -rf awscliv2.zipwhi

## SSH設定
RUN mkdir /var/run/sshd
RUN echo 'root:rootpassws' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
COPY ./id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 700 /root/.ssh
RUN chmod 600 /root/.ssh/authorized_keys

COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

EXPOSE 22
CMD ["./entrypoint.sh"]

適当な ssh 鍵を用意して、公開鍵(id_rsa.pub)をイメージにいれて登録しておいておきます。
COPY しているentrypoint.shは以下のようになっています

#!/bin/bash

echo $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > /root/AWS_CONTAINER_CREDENTIALS_RELATIVE_URI

curl --max-time 5 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > /root/IAM

# 最後はSSH
/usr/sbin/sshd -D

AWS_CONTAINER_CREDENTIALS_RELATIVE_URIはメインプロセス(ECS で起動しているプロセス)にしかないので、SSH でログインしても参照できません。
そのため今回のテスト用にファイルに吐き出すことで後から参照できるようにしてます。

これで build して ECR へ push し、ECS のタスク定義を作成し、起動します。

SSH して実際に確認する

$ ssh root@<taskのip> -i ~/.ssh/id_rsa

# ls
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI  IAM

# cat AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
/v2/credentials/................

// 環境変数に設定しておく
# export AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=$(cat AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)

// 一応IAMの確認
# curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
{"RoleArn":"arn:aws:iam::<account>:role/<role名>","AccessKeyId":"....","SecretAccessKey":"....","Token":"...","Expiration":"2020-06-11T15:36:22Z"}

// AWS操作
# aws s3 ls s3://<割り当てたIAMの権限でアクセス可能なバケット>/
2020-06-10 05:24:56       4779 foooooo

// 環境変数を外した場合
# unset AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
// 見れなくなる
# aws s3 ls s3://<割り当てたIAMの権限でアクセス可能なバケット>/
Unable to locate credentials. You can configure credentials by running "aws configure".

まとめ

EC2 と ECS では IAM Role の取得方法が異なりました。
ただ、AWS SDK や CLI 使う場合はこれらのツールがその差を吸収してくれるので、別段意識する必要はなさそうです。

参考