背景
AWS認定資格の学習中に、「未使用期間が90日以上経過したIAMユーザーのユーザーアクセスキーを無効する作業を自動化するには?」という問題がありました。
AWSの設定のみで実現できないルーチンワーク的なセキュリティ処理をLambda関数などで作成するという解答はAWS認定試験の問題集などに散見されるのですが、具体的にどうやるのかの情報があまりヒットしないので、自分で作って確認してみることにしました。
目次
選択肢
- 管理コンソールのIAMサービスからIAMユーザーの利用状況をダウンロードし、90日以上経過したユーザーをピックアップし、無効化する
手動操作、および目視確認のため、自動化には程遠い
→却下 - AWS CLIで以下のコマンドを実行する
$ aws iam generate-credential-report
$ aws iam get-credential-report
$ aws iam update-access-key
取得した認証情報はbase64でエンコードされたテキストファイルで取得できるが、そのファイルをテキスト抽出し、更にCLIコマンドを実施するのは結構労力を要する。
そもそもアクセスキー、シークレットキーの管理が必要でセキュリティ的に不向きに思われる。
→却下
3. AWS SDKでのGenerateCredentialReportでレポート作成後、GetCredentialReportでダウンロードし、UpdateAccesskeyを使用して無効化する
→こちらが正解
実現方法
実装環境
- AWS CloudShell上にpython3.8をインストールする
- boto3などライブラリをインストール
- 実装する
実装したコード
※参照する列が間違ってたので修正しました。
CSVで出力される列の「access_key_1_last_rotated」で取得される日付が90日以上経過している場合にアクセスキーを無効にする処理を実施しています。
GenerateCredentialReport/GetCredentialReport/UpdateAccesskeyの利用例の一例ですが、ご使用は自己責任でお願いします。
import sys
import time
import boto3
from datetime import timedelta
from datetime import datetime as dt
from botocore.exceptions import ClientError
iam = boto3.resource('iam')
def generate_credential_report():
try:
response = iam.meta.client.generate_credential_report()
except ClientError:
print("Couldn't generate a credentials report for your account.")
raise
else:
return response
def get_credential_report():
try:
response = iam.meta.client.get_credential_report()
print(response['Content'])
except ClientError:
print("Couldn't get credentials report.")
raise
else:
return response['Content']
def suspend_user(username):
update_res = []
check_access_keys = iam.meta.client.list_access_keys(UserName=username)
for key_lists in check_access_keys['AccessKeyMetadata']:
print()
res = iam.meta.client.update_access_key(UserName=username, AccessKeyId=key_lists['AccessKeyId'], Status='Inactive')
update_res.append(res)
return update_res
def inactive_unused_access_key():
print("Let's generate a credentials report...")
report_state = None
while report_state != 'COMPLETE':
cred_report_response = generate_credential_report()
old_report_state = report_state
report_state = cred_report_response['State']
if report_state != old_report_state:
print(report_state, sep='')
else:
print('.', sep='')
sys.stdout.flush()
time.sleep(1)
USER = 0
ACCESS_KEY_1_LAST_ROTATED = 9
cred_report = get_credential_report()
col_count = ACCESS_KEY_1_LAST_ROTATED + 1
print(f"Got credentials report. Showing only the first {col_count} columns.")
cred_lines = [line.split(',')[:col_count] for line in cred_report.decode('utf-8').split('\n')]
col_width = max([len(item) for line in cred_lines for item in line]) + 2
today = dt.today()
for line in cred_report.decode('utf-8').split('\n'):
if line.split(',')[ACCESS_KEY_1_LAST_ROTATED] == "access_key_1_last_rotated" or line.split(',')[ACCESS_KEY_1_LAST_ROTATED] == "N/A" :
continue
else:
date = line.split(',')[ACCESS_KEY_1_LAST_ROTATED]
lastloginday = dt.strptime(date[0:9], '%Y-%m-%d')
if lastloginday < (today - timedelta(days=90)):
print(line.split(',')[USER] + "is not used. Update access key to inactive. ")
suspend_user(line.split(',')[USER])
if __name__ == '__main__':
inactive_unused_access_key()