AWS IAMユーザを作業申請の時間に限定して都度発行する

# 背景・目的

AWSのIAMはAWS環境の各種作業をするために必要なものです。AWSの世界においてIAMがセキュリティ上非常に重要な役割を果たしているため、これが漏洩・悪用されてしまうとその影響は非常に大きいです。
そんなこともありAWS公式ドキュメントの中にはIAMに関するセキュリティのベストプラクティスがあります。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/best-practices.html

このベストプラクティスの「追加セキュリティに対するポリシー条件を使用する（Use Policy conditions for extra security)」という項目の中に以下の文言が出てきます。


> 指定した日付範囲または時間範囲内でのみリクエストが許可されるように指定することもできます。 
(You can also specify that a request is allowed only within a specified date range or time range. )

普段使うIAMユーザで利用できる時間まで指定することは少ないですが、例えば本番環境の作業で事前に作業申請の提出が義務付けられている状況において、そこで申請されている作業時間帯だけ使えるIAMユーザを発行することができればセキュアなIAM運用が可能なのではないかと考えて実装してみました。

# 実装上の前提

-  都度発行されるIAMユーザが所属するためのIAMグループは既に作成されていることを前提としたとする
-  IAMグループがスイッチするためのIAMロール（IAMロールに各種AWSリソースに対するアクセス権限を付与）は既に作成されていることを前提とする
-  都度作成するIAMユーザのユーザ名は既定の文字列＋作業申請番号の組み合わせとする、これはIAMユーザ名が重複するとエラーになることへの対応

# 構成
## 構成イメージ
![IAM都度発行システム構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645488/96a48ec6-5058-e4a4-cf34-ddc0bf902411.png)


## 各AWSリソースの役割及びフロー概要

AWSリソースの役割

| リソース名 | 役割 | 
|:-:|:-:|
| API Gateway  |IAMユーザ作成時にクライアントから作業申請番号/作業開始日時/作業終了日時情報を受け取りLambda（CreateFunction)に渡す   |  
|WAF   | API Gatewayにアタッチして接続元の制限や各種セキュリティ対策を実施  |   
| Lambda(CreateFunction)  | APIGWからの通信を受けて作業開始日時〜終了日時以外の時間帯は一切の作業を禁止する（Deny）IAMポリシーを付帯したIAMユーザを発行、その情報をDynamo DB(OperationIamUserTable）に格納  |   
| DynamoDB(OperationIamUserTable)  |作成されたIAMユーザ情報（作業申請番号/作業開始日時/作業終了日時情報)を格納。Lambda（DeleteFunction）が定期的に期限切れになったIAMユーザを削除する際にこの情報をチェック   |   
| IAM  | Lambda（CreateFunction)によってIAMユーザを都度作成、IAMユーザは事前に作成されているIAMグループに所属しつつインラインポリシーで作業時間帯以外は全ての作業をDeny  |     
| Lambda(DeleteFunction) |  EventBridgeにより1時間に1回実行され、期限切れ（作業終了日時が現在時刻よりも古い）のIAMユーザがあればそのIAMユーザを削除、削除した情報をDeletedIamUserTableに格納 |   
| DynamoDB(DeletedIamUserTable)  | Lambda（DeleteFunction）によって削除されたIAMユーザの情報を格納<br>※今回の記事からは除外するが、後々運用担当者が作業実績を管理する際などにこの情報を利用することができる |   
| EventBridge  |  1時間に1回Lambda（DeleteFunction）を起動 |   
| SNS  | IAMユーザ作成完了後、IAMユーザパスワードを作業者に送付  |   

フロー概要

| No | カテゴリ　| 通信元 | 通信先 | 通信内容 |
|:-:|:-:|:-:|:-:|:-:|
| 1  | ユーザ作成　|申請拠点  | API　Gateway  | REST APIでPOST通信を実施。その際に作業情報（申請番号・作業開始日時・作業終了日時）をリクエストのbodyに含む  |
| 2 |ユーザ作成　|API Gateway  |Lambda <br>(CreateFunction)   |  APIのバックエンドにLambdaを指定して呼び出し。クライアントからのリクエストのbodyをそのままLambdaへ連携|
| 3 | ユーザ作成　|Lambda <br>(CreateFunction)  | DynamoDB<br>(OperationIamUserTable)  | クライアントから連携された作業情報をそのままDynamoDBに登録|
| 4 | ユーザ作成　|Lambda <br>(CreateFunction)  | IAM  | ユーザ名：既定の文字列＋申請番号の組み合わせ<br>パスワード：ランダム文字列<br>でIAMユーザを作成する処理を実行<br>このIAMユーザにインラインポリシーで作業開始日時〜終了日時以外の時間帯の一切の作業をDenyするルールを付帯  |
| 5 | ユーザ作成　|Lambda <br>(CreateFunction)  | SNS  | 作成したIAMユーザのパスワードを作業者メールアドレスに通知<br>通知先メールアドレスは事前登録制  |
| 6 | ユーザ作成　|SNS  | 作業者メールアドレス  | Lambdaから送られた情報で実際にメールアドレスに対して通知を実行  |
| 7 | ユーザ削除　|EventBridge  | Lambda <br>(DeleteFunction)  | 1時間に1回Lambdaを起動  |
| 8 | ユーザ削除　| Lambda <br>(DeleteFunction) | DynamoDB<br>(OperationIamUserTable)   | DynamoDB上の各アイテムについて、作業終了日時が現在時刻よりも古いかどうかをチェックし該当するものがあればNo9~No11の処理を実行以下の処理を実行した上でDynamoDB（OperationIamUserTable）から対象アイテムを削除 |
| 9 | ユーザ削除　| Lambda <br>(DeleteFunction) | IAM  | 対象のIAMユーザを削除  |
| 10 | ユーザ削除　| Lambda <br>(DeleteFunction) | DynamoDB<br>(DeletedIamUserTable)   | 削除処理を行なったIAMユーザの情報をアイテムとして作成  |
| 11 | ユーザ削除　| Lambda <br>(DeleteFunction) | SNS  | 作業者メールアドレスにIAMユーザを削除したことを通知  |

