使用していないIAMロールを一括削除するシェルスクリプトを作成したけど、コンソールから削除した方がいいと思った件

# スクリプト作成の背景

AWSの個人アカウントで不要なIAMロールが増えてきたので、IAMロールの整理を考えたのが切っ掛けです。
コンソールからでも一括削除はできますが、数が増えた場合でも楽に削除できるようにしたいと思い作りました。

AWS SDKの方が楽にできそうだと思いましたが、長めのシェルスクリプトを書いたことがなかったのでチャレンジしてみました。

# コンソールから削除した方がいいと思った理由

長期間使用していないけど必要なIAMロール（*）があると思いますが、その調査をするのにコンソールを使用することが多いと思うので、それならコンソールからIAMロールを一括削除した方が早いのでは？と思った次第です。

必要なIAMロールが存在しない（削除しても深刻な問題にならない）私的アカウント等であれば、スクリプトを使用して削除していいと思います。

*機会がなくて使用されていないIAMロールのことで、Configルール違反時の自動修復で利用されるロールや、EventBridgeルールのターゲットであるLambdaのロールなどが当たります。
違反時やイベント発生時にはそのロールが必要だが、違反やイベントが発生していない限り使用されないロールです。

# 削除対象のIAMロール

- 指定期間内にIAMロールを使用していないもの
    - デフォルトは30日
    - 日数を変更する場合はスクリプト内の変数`DELETE_TIME`を変更
- 存在するインスタンスで使用されているIAMロールは削除しない
- 削除しないIAMロールを指定可能
    - スクリプト内の変数`except_role_list`で指定
    - デフォルトでは「AWSServiceRoleFor」で始まるIAMロールは削除しない
        - AWSサービスを利用する始めに作成されるロールだと思われるので、そのサービスを再度利用する際に用いられるから削除する必要はないと考えた

# ログ

標準出力にも出ますが、スクリプト実行時にカレントディレクトリに以下の形式のログファイルが作成されます。


- スクリプトのログ
    - `old_iam_role_delete_script.sh_20210925_195844.log`
- 削除したインスタンスプロファイルリスト
    - `delete_instance_profile_20210925_195844.log`
- 削除したIAMロールリスト
    - `delete_role_20210925_195844.log`

# シェルスクリプト

zshを選択した理由は特になく、bash、zshの挙動の違いもよく分かっていません。
細かな検証は行っていないため、使用する場合はご注意ください。

