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


はじめに

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

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

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

このページを抜粋

インスタンスメタデータサービス(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リソースを操作します。

処理イメージ

レスポンス例

一時セキュリティ認証情報
{
  "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ロールの信頼関係に下記を指定

{
  "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にセット

[default]
aws_access_key_id=~~~
aws_secret_access_key=~~~

docker-compose.ymlを作成

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はエラーメッセージ以外は何もログ出力しません(念の為)

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ファイルに記載のdefaultのコメントアウトを外すと通常はdefaultネットワークを
使いつつ、クレデンシャル取得時のみ169.254.169.0/24ネットワークを使います。

ちなみに、この設定は入れた方がいいと思ったので、roleSessionNameに自動的に
スイッチロール元のIAMユーザ名が入るようにしています。

ローカルIMDSのコードはこちら

どこで使えるか?

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

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

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

~/.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^)/