一時クレデンシャルを発行するローカルIMDSを作った

# はじめに

AWSを使う上でクレデンシャルをEC2インスタンス上(~/.aws/credentials)に保存したり
プログラムの設定ファイルに指定することは、セキュリティ上の観点から推奨されません。
（というかアクセスキーを発行すること自体がセキュリティリスクになります）
通常は、IAMロールをEC2に紐づけてIMDS(インスタンスメタデータサービス)から一時的な
クレデンシャルを取得してAWSリソースにアクセスにすると思います。

この一時クレデンシャルを提供するのが、IMDS(後述)と呼ばれるサービスですが、
ローカルで動くIMDSもどきを作ってみたので、その紹介です。

# インスタンスメタデータサービス(IMDS)とは

[このページ](https://aws.amazon.com/jp/blogs/news/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/)を抜粋

```
インスタンスメタデータサービス(IMDS)は、一時的な認証情報へのアクセスを提供することで
クラウドユーザーにとって大きなセキュリティ上の課題を解決し、手動またはプログラムによって
インスタンスに機密認証情報をハードコードしたり、配布したりする必要をなくしました。
EC2 インスタンスにアタッチされた IMDS は、特別な「リンクローカル」の IP アドレス 
169.254.169.254 で接続され、インスタンスで実行中のソフトウェアだけがアクセスできます。
アプリケーションは IMDS にアクセスして、インスタンス、ネットワーク、およびストレージに関する
メタデータを利用できます。
また、IMDSは、インスタンスにアタッチされている IAM ロール による AWS 認証情報を使用
できるようにします。
```

簡単に言うと、EC2上でaws sdkを使ったプログラムやaws cliコマンドを実行すると、
http://169.254.169.254/latest/meta-data/iam/security-credentials/{iamrole}
にリクエストを送信してクレデンシャルを取得し、その認証情報を使ってAWSリソースを操作します。

#### 処理イメージ
![imds.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51108/11345354-a132-7be5-ce39-67c485418d78.png)

#### レスポンス例
```json:一時セキュリティ認証情報
{
  "Code" : "Success",
  "LastUpdated" : "2020-08-04T06:34:37Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIA4KYQ77T7MF27ET2B",
  "SecretAccessKey" : "Q8vJapXly4HA1~~",
  "Token" : "IQoJb3JpZ2lu~~",
  "Expiration" : "2020-08-04T12:38:26Z"
}
```

# ローカルIMDSとは

有効期限が切れない間隔で定期的にsts.assumeRoleして一時クレデンシャルを取得して
それをリンクローカルアドレス(169.254.169.254)へのhttpリクエストの結果として返すDocker
コンテナです。 

# 使い方

前提）
・スイッチロール元アカウントID：111111111111 / ユーザ名：miyaz@sencorp
・スイッチロール先アカウントID：222222222222 / ロール名：Dev

### スイッチロール先IAMロールの信頼関係に下記を指定

```json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringLike": {
          "sts:RoleSessionName": "${aws:username}"
        }
      }
    }
  ]
}
```

### スイッチロール元アカウントのクレデンシャルをローカルの ~/.aws/credentialsにセット

```terminal:
[default]
aws_access_key_id=~~~
aws_secret_access_key=~~~
```

### docker-compose.ymlを作成

```yaml:docker-compose.yml
version: '3.8'

services:
  app:
    image: centos:7
    tty: true

## 既存のdocker-compose.ymlで使いたい場合はここ以降をコピペでOK(RoleArnは書き換え要)
  imds:
    image: miyaz/local-imds:latest
    volumes:
      - ~/.aws:/root/.aws
    environment:
      IMDS_ROLE_ARN: "arn:aws:iam::222222222222:role/Dev"
    networks:
      default:
        ipv4_address: 169.254.169.254

networks:
  default:
    ipam:
      config:
        - subnet: 169.254.169.0/24
```

### 実行する

以下の例では、centos7にawscliを入れて `aws s3 ls` を実行する手順です。
ちなみにimdsはエラーメッセージ以外は何もログ出力しません（念の為)

```terminal:
docker-compose up -d
docker-compose exec app bash
yum -y install awscli
aws s3 ls
```

## 解説

imdsコンテナがIMDSの役割を持っています。
~/.awsをボリューム共有するので、スイッチロール可能なクレデンシャルが指定され
ている必要があります。

環境変数[IMDS_ROLE_ARN]に権限を引き受けるロールを指定します。
`IMDS_ROLE_ARN: "arn:aws:iam::{AccountId}:role/{IamRole}"`

スイッチロール元クレデンシャルのプロファイル名が`default`でなければ、
環境変数[IMDS_SOURCE_PROFILE]で指定できます。

imdsが169.254.169.254というIPで動くので、そこからクレデンシャルを取得する
appコンテナが169.254.169.0/24のIPを持っていればOK。
[ymlファイル](https://github.com/miyaz/local-imds/blob/master/docker-compose.yml)に記載のdefaultのコメントアウトを外すと通常はdefaultネットワークを
使いつつ、クレデンシャル取得時のみ169.254.169.0/24ネットワークを使います。

ちなみに、[この設定](https://www.beex-inc.com/blog/add_role_session_name/)は入れた方がいいと思ったので、roleSessionNameに自動的に
スイッチロール元のIAMユーザ名が入るようにしています。

ローカルIMDSのコードは[こちら](https://github.com/miyaz/local-imds)

# どこで使えるか？

うーん、あまり有用な使い道は思いつかないですが、、強いて言えばということで。

AWSパートナー経由で契約している場合など[Organizations](https://aws.amazon.com/jp/organizations/)が使えない場合はスイッチ
ロール運用している会社が多いと思います。
その場合にはログイン専用アカウントのみにIAMユーザを作成し、ログイン後スイッチロール
して他のアカウントのマネジメントコンソールにアクセスしていると思います。

で、開発しているアプリ（gitリポジトリ)とAWSアカウントが紐づいている場合、新たに
参加した開発者にはスイッチロール用クレデンシャルを伝えて .aws/configに追記してもらうと思います。

```terminal:~/.aws/configに追記する例
[profile hoge-dev]
region=ap-northeast-1
role_arn=arn:aws:iam::222222222222:role/Dev
source_profile=default
role_session_name=miyaz@sencorp
```

アプリのコードベースにあるdocker-compose.ymlに予めRole名が記載されて
いれば、これを伝達する必要がなくなって手間削減！！（無理やりw

# まとめ

思いついたので試してみたら動いた！！
て感じで、あまり運用上有用な使い方はなさそうですが、仕組みの理解は進みました＼(^o^)／