IAM アクセスアドバイザーからIAMポリシーを作る

## 概要

IAMユーザーグループのアクセスアドバイザーから直近1年にアクセスしたサービスのアクションを持つIAMポリシーを作成します。

jqコマンドで完結できるのが一番いいのですが、心が折れたのでPythonプログラムで実装しています。

## 結論

### STEP1 ： アドバイザーの情報を一覧を取得（CLI）

```shell
JOBID=`aws iam generate-service-last-accessed-details \
--granularity ACTION_LEVEL \
--arn arn:aws:iam::xxxxxxxxxx:group/hogehoge --output text`
```

```shell
aws iam get-service-last-accessed-details --job-id $JOBID > input.json
```

### STEP2 ： PythonプログラムでIAMポリシーを作成

リソースまでは絞れなさそうなので`*`固定としています。
また、6144文字の制限を超えるポリシーは作成できないのでその考慮も必要です。

```python
import json
from datetime import datetime, timedelta, timezone

# JSONファイルの読み込み
with open('input.json', 'r') as f:
    data = json.load(f)

# 直近1年間の日時を取得
one_year_ago = datetime.now(timezone.utc) - timedelta(days=365)


def is_within_last_year(date_str):
    date = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
    return date >= one_year_ago


# IAMポリシーのテンプレート
policy_template = {"Version": "2012-10-17", "Statement": []}

# ポリシーを保存するリスト
policies = []

for service in data['ServicesLastAccessed']:
    if 'TrackedActionsLastAccessed' in service:
        actions = [
            action['ActionName']
            for action in service['TrackedActionsLastAccessed']
            if 'LastAccessedTime' in action
            and is_within_last_year(action['LastAccessedTime'])
        ]
        if actions:
            statement = {
                "Effect": "Allow",
                "Action": [
                    f"{service['ServiceNamespace']}:{action}" for action in actions
                ],
                "Resource": "*",
            }
            policy_template["Statement"].append(statement)

            # ポリシーのサイズをチェックし、超えた場合は新しいポリシーを作成
            policy_str = json.dumps(policy_template)
            if len(policy_str) > 6144:
                # 現在のポリシーを保存
                policies.append(policy_template.copy())
                # 新しいポリシーを開始
                policy_template["Statement"] = [statement]

# 最後のポリシーも追加
if policy_template["Statement"]:
    policies.append(policy_template.copy())

# 各ポリシーをファイルに保存
for idx, policy in enumerate(policies):
    with open(f'iam_policy_{idx+1}.json', 'w') as f:
        json.dump(policy, f, indent=4)

print(f'{len(policies)}個のIAMポリシーファイルが生成されました。')
```


## コマンドライン引数にして1回の処理にする

pythonをちょっと修正してコマンドを繋げるだけですが、JOB生成に時間がかかるのでsleepを挟んでいる点に注意です。

```shell
JOBID=`aws iam generate-service-last-accessed-details \
--granularity ACTION_LEVEL \
--arn arn:aws:iam::xxxxxxxxxx:group/hogehoge --output text` \
&& sleep 10 \
&& aws iam get-service-last-accessed-details --job-id $JOBID > input.json \
&& python index.py input.json
```

#### 生成途中のjsonファイルの中身

```json
{
    "JobStatus": "IN_PROGRESS",
    "JobCreationDate": "2024-06-10T22:23:59.288000+00:00",
    "IsTruncated": false
}
```

#### 修正したPythonコード

```python
import json
from datetime import datetime, timedelta, timezone
import sys

# コマンドライン引数を取得
args = sys.argv

input_json_path = args[1]

# JSONファイルの読み込み
with open(input_json_path, 'r') as f:
    data = json.load(f)

以下は同じ
```


### 作成したIAMポリシーの例

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "access-analyzer:ListAnalyzers",
                "access-analyzer:ListPolicyGenerations",
                "access-analyzer:ValidatePolicy"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "account:ListRegions"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "acm:ListCertificates"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "amplify:CreateApp",
                "amplify:CreateBranch",
                "amplify:DeleteApp",
                "amplify:GetApp",
                "amplify:GetBranch",
                "amplify:GetDomainAssociation",
                "amplify:GetJob",
                "amplify:ListApps",
                "amplify:ListBackendEnvironments",
                "amplify:ListBranches",
                "amplify:ListDomainAssociations",
                "amplify:ListJobs",
                "amplify:ListWebHooks",
                "amplify:StartJob",
                "amplify:UpdateApp"
            ],
            "Resource": "*"
        },
        （中略）
        {
            "Effect": "Allow",
            "Action": [
                "cognito-sync:GetBulkPublishDetails",
                "cognito-sync:GetCognitoEvents",
                "cognito-sync:GetIdentityPoolConfiguration",
                "cognito-sync:ListDatasets"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "compute-optimizer:GetEnrollmentStatus"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "config:DescribeConfigurationRecorderStatus",
                "config:DescribeConfigurationRecorders"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dax:DescribeClusters"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "devops-guru:GetResourceCollection",
                "devops-guru:SearchInsights"
            ],
            "Resource": "*"
        }
    ]
}
```


## 参考

https://dev.classmethod.jp/articles/review-last-accessed-information-identify-unused-permissions/