チームで複数のAWSアカウントを運用するときのそこそこ良い感じのID管理方法

これは [NTTテクノクロスアドベントカレンダー2022](https://qiita.com/advent-calendar/2022/ntt-tx) の22日の記事です

# 自己紹介
こんにちは、NTTテクノクロスの諸星(@Echos)です。
業務としてはDX推進に関連する業務に従事しております。
特にデータ分析・ログ活用等、お困りなことがありましたら是非ご相談ください。

# 背景説明
上記DX業務を遂行するなか、従属的な業務といたしまして基盤環境として利用しているAWSの管理なども行っております。

ここで突然ですが質問です。
**『皆さんAWSアカウントをいくつ管理していますか？』**
1環境？10環境？それとも100環境？風の噂では10,000環境以上という人もいるみたいですね。怖い・・・

さすがに10,000はいかないまでも、弊社チーム内も様々なお客様にサービス展開をさせていただいてる関係上それなりの環境数を預からせていただいています。

ここで多くのAWS管理者が頭を悩ますのがAWSのID管理問題かと思います。
弊社チーム内もご多分に漏れずこの問題を抱えており、以下のような課題を抱えておりました。

- **ID管理の煩雑化** : 複数環境・流動的なメンバー編成により、IDの管理が煩雑になりやすい状況でした。
- **権限管理の煩雑化** : 必要な人に最低限必要な権限付与が理想です。参照のみで良いのに強い権限を利用するなどの状況が発生していました。
- **利用状況確認の煩雑化** : ID利用の監査情報が各環境で管理されており、分散管理状態となっていました。

これらの諸問題の解決として、[AWS Organizations](https://aws.amazon.com/jp/organizations/)などを採用するなど、基盤側で用意された素晴らしい諸機能があることは把握しております。
しかし様々な要因により、今回は独立した複数のAWS環境に対して改善をすることを目的として対策を実施しております。

# 改善方針

いろいろ試行錯誤した結果、ベターな策として以下の方針としました。
（どうやってこの結論にたどり着いたかに関して真に驚くべき工程を経たがこの記事はそれを書くには狭すぎるため、それでも聴きたいかたは別途ご連絡ください）

### 今回ご用意するもの　

1. 環境毎に[IAM](https://aws.amazon.com/jp/iam/)ユーザを作るのをやめ、[STS](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp.html)を用いた一時ユーザ発行を採用する。
    1. 権限は利用シーンに分け、**強いユーザ**と**参照ユーザ**を用意する。
    1. 強いユーザは利用を時限式にして必要な時のみ利用可能とする
1. ID管理を行うWebアプリを作成し、一時利用ユーザの発行を行う
    1. 簡易的な承認機構を用意し、利用目的・利用時間を厳密に管理を行う
    1. 各環境のAudit logと連携し、監査可能な状況とする

### 出来上がったもの
それで出来上がったものがこちらになります。

**★ユーザ登録画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/a8c3c33a-98c9-c21b-8538-68e524fe862d.png)
- 管理職 : 利用申請の承認が可能なユーザです。
- リーダー : 利用申請が可能なユーザです。
- プロパー : 環境利用者です。

利用者ないしリーダーが環境利用計画に基づいて申請を実施し、管理職が可否の判断をする流れになります。

**★テナント管理画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/10d9a10f-bf07-76d4-ac70-816927e1aacf.png)

AWSのテナント情報を管理します。

**★利用申請画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/7d1ad86a-9ab1-d3fb-8c20-a43a2692acb6.png)

利用者・利用時間・利用目的を記載し、申請します。

**承認画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/cb58b5b2-fbbf-8367-c0cb-2c3113ca10d8.png)
管理職権限では申請内容の承認・否認ができます。

承認されると、利用者には利用開始ボタンが表示され、ここからAWSマネジメントコンソールに遷移可能となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/83309254-e8c9-6656-5ac7-6df41516e07e.png)

**★監査画面**
![利用ログ 2022-12-13 19-22-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9461/92dbe031-2a90-21b2-1aa6-6d1e0bd28475.png)
ログイン時間は記録されテナントごとに確認が可能です。

---

上記対応により、今後AWSアカウントが100や1000に増えても、また、チームメンバーがどのように変動しても、
AWSのID管理という面では一元的に/効率的に管理できるようになりました。

以降では本アプリ作成に向けた技術要素に関してTips的な部分を抽出してご説明したいと思います。

:::note alert
以降の説明では、説明の簡略化を目的として厳密な権限設定やアプリ設定を行っておりません。また、意図的に省いているものもあります。
本エントリを参考に同様の機能を実装されることを検討される方は、上記をよくご理解いただいたうえで、<font color="Red">**ご自身の判断の元環境に即した諸設定**</font>を行ってください。
:::