```zsh:old_iam_role_delete_script.sh
#! /bin/zsh

# ログファイルと標準出力・エラー出力先を指定
LOG_FILE=./$(basename ${0})_`date "+%Y%m%d_%H%M%S"`.log
DLETE_ROLE_FILE=delete_role_`date "+%Y%m%d_%H%M%S"`.log
DELETE_INSTANCE_PROFILE_FILE=delete_instance_profile_`date "+%Y%m%d_%H%M%S"`.log
# 4320000秒=50日
# 3456000秒=40日
# 2592000秒=30日
# DELETE_TIME以上使用していないIAMロールが削除対象
DELETE_TIME=2592000
DELETE_DAY=$(($DELETE_TIME / 86400))

exec > >(tee -a ${LOG_FILE}) 2>&1

# aws cliのプロファイル設定
if ([[ $1 == "--profile" ]] || [[ $1 == "-p" ]]) && [[ ! $2 == "" ]]; then
    profile=$2
elif [[ $1 == "" ]]; then
    profile="default"
else
    cat << EOM
Usage: $(basename "$0") [-p|--profile]
    -p, --profile PROFILE-NAME      Specify the profile to be used by aws cli
    None                            Use "default" as a profile for aws cli
EOM
    exit 1
fi

# 削除したくないIAMロールを指定. grepで除外する
except_role_list=("^AWSServiceRoleFor")

# 全てのロール（except_role_listを除く）を取得
role_list=($(aws iam list-roles --query "Roles[].[RoleName]" --output text --profile ${profile} | grep -vE \
    `i=1; \
    for except_role in $except_role_list
    do
        if [[ $i -eq $#except_role_list ]]; then
            echo -n $except_role
        else
            echo -n "${except_role}|"
        fi
        ((i++))
    done`))

if [[ ! $? -eq 0 ]]; then
    echo "IAMロール取得の際にエラーが発生しました"
    exit 1
fi

echo "取得したIAMロールのリスト" >> $LOG_FILE
for role in ${role_list[@]}
do
    echo $role >> $LOG_FILE
done

# 現にインスタンスで使用されているロールはロールリストから除外(削除しない)
associations_instance_profile_list=($(aws ec2 describe-iam-instance-profile-associations --query "IamInstanceProfileAssociations[].IamInstanceProfile.[Arn]" --output text --profile ${profile} | cut -d "/" -f2))

if [[ $#associations_instance_profile_list -ge 1 ]]; then
    target_role_list=()
    echo "現にインスタンスでインスタンスプロファイルのリスト（削除しない）" >> $LOG_FILE
    for instance_profile in $associations_instance_profile_list[@]
    do
        echo $instance_profile >> $LOG_FILE
        target_role=$(aws iam get-instance-profile --instance-profile-name $instance_profile --query "InstanceProfile.Roles[0].RoleName" --output text --profile ${profile})
        echo "インスタンスプロファイルにアタッチされたIAMロール ${target_role}" >> $LOG_FILE
        target_role_list+=$target_role
    done

    role_list=($(for role in $role_list[@]
    do
        echo $role
    done \
    | grep -vE \
    `i=1; \
    for target_role in $target_role_list
    do
        if [[ $i -eq $#target_role_list ]]; then
            echo -n $target_role
        else
            echo -n "${target_role}|"
        fi
        ((i++))
    done`))
fi

# IAMロール削除前にインスタンスプロファイルを削除する必要があるため、存在する全てのインスタンスプロファイルのリストを取得
# ただし現にインスタンスで使用されているインスタンスプロファイルは除外
if [[ $#associations_instance_profile_list -ge 1 ]]; then
    role_name_of_instance_profile_list=($(aws iam list-instance-profiles --profile $profile --query "InstanceProfiles[].Roles[].[RoleName]" --output text | grep -vE \
`f=1; \
for i in $associations_instance_profile_list[@]
do
    if [[ $f -eq $#associations_instance_profile_list ]]; then
        echo -n "${i}"
    else
        echo -n "${i}|"
    fi
    ((f++))
done`))
else
    role_name_of_instance_profile_list=($(aws iam list-instance-profiles --profile $profile --query "InstanceProfiles[].Roles[].[RoleName]" --output text))
fi

# ロールが削除基準に当たるかチェックし、削除基準に該当すればロールを削除リストに入れる
delete_role_list=()
echo "${DELETE_DAY}日以上使用していないIAMロールをチェックします"
for role in ${role_list[@]}
do
    echo "${role} のlast_used_dateを取得します" >> $LOG_FILE
    last_used_date=$(aws iam get-role --role-name $role --query "Role.RoleLastUsed.LastUsedDate" --output text --profile ${profile})
    echo $last_used_date >> $LOG_FILE

    if [[ $last_used_date == "None" ]]; then
        delete_role_list+=$role
        echo "追跡期間中のアクセスがないロール ${role} （削除対象）"
        continue
    fi

    last_used_date_epoc=$(date -jf "%Y-%m-%dT%H:%M:%S" "$(echo $last_used_date | sed s/+00:00$//)" "+%s")
    result_epoc=$(expr `date -u "+%s"` - $last_used_date_epoc)
    echo "現在日時 - 最終使用日時 = ${result_epoc}(秒)" >> $LOG_FILE

    if [[ $result_epoc -ge $DELETE_TIME ]] && ; then
        echo "${DELETE_DAY}日以上使用していないロール ${role} （削除対象）"
        delete_role_list+=$role
    fi
done

# 利用者に削除するロールの一覧を表示
echo "以下のロールを削除します"

for role in $delete_role_list[@]
do
    echo "・ ${role}"
done

# 削除の最終確認
echo -n "削除していいですか？[y/N]"
read answer

# Yyなら削除開始
case $answer in
    [Yy] )
    echo "削除を実行したロール" >> $DLETE_ROLE_FILE
    echo "削除を実行したインスタンスプロファイル" >> $DELETE_INSTANCE_PROFILE_FILE
    for role in $delete_role_list[@]
    do
        # インスタンフプロファイル削除
        for role_name_of_instance_profile in $role_name_of_instance_profile_list[@]
        do
            if [[ $role == $role_name_of_instance_profile ]]; then
                instance_profile_name=$(aws iam list-instance-profiles-for-role --role-name $role --query "InstanceProfiles[].[InstanceProfileName]" --profile $profile --output text)
                aws iam remove-role-from-instance-profile --instance-profile-name $instance_profile_name --role-name $role --profile $profile
                aws iam delete-instance-profile --instance-profile-name $instance_profile_name --profile $profile
                if [[ ! $? -eq 0 ]]; then
                    echo "インスタンスプロファイル ${instance_profile_name} 削除中にエラーが発生しました。"
                    exit 1
                fi
                echo "IAMロール ${role} のインスタンスプロファイル ${instance_profile_name} 削除完了"
                echo $instance_profile_name >> $DELETE_INSTANCE_PROFILE_FILE
            fi
        done
        # ロールのインラインポリシー削除
        inline_policy_list=($(aws iam list-role-policies --role-name $role --query "PolicyNames" --output text --profile $profile))
        if [[ -n $inline_policy_list ]]; then
            for policy_name in $inline_policy_list[@]
            do
                aws iam delete-role-policy --role-name $role --policy-name $policy_name --profile $profile
                if [[ ! $? -eq 0 ]]; then
                    echo "IAMロール ${role} インラインポリシー ${policy_name} を削除中にエラーが発生しました"
                    exit 1
                fi
            done
        fi
        # ロールの管理ポリシーデタッチ
        managed_policy_list=($(aws iam list-attached-role-policies --role-name $role --query "AttachedPolicies[].[PolicyArn]" --output text --profile $profile))
        if [[ -n $managed_policy_list ]]; then
            for policy_arn in $managed_policy_list[@]
            do
                aws iam detach-role-policy --role-name $role --policy-arn $policy_arn --profile $profile
                if [[ ! $? -eq 0 ]]; then
                    echo "IAMロール ${role} から管理ポリシー ${policy_arn} をデタッチ中にエラーが発生しました"
                    exit 1
                fi
            done
        fi
        # ロール削除
        aws iam delete-role --role-name $role --profile $profile
        if [[ ! $? -eq 0 ]]; then
            echo "IAMロール ${role} 削除中にエラーが発生しました"
            exit 1
        fi
        echo "IAMロール ${role} 削除完了"
        echo $role >> $DLETE_ROLE_FILE
    done
    ;;
    *)
    echo "スクリプト終了"
    exit 0
    ;;
esac
```

https://github.com/kazu638/aws-scripts/tree/main/shell


# スクリプト実行例

実行環境

```
[21-09-25 21:01 ~/aws]# sw_vers
ProductName:    macOS
ProductVersion: 11.6
BuildVersion:   20G165
[21-09-25 21:01 ~/aws]# echo $SHELL
/bin/zsh
```
10日使用していないIAMロールを削除対象に指定し、削除不可のIAMロールを指定した場合の実行結果。

```
[21-09-25 19:58 ~/aws]# ./old_iam_role_delete_script.sh --profile hands-on-user   
10日以上使用していないIAMロールをチェックします
10日以上使用していないロール DeliverLogsRole-CreateVpcFlowLogs （削除対象）
10日以上使用していないロール LambdaExecutionS3AccessRole （削除対象）
10日以上使用していないロール new-resource-all-delete-role-nuiqf714 （削除対象）
10日以上使用していないロール new-resource-delete-role-038dhko1 （削除対象）
追跡期間中のアクセスがないロール test （削除対象）
以下のロールを削除します
・ DeliverLogsRole-CreateVpcFlowLogs
・ LambdaExecutionS3AccessRole
・ new-resource-all-delete-role-nuiqf714
・ new-resource-delete-role-038dhko1
・ test
削除していいですか？[y/N]y
IAMロール DeliverLogsRole-CreateVpcFlowLogs 削除完了
IAMロール LambdaExecutionS3AccessRole 削除完了
IAMロール new-resource-all-delete-role-nuiqf714 削除完了
IAMロール new-resource-delete-role-038dhko1 削除完了
IAMロール test のインスタンスプロファイル test 削除完了
IAMロール test 削除完了
```