# 設定内容
全ての設定を記載していくと膨大になってしまうので、解説した方が良さそうなところだけ記載していきます。

### APIGatewayの設定
APIGatewayではPUTメソッドで以下の情報をbodyに含む通信を受け付けます。（設定としてはシンプルにプロキシ統合使ってバックエンドのLambdaに情報渡してしまうだけです）
・作業申請番号(ope_no)
・作業開始日時(ope_begin)
・作業終了日時（ope_end)

### Lambda(CreateFunction)の設定
Lambdaの中で行っている処理の流れは以下です。

1. APIGatewayからの情報の取得
1. IAMユーザの作成（ユーザ名の中にAPIGatewayから取得したope_noを利用することで一意なユーザ名にする）
1. IAMユーザ用パスワード生成とIAMユーザへのパスワード設定
1. IAMポリシー（インラインポリシー）作成
1. 作成したIAMユーザのIAMグループへの追加
1. DynamoDB(OperationIamUserTable）へのItem追加
1. メール通知のためのSNS呼び出し

この中で今回の要になる４のIAMポリシー部分だけコード載せておきます。python3.8で作成しています。
なお、インラインポリシーにしている理由について、IAMグループやロールにポリシーをアタッチしてしまうと複数の作業申請が出されたときにそれぞれのポリシーでDenyを掛け合ってしまって結局どの時間帯も作業できないということになってしまうため、IAMユーザごとに個別でポリシーをかけるようにしています。

※. 以下コードの前段で
user = iam.User('ユーザ名')
というコードを入れてIAMユーザを作成しています

    #IAMポリシーの作成（特定時間以外の操作をDeny）
    begin_pd = {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Deny",
                        "Action": "*",
                        "Resource": "*",
                        "Condition": {
                            "DateLessThan": {"aws:CurrentTime": ope_begin}
                        }
                    }
                ]
            }
 
    begin_policy = user.create_policy(
            PolicyName = ope_no + 'beginpolicy',
            PolicyDocument = json.dumps(begin_pd)
        )
 
    end_pd = {
                "Version": "2012-10-17",
                "Statement": [
                   {
                        "Effect": "Deny",
                        "Action": "*",
                        "Resource": "*",
                        "Condition": {
                            "DateGreaterThan": {"aws:CurrentTime": ope_end}
                        }
                    }
                ]
            }
 
    end_policy = user.create_policy(
            PolicyName = ope_no + 'endpolicy',
            PolicyDocument = json.dumps(end_pd)
        )


上記のコードで
beginpolicyによって、作業開始時刻が現在時刻よりも前だったら全ての処理をDenyする
endpolicyによって、作業終了時刻が現在時刻よりも後だったら全ての処理をDenyする
ことが可能になり、結果として作業申請の時間内のみIAMグループ・ロールにアタッチされた権限内の処理が行えるようになります。
なお、作業終了に関しては例えば12：00が作業終了時刻になっていたとしても12：00ジャストに強制ログアウトなどがされるような挙動にはならず、次回セッションが切れたタイミングで再接続しても全ての操作がDenyされるようになります。

### Lambda(DeleteFunction)の設定
Delete Functionで行っている処理は以下の通りです
1. DynamoDB(OperationIamUserTable)上の各ItemをScanしてope_end（作業終了時刻）が現在時刻よりも前のItemだけを抽出
2. 1の結果対象が0件であれば処理終了、1件以上対象があれば後続処理を行う
3. 対象Itemに対応するIAMユーザを削除
4. DynamoDB（OperatioIamUserTable）上の対象Itemを削除
5. DynamoDB(DeletedIamUserTable)に対象Itemを追加
6. SNSトピックを呼び出しIAMユーザを削除したことをメール通知

大量のデータが入る場合、手順1のスキャンはインデックスを設定するなどした方が良いと思いますが今回のような用途であればデータが大量になることは考えにくいのでシンプルにスキャンしています。

# まとめ
今回の仕組みは約1年前、コロナでリモート勤務が多くなったこともあり、セキュリティを担保した上でAWS環境に対するリモート作業する望ましい方法ってないかなということで検討したものです。

実運用には使っていませんが、テストで作った環境では「構成イメージ」で記載した構成は構築できて問題なく作業ができること及び時間外にはIAMユーザが使えないことは確認できています。また、構成には記載していませんがCloudTrailの情報も含めて、作業後にどの作業申請番号のユーザがいつどんな作業したのかをメール通知するということも実装しています。

理想を言うと、作業申請する仕組みと今回の仕組みを連携させることで、作業申請が完了したら自動的にIAMユーザが作成されて、ユーザにメールでお知らせできると良いなと思いました。（が、体力的にそこまでは作り込みできませんでした）