# 要素説明
:::note info
本アプリは作者の趣味でrubyで実装されており、以降の説明も断りなくrubyコードの断片での説明となります。
AWSの各種操作には[AWS SDK for Ruby](https://aws.amazon.com/jp/sdk-for-ruby/)を利用しておりますことをご了承ください。
:::

## 時限式一時ID発行
本アプリでは以下の機能を実現しました。

- 許可された時間のみ利用できるユーザの発行
- 強いユーザと参照のみのユーザの二種を必要に応じて発行
- AWSマネジメントコンソールへログインした状態で遷移

上記実現に向けた技術要素としては[STS](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp.html)を利用しております。

### ポリシーの準備
利用するポリシーとして以下を準備しました。

**強いユーザポリシー**
```ruby
	policy = {
		"Version": "2012-10-17",
		"Statement": {
			"Effect": "Allow",
            (省略)
			"Condition": {
				"DateGreaterThan": {"aws:CurrentTime": "#{s}"},
				"DateLessThan": {"aws:CurrentTime": "#{e}"}
			}
		}
	}

	client = Aws::STS::Client.new(region: 'ap-northeast-1')
	session = client.get_federation_token(
		name: "feduser-#{user.user_id}",
		policy: policy.to_json
	)

```
|項目|内容|
|-|-|
|(省略)|環境の即したアクセスポリシーを適宜設定ください|
|DateGreaterThan|ポリシー適用の有効期間開始。Webアプリより申請された時間がセットされます [※参考](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_examples_aws-dates.html)|
|DateLessThan|ポリシー適用の有効期間終了。Webアプリより申請された時間がセットされます|
|feduser-#{user.user_id}|チームメンバ毎のIDを代入し誰の利用かを明確化しています。|

尚、設定された時間外で利用された場合、参照のみユーザと同じ程度の権限となります。

**参照のみユーザ**
```ruby
	client = Aws::STS::Client.new(region: 'ap-northeast-1')
	session = client.get_federation_token(
		name: "feduser-#{@user.user_id}",
		policy_arns: [
			{
				arn: "arn:aws:iam::aws:policy/ReadOnlyAccess"
			}
		] 
	)
```
|項目|内容|
|-|-|
|policy_arns|AWS標準のReadOnluAccessポリシーをそのまま利用しています。|
|feduser-#{user.user_id}|チームメンバ毎のIDを代入し誰の利用かを明確化しています。|

### 一時ユーザ作成とログインURLの生成
上記ポリシーを元に、一時利用ユーザを作成しマネジメントコンソールにログイン可能なログインURLの生成を行います。
処理のながれは[こちら](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html)を参照ください。

```ruby
	signInURL  = "https://signin.aws.amazon.com/federation"
 
	json_string_with_temp_credentials = CGI.escape({
      	sessionId: session.credentials.access_key_id,
      	sessionKey: session.credentials.secret_access_key,
      	sessionToken: session.credentials.session_token
	}.to_json)
 
	getSigninTokenURL  =  signInURL + 
                           "?Action=getSigninToken" +
                           "&DurationSeconds=43200" + 
                           "&SessionType=json&Session=" + 
                           json_string_with_temp_credentials; 
	signin_token  = JSON.parse(URI.parse(getSigninTokenURL).read)['SigninToken']
	signin_token_param = "&SigninToken=" + CGI.escape(signin_token)
 
	issuer_param  = "&Issuer=" + CGI.escape(<イシュアURL>)
	destination_param = "&Destination=" + CGI.escape(<対象とするコンソールURL>)
	login_url = signInURL + "?Action=login" + signin_token_param + issuer_param + destination_param
```

上記の処理でログインURLを生成できましたので、Webアプリ側でリダイレクトすることで一時ユーザでのID利用が可能となります。

## Webアプリ
:::note alert
本アプリの特性上、IDを払い出すIAMユーザを別に用意しアプリ内でID情報を管理しています。
保存に当たっては<font color="Red">**最低限暗号化の実施やWebアプリの不用意な公開などはしない**</font>ようにご注意ください。
:::
### 機能概要
Webアプリとしては以下の要件を満たす機能を最低限実装いたしました。
|機能|内容|
|-|-|
|AWSテナント管理|一時ユーザ発行用のIAMユーザのConsumerKey/Secretを登録・管理します。|
|ユーザ管理|利用ユーザの管理を行います。ID/Pass/権限|
|利用申請機能|AWS IDの利用申請を行います。利用者/利用時間/利用テナント/利用目的を登録します|
|承認機能|上記で申請された内容を確認し、承認を行います。|
|監査ログ|IDの発行を記録し、だれがいつIDを利用したか/しなかったのかを管理します|

### 構成要素
本Webアプリは以下で構成されています。
|項目|要素名|
|-|-|
|利用言語/フレームワーク|[ruby](https://www.ruby-lang.org/ja/) + [sinatra](https://sinatrarb.com/)|
|フロントエンド|[Bootstrap5](https://getbootstrap.jp/)|
|データベース|[MongoDB](https://www.mongodb.com/)|

今回のように思い付きを早期にWebアプリ化するにはRuby+Sinatraの組み合わせは最高なのでお勧めです。

### Tips
何か技術的に特筆すべきことをしているかとソースを見直してみましたが、普通のWebアプリでした。
思いついてからWebアプリの導入まで構想２週間、実装３日という感じです。



# 終わりに
本アプリ投入により弊社チーム内でのAWS ID管理フローは飛躍的に改善しました。

もともとの課題からの改善点は以下のとおりです。

- **ID管理の煩雑化の解消** : 管理アプリ経由での一元的管理に切り替えシステム化することで、IDの払い出し・承認作業等一連のフローを改善することを可能とした。
- **権限管理の煩雑化の解消** : 必要なときにのみ強い権限を利用可能とすることで、テナントごとの権限管理が不要となり、ID管理の安全性が向上した。
- **利用状況確認の煩雑化の改善** : 監査情報を集約し、全テナント横通しで参照できる仕組みとすることで、トレーサビリティが向上した。

しかし本アプリ、草の根的に私１名で作っているため、持続性で問題を抱えております。
ゆくゆくは外に広くつかっていただけることを夢見て、まずは社内展開をと考えております。
絶賛お社内の友達募集中ですので、ご興味のある方はご連絡よろしくお願いいたします。