まず最初に、本当にやりたかったことと、調べているうちにだいぶ脱線して得た知識を書いてます。少々規模がでかいです。
最初にやりたかったのは、 EC2 role (Instance Profile) を使っているときに awscliとかで Unable to locate credentials
が稀に出るという問題の解消です。
で、いきなり(今わかっている) 答えを書くと、
あたりの環境変数を指定しておくです。
AWS CLI Configuration Variables — AWS CLI 1.18.123 Command Reference
上記を含む、大部分が脱線した内容を掘り下げて行きますが、前提となる知識が結構多く、いちいち細かく書くと長すぎるので、AWS上級者向けです。
EC2ロール
EC2ロールってどうやって動いている?
そもそも私も理解が間違っていたのでここから始める。なぜEC2ロールを利用するのかは前提知識なので説明省くが、EC2を起動するときに、EC2ロール/InstanceProfileを指定したらなんでその権限が使えるのかを調べる。
これはググればすぐに出てくるが、AWSのドキュメントにもある。169.254.169.254
でお馴染みのインスタンスメタデータに credentialが格納され、それをbotocoreとかaws-sdk/ライブラリが参照している。まず最初に私が間違っていたのは、メタデータにいきなりcredentialが入っていることはないと思っており、てっきりaws-sdkとかがstsを叩いて発行していると思っていたが、そうではなくていきなり使える状態でメタデータから取得できる。もちろんtoken付きなので有効期限はあるが、自動で更新された状態でメタデータに入ってくるらしい。Expireの5分前には新しいのに更新されているらしい。
早速自分の誤解が一つ減った。
VPC-Endpointを使い、インターネット辞めたいとき、stsへのエンドポイントがなくともEC2ロールは使える。
本当にstsのエンドポイントが必須なのはEC2でさらにassume-roleしたい場合のみ。
EC2ロールを使ってるときになんでUnable to locate credentials
おそらくメタデータの取得が失敗することがあるのではないかと予想している。つまり curl 169.254.169.254
が稀に失敗している?。それを見越してのAWS_METADATA_SERVICE_NUM_ATTEMPTS
なのかなと思う。ただしこの実装は botocore など aws-sdk側の実装で実現されている。よって、sdkなんか使わない独自実装はともかく、場合によってはsdkの言語やバージョン次第では対応していないかもしれない。ここはちょっと確証が持てないので、いまも実験中、そもそもどれぐらいの頻度でエラーになるのか。
IMDSv2?
メタデータとさっきまで書いていたやつのv2版のことです,ということはいままではv1だった。v2が出た理由は一応はセキュリティー観点かららしい。ググればたくさんでてくるが
- 問題点であるSSRFは、一発即死系のかなりやばいやつ
- v1にくらべてv2は
ちょっと攻撃をやりづらくはするが、完璧では全然ない
詳細は下記で詳しく述べられています。AWSに限らずクラウド屋なら必読です・他のプラットフォームでも同じ問題があります。
SSRF対策としてAmazonから発表されたIMDSv2の効果と限界 | 徳丸浩の日記
このエントリでもIMDSv2関連を書きますが、SSRF対策は真剣に取り組むべきです。とりあえずすぐに対応できそうなことを一つ上げておくと、EC2ロールだからといって、無駄に強い権限を割り当ててはいけない
IMDSv2 / v1 の違い
違いを列挙
- v2/v1ともにIPはおなじ、v2はHeaderが必須になる
- v2は最初にPUTをうってTokenをもらい、そいつを使う、v1はTokenとかなしにいきなりデータ取れる
- デフォルトで v2 / v1 両方が生きた状態で起動。実質セキュアにするならv1を殺す必要あり
- IMDSを設定するためのmodify-instance-metadata-options
- PUTはデフォルトでhop=1でしかたたけない、つまりdocker・コンテナ内からはデフォルトたたけない
今回ハマったのは最後のやつ、network的に、ルーターかます(forwardする)とあかん。ので、hostモードでdocker内からたたけない。
aws-sdkとかの credential取得方法
結構なところで言及されているが、環境変数・所定の設定ファイル・EC2ロールなど、優先順位別に試していき、見つかったものを使う。これはみんな知っているはず。
aws-sdkとかの IMDSv2 対応
上記より、EC2ロールが実際に評価される場合、どうやってとっているのか?botocoreで実装されているのでちょっと読んで調べた。
- v2のためにPUTをいきなり打つ(Token取得)
- Token取れなかったら v1方式で、取れたらv2方式
- 最初からv1でやれ!という設定は今の所できない
言語によっては aws-sdkで IMDSv2が出たしたときにちょっと問題になったらしいが、今の所上記の通り。
さあ、感のいい人ならもう気づいたと思うが、コンテナ内からEC2ロールを使おうとしたときどうなるか。
答えは最初のv2のPUTがhopの問題で失敗し、デフォルト設定だとcredentialの取得が常に1秒遅くなります。
さらに冒頭で書いた AWS_METADATA_SERVICE_TIMEOUT
を30とかに弄っている場合はどうなるか?常に30秒遅くなります。
いやいや、そもそもコンテナからEC2ロールつかうなよ・Taskロールつかえよ・きょうびFargateやろ普通
正論。
だけど俺の経験としてTaskロール使えないケースがちょいちょいあるんだよな。EC2ロール・Taskロールが使えるのは、aws-sdkのバージョンや別にSDK使わず自前実装しているならば、アプリ側の対応次第となる。特にどうしても古いアプリを使わなければならないときなので、ベースとなっているaws-sdkが古すぎて Taskロールガン無視・EC2ロールは使えたりとか結構ある。OSSなら新しいsdkを指すなり、独自実装ならPR出すなりすればいいと思うけど。
今回の問題の対策
AWS_METADATA_SERVICE_TIMEOUT
のデフォ1秒を30秒とかにしたときにEC2ロールからcredentialの取得が常に遅い
の対応は modify-instance-metadata-options
で --http-put-response-hop-limit 2
を指定しろになる。
そもそもコンテナ内からEC2ロール使わなくて済むならこんな対処いらないし、Taskロール使うべき。
Taskロールについて
話がそれるが、ついでなのでTaskロールの実態について調べてみた。
取得方法
IAM Roles for Tasks - Amazon Elastic Container Service
curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
よく見るとIPアドレスが違います。170.2
です。のでIMDSではない。次に $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
環境変数ですが、ECSでTaskロール指定して稼働しているとついているらしい。実際にそのようになってます。で、Taskロールが使える状態で、curlで IMDS(=つまりEC2ロール)もみえるかcurlでやってみた。すると「普通に見えたぞ」。
$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
の有無をみてTaskロールがついてるか判断- Taskロールついてる判断したら
169.254.170.2
のほうで取得する - Taskロールついてない判断したらEC2ロール試す
となっているはず。つまり、Taskロール指定しててもIMDSを直接叩くSSRFの攻撃の防御にはならない。
これに対応するには
- おとなしくFargate使え
- どうしてもEC2で行くなら、コンテナ->IMDSのアクセスをiptablesで殺すとか