IAM roleにおける、混乱した代理問題についての説明

この記事は、 [Supershipグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/supership) の21日目の記事になります。

# はじめに
Supership株式会社の @hkak03key です。
主にaws/databricksを利用した分析環境の設計・構築に携わっております。

さて、Supershipにjoinしてから2年半が経ちました。（[2年前のadvent calendar](https://qiita.com/hkak03key/items/ad78bfc2d9d944c38012)）
この間に身に付いた知識・技術は多くありますが、 **IAM** についてはS3設計とともに考える機会が多く、難しいですが重要な技術であると認識してます。

さて、IAM roleはaws accountを跨いで利用することができますが、それ単体で使用すると **「混乱した代理」問題[^id_roles_create_for-user_externalid]** というセキュリティリスクに晒されます。
これについて、最初はよく分からなかったのですが、チームメンバーの助けもあり理解に至りました。

本記事では、 **「混乱した代理」問題** について、具体的な例を交えながら説明します。

# IAM roleの簡単なおさらい
正確な定義は [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html) をご確認頂きたいのですが、使う側としては

- EC2とかlambdaのようなコンピューリングリソースに権限を渡す仕組み
- 他のIAM user/roleに、標準の長期認証情報を渡さずに、権限を渡す仕組み

という用途があるということを押さえておけば一旦は大丈夫でしょう。
そして、今回は後者の方を中心に取り上げます。

では、 **どうやって他のIAM user/roleに権限を渡すのか** ということですが、これはClassmethodさんの記事[^aws-sdk-for-python-boto3-assumerole]をご覧いただくのがとてもわかり易くてよいかと思います。

記事内のコードを読むとわかるのですが、ざっくりまとめると
**「利用したいroleの短期的な認証情報を発行し、それを利用してsessionを立ち上げ、awsにアクセスする」**
という流れでこれが実現されています。

そして、この方法を利用すると、 **他のaws accountへ、長期認証情報を発行せずに権限を渡すことができます。**
アクセスキーの管理をawsが巻き取ってくれる、ということですね。
外部組織に対して長期認証情報を発行せずにアクセスさせることができるのは、セキュリティ的にとてもメリットがある方法であると思います。

# 混乱した代理問題
さて、そんな一見便利でセキュリティ的にも優れたIAM roleですが、弱点も存在します。

ユーザのIAM roleをAssume Roleし、ユーザのAWS内に入って何らかのサービスを提供する、そんなサービスがあったとします。
このとき、 **悪意のあるユーザによってIAM role arnを推測された場合、推測されたIAM roleでawsにアクセスされてしまうという問題があります。**

これについて、具体例を交えて説明します。

## 混乱した代理における具体例
ここでは、「登録したIAM roleが所属するaws accountのs3 bucket一覧を見れるサービス」である "some-service" があったとします。

### 正規ユーザによるサービス利用
`my aws account` の管理者Xさんは、このサービスを利用したいと考えました。
Xさんは、このサービスのために手順通りにリソースを準備します。
即ち、

- IAM roleを作成する。
- IAM roleに必要な権限を振る。
- IAM roleに `some service aws account` への信頼関係を設定する。
- "some-service" にIAM role arnを登録する。

この手順を踏むことで、めでたくXさんは "some-service" を利用し、s3 bucket一覧を確認することができました。

![authorized user w/o eid](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223584/f0ce5eaf-6831-dae0-54de-a99cb84e8038.png)

### 不正なユーザによるアクセス
さて、世の中ではセキュリティのインシデントが絶えないですが、残念ながら "some-service" に対しても悪意あるユーザによって利用されているようです。
悪意ある第三者Yさんは、「混乱した代理」問題に対して脆弱な "some-service" を利用して、以下のように攻撃を仕掛けます。

- "some-service" に既に登録されているIAM role arnを推測する。
    - IAM role arnは、formatが決まっている上にrole名もHuman-Readableな名前を持つことが多いため、比較的容易に推測できる
- "some-service" に **推測したIAM role arnを登録する。**
    - 推測したIAM roleは既にサービスで利用されているため、 **信頼関係も含めて設定が完了しており、利用できてしまう**

このようにして、Yさんは `my aws account` の関係者ではないのにもかかわらず、 `my aws account` 内で発行された "some-service" 用のIAM roleを利用して **"some-service" から不正に `my aws account` のs3 bucket一覧を確認できてしまいました。**

![illigal user w/o eid](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223584/334d9203-6ec0-1015-54bc-a05feefb4f06.png)

## External IDを利用した回避方法の具体例
この問題を回避する方法として、 **External IDを利用すること** が挙げられます。
これは、 **「serviceのユーザ毎に異なるIDをservice側から発行し、それをassume roleする際の条件とすることでなりすましを防ぐ」** という戦略です。

### 正規ユーザによるサービス利用
`my aws account` の管理者Xさんが利用する場合のフローは以下のように変更されます。
即ち、

- IAM roleを作成する。
- IAM roleに必要な権限を振る。
- IAM roleに `some service aws account` への信頼関係を設定する。
- "some-service" にIAM role arnを登録する。
- "some-service" から与えられたExternal IDを、信頼関係の条件として設定する。
    - **External IDは、some-serviceのユーザ毎に異なる点に注意**

![authorized user w eid](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223584/305e89fd-c341-217f-a7ae-c7de6454b152.png)

これだけだと何がどう嬉しいのかわからないので、不正なユーザによるアクセスの場合を見てみましょう。

### 不正なユーザによるアクセス
悪意ある第三者Yさんは、 "some-service" を利用して、以下のように攻撃を仕掛けようとします。

- "some-service" に既に登録されているIAM role arnを推測する。
- "some-service" に 推測したIAM role arnを登録する。

しかしここで、Yさんは **"some-service" から与えられたExternal IDを、信頼関係の条件として設定する必要があります。**
**なぜなら、External IDは、some-serviceのユーザ毎に異なるためです。**

先程Xさんによって登録されたExternal IDとは異なるID（画像の例では、user Xに対しては `X01234` 、user Yに対しては `Y56789` ）が発行されたため、このままではassume roleに失敗してしまいます。
しかしこのとき、Yさんは `my aws account` の関係者ではないため、 "some-service" から与えられたExternal IDを信頼関係の条件として設定することも、それを依頼することもできません。

こうして、 **「混乱した代理」問題を回避することができます。**

![illigal user with external id](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223584/252caf9f-ce54-29de-b5d4-6fd020637bdf.png)

## External IDの設定・利用方法
設定方法はaws公式ドキュメント[^id_roles_create_for-user_externalid]を参照してください。
また、利用方法についてですが、Classmethodさんの記事[^aws-sdk-for-python-boto3-assumerole]内の `client.assume_role()` に引数 `ExternalId` を与えることで利用可能です。
詳細は、boto3のドキュメント[^boto3-sts-assume-role]を参照してください。

# 終わりに
「混乱した代理」問題とその回避方法について、具体例を交えながら説明しました。
今回の例はs3 bucketの一覧だけですが、サービスによっては、「ec2のinstanceを作る権限」「s3 objectを作る権限」などが与えられている場合があります。
そのような場合に悪用されれば **クラウド破産に繋がります** のでご注意ください！

## 宣伝など
### 個人的な話
[terraform Advent Calendar 2020](https://qiita.com/advent-calendar/2020/terraform)にも投稿したので、こちらの記事も是非お読みください。

[OOPの考え方を応用したTerraformディレクトリ構成の提案](https://qiita.com/hkak03key/items/cecfe063cbb7eac15401)

### 会社的な話
Supershipではプロダクト開発やサービス開発に関わる人を募集しておりますので、どうぞよろしくお願いします。
個人的には、僕らと一緒にデータ分析基盤について考え、作る仲間を絶賛大募集しております。

[Supership株式会社 採用サイト](https://supership.jp/recruit/)


[^id_roles_create_for-user_externalid]: docs.aws.amazon.com: [AWS リソースへのアクセス権を第三者に付与するときに外部 ID を使用する方法](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html)
[^aws-sdk-for-python-boto3-assumerole]: dev.classmethod.jp: [AWS SDK For Python (Boto3) で AssumeRole を使ってみた](https://dev.classmethod.jp/articles/aws-sdk-for-python-boto3-assumerole/)
[^boto3-sts-assume-role]: boto3.amazonaws.com: [STS — Boto3 Docs 1.16.40 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sts.html#STS.Client.assume_role)