はじめに
Boto3を使ってAWSコンソールのIAMユーザ一覧に表示されている情報取得をやっていく。
IAMユーザの情報がほしい時の参考に。
Boto3とは、PythonでAWSの各種サービスを簡単に扱えるようにするライブラリ。
今回はこちらのBoto3 IAMを参照します。
構成
構成はこちら。
API Gatewayを使ったのは、Lambdaを実行する際にアクセス制限をしたかったからです。
下図は、API GatewayのリソースポリシーでIPアドレス制限を入れた例です。
LambdaでBoto3を使います。
Boto3を使うためにLambdaの先頭でimportします。
import boto3
iam_client = boto3.client('iam')
また、Lambdaのアクセス権限で付与されているロールに「IAMReadOnlyAccess」を追加します。
それではやっていきましょう!
IAMユーザのすべて
AWSコンソールのIAMユーザ一覧です。
IAMユーザ一覧の右上の マークをクリックすると一覧の表示項目がわかります。
それぞれの項目の値を取得していきます。
ユーザ名
- 「ユーザ名」はBoto3のメソッド「list_users」で取得した「Users」の「UserName」
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["ユーザ名"] = user["UserName"]
パス
- 「パス」はBoto3のメソッド「list_users」で取得した「Users」の「Path」
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["パス"] = user["Path"]
グループ
- 「グループ」はBoto3のメソッド「list_groups_for_user」で取得した「Groups」
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- IAMユーザ一覧ではIMAユーザが所属するグループの数が表示されている(lenで取得)
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["グループ"] = len(iam_client.list_groups_for_user(UserName=user["UserName"])["Groups"])
- 実際に所属するグループ名を取得したければ、グループのリストにappend
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
jsonObj["グループ"] = []
for user in response["Users"]:
groups = iam_client.list_groups_for_user(UserName=user["UserName"])["Groups"]
for group in groups:
jsonObj["グループ"].append(group["GroupName"])
最後のアクティビティ
- 「最後のアクティビティ」は「コンソールを通じたアクセス」と「アクティブなアクセスキーによるプログラムによるアクセス」の日付の若い方
(IAMユーザ一覧の「最後のアクティビティ」の項目をクリックすると以下の表示)
- 「コンソールを通じたアクセス」はBoto3のメソッド「list_users」で取得した「PasswordLastUsed」
- IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
※日付の差分はtimezoneを合わせないとエラーになるので注意。 - あとで「アクティブなアクセスキーによるプログラムによるアクセス」の日付と比較するためリストにappend
# プログラムは必要な個所のみ記載
from datetime import datetime, timezone
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["最後のアクティビティ"] = []
if user.get("PasswordLastUsed"):
jsonObj["最後のアクティビティ"].append(str((datetime.now(timezone.utc) - user["PasswordLastUsed"]).days) + "日前")
- 「アクティブなアクセスキーによるプログラムによるアクセス」はBoto3のメソッド「list_access_keys」で取得した「AccessKeyMetadata」のうち、Activeなキーが最後に使用された日付(LastUsedDate)
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- アクセスキーは1 IAMユーザにつき最大2つ生成できるためfor文で取得
- IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
※日付の差分はtimezoneを合わせないとエラーになるので注意。 - あとで「コンソールを通じたアクセス」の日付と比較するためリストにappend
# プログラムは必要な個所のみ記載
from datetime import datetime, timezone
response = iam_client.list_users()
for user in response["Users"]:
accesskeymetadatas = iam_client.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
for accesskeymetadata in accesskeymetadatas:
if accesskeymetadata["Status"] == "Active":
if accesskeylastused.get("LastUsedDate"):
jsonObj["最後のアクティビティ"].append(str((datetime.now(timezone.utc) - accesskeylastused["LastUsedDate"]).days) + "日前")
- ここで「コンソールを通じたアクセス」と「アクティブなアクセスキーによるプログラムによるアクセス」の日付の若い方を取得(minで取得)
- そもそも「最後のアクティビティ」がない場合は「-」を表示
# プログラムは必要な個所のみ記載
if len(jsonObj["最後のアクティビティ"]) != 0:
jsonObj["最後のアクティビティ"] = min(jsonObj["最後のアクティビティ"])
else:
jsonObj["最後のアクティビティ"] = "-"
MFA
- 「MFA」はBoto3のメソッド「list_mfa_devices」で取得した「MFADevices」
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- MFAデバイスも複数設定可能のためfor文で取得
- IAMユーザ一覧では「仮想(x)」表示であるが、詳細を取得したければリストにappend
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["MFA"] = []
mfadevices = iam_client.list_mfa_devices(UserName=user["UserName"])["MFADevices"]
for mfadevice in mfadevices:
jsonObj["MFA"].append(mfadevice["SerialNumber"])
パスワードが作成されてから経過した期間
- 「パスワードが作成されてから経過した期間」はBoto3のメソッド「get_login_profile」で取得した「LoginProfile」の「CreateDate」 もし値がなければ「Users」で取得した「CreateDate」
つまり、LoginProfileが生成された日か、そうでなければIAMユーザが作成された日 - LoginProfileはAWSコンソールログインを有効にした場合に生成
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
※日付の差分はtimezoneを合わせないとエラーになるので注意。 - 実データと比較すると一致しないケースがあるため別ロジックかもしれません。
正確にはIAMユーザの認証情報レポートのpasswrd_last_changedから算出?
# プログラムは必要な個所のみ記載
from datetime import datetime, timezone
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["パスワードが作成されてから経過した期間"] = "-"
loginprofile = iam_client.get_login_profile(UserName=user["UserName"])['LoginProfile']
if loginprofile.get("CreateDate"):
jsonObj["パスワードが作成されてから経過した期間"] = str((datetime.now(timezone.utc) - loginprofile.get("CreateDate")).days) + "日前"
else:
jsonObj["パスワードが作成されてから経過した期間"] = str((datetime.now(timezone.utc) - user.get("CreateDate")).days) + "日前"
コンソールの最終サインイン
- 「コンソールの最終サインイン」はBoto3のメソッド「list_users」で取得した「Users」の「PasswordLastUsed」
- 日本時間で表示するためtimedeltaで+9時間
# プログラムは必要な個所のみ記載
from datetime import timedelta
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["コンソールの最終サインイン"] = "-"
if user.get("PasswordLastUsed"):
jsonObj["コンソールの最終サインイン"] = getTimeFromUser(user, "PasswordLastUsed")
def getTimeFromUser(user, key):
try:
return str((user[key] + timedelta(hours=9)).strftime('%Y/%m/%d %H:%M:%S'))
except (KeyError):
return "None"
アクセスキーID、アクティブなキーが作成されてから経過した期間、最後に使用したアクセスキー
- 「アクセスキーID」はBoto3のメソッド「list_access_keys」で取得した「AccessKeyMetadata」の「AccessKeyId」
- 「アクティブなキーが作成されてから経過した期間」はアクセスキーIDのStatusがActiveなもので「CreateDate」の日付
- 「最後に使用したアクセスキー」はBoto3メソッド「get_access_key_last_used」で取得した「AccessKeyLastUsed」の「LastUsedDate」
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
※日付の差分はtimezoneを合わせないとエラーになるので注意。 - アクセスキーは1 IAMユーザにつき最大2つ生成できるためfor文で取得
# プログラムは必要な個所のみ記載
from datetime import datetime, timezone
response = iam_client.list_users()
for user in response["Users"]:
accesskeymetadatas = iam_client.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
for accesskeymetadata in accesskeymetadatas:
jsonObj["アクセスキーID"].append(accesskeymetadata["Status"] + " - " + accesskeymetadata["AccessKeyId"])
if accesskeymetadata["Status"] == "Active":
jsonObj["アクティブなキーが作成されてから経過した期間"].append(str((datetime.now(timezone.utc) - accesskeymetadata["CreateDate"]).days) + "日前")
accesskeylastused = iam_client.get_access_key_last_used(AccessKeyId=accesskeymetadata["AccessKeyId"])["AccessKeyLastUsed"]
if accesskeylastused.get("LastUsedDate"):
jsonObj["最後に使用したアクセスキー"].append(str((datetime.now(timezone.utc) - accesskeylastused["LastUsedDate"]).days) + "日前")
ARN
- 「ARN」はBoto3のメソッド「list_users」で取得した「Users」の「Arn」
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["Arn"] = user["Arn"]
作成時刻
- 「作成時刻」はBoto3のメソッド「list_users」で取得した「Users」の「CreateDate」
- IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
※日付の差分はtimezoneを合わせないとエラーになるので注意。
# プログラムは必要な個所のみ記載
from datetime import datetime, timezone
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["作成時刻"] = str((datetime.now(timezone.utc) - user["CreateDate"]).days) + "日前"
コンソールを通じたアクセス
- 「コンソールを通じたアクセス」はBoto3のメソッド「get_login_profile」で取得した「LoginProfile」の「CreateDate」の有無
- IAMユーザ一覧では「有効」の表示のため、「CreateDate」があれば「有効」、それ以外は「無効」
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jsonObj["コンソールを通じたアクセス"] = "-"
loginprofile = iam_client.get_login_profile(UserName=user["UserName"])['LoginProfile']
if loginprofile.get("CreateDate"):
jsonObj["コンソールを通じたアクセス"] = "有効"
else:
jsonObj["コンソールを通じたアクセス"] = "無効"
署名証明書
- 「署名証明書」はBoto3のメソッド「list_signing_certificates」で取得した「Certificates」
- UserNameに「list_users」で取得した「Users」の「UserName」を指定
- 複数の証明書が考えられるためリストにappend
# プログラムは必要な個所のみ記載
response = iam_client.list_users()
for user in response["Users"]:
jjsonObj["署名証明書"] = []
listsigningcertificates = iam_client.list_signing_certificates(UserName=user["UserName"])["Certificates"]
for listsigningcertificate in listsigningcertificates:
jsonObj["署名証明書"].append(listsigningcertificate["Status"])
仕上げとして
Lambdaで取得したIAMユーザの情報をjsonからhtmlに変換する。
htmlに変換するためpandasを利用。
pandasを利用するためのLambdaの設定方法はこちらが参考になります。
jsonデータをhtmlに変換して返す。
# プログラムは必要な個所のみ記載
df = pd.DataFrame(jsonObj)
html_table = df.to_html(index=False, justify='center')
with open('/tmp/table.html', 'w', encoding='utf-8') as file:
file.write(html_table)
print(html_table)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'text/html; charset=UTF-8'
},
'body': html_table
}
ブラウザで確認するとこんな感じ。(マスクしてるためほとんどわからない)
LambdaのPythonコード全文です。
import json
import boto3
from datetime import datetime, timedelta, timezone
import pandas as pd
iam_client = boto3.client('iam')
def lambda_handler(event, context):
response = iam_client.list_users()
jsonObj = [""] * len(response["Users"])
i = 0
for user in response["Users"]:
jsonObj[i] = {}
jsonObj[i]["ユーザ名"] = user["UserName"]
jsonObj[i]["パス"] = user["Path"]
# jsonObj[i]["グループ"] = len(iam_client.list_groups_for_user(UserName=user["UserName"])["Groups"])
jsonObj[i]["Groups"] = []
groups = iam_client.list_groups_for_user(UserName=user["UserName"])["Groups"]
for group in groups:
jsonObj[i]["Groups"].append(group["GroupName"])
# 最後のアクティビティは「コンソールログイン」と「最後に使用したアクセスキー」の日付の若い方
jsonObj[i]["最後のアクティビティ"] = []
if user.get("PasswordLastUsed"):
jsonObj[i]["最後のアクティビティ"].append(str((datetime.now(timezone.utc) - user["PasswordLastUsed"]).days) + "日前")
jsonObj[i]["MFA"] = []
mfadevices = iam_client.list_mfa_devices(UserName=user["UserName"])["MFADevices"]
for mfadevice in mfadevices:
jsonObj[i]["MFA"].append(mfadevice["SerialNumber"])
# 「パスワードが作成されてから経過した期間」は、「get_userのPasswordLastUsed」もし値がなければ、「get_userのCreateDate」
# 正確にはcredential_reportのpasswrd_last_changedから算出すると思われる。
loginprofile = iam_client.get_login_profile(UserName=user["UserName"])['LoginProfile']
jsonObj[i]["パスワードが作成されてから経過した期間"] = "-"
if loginprofile.get("CreateDate"):
jsonObj[i]["パスワードが作成されてから経過した期間"] = str((datetime.now(timezone.utc) - loginprofile.get("CreateDate")).days) + "日前"
else:
jsonObj[i]["パスワードが作成されてから経過した期間"] = str((datetime.now(timezone.utc) - user.get("CreateDate")).days) + "日前"
jsonObj[i]["コンソールの最終サインイン"] = "-"
if user.get("PasswordLastUsed"):
jsonObj[i]["コンソールの最終サインイン"] = getTimeFromUser(user, "PasswordLastUsed")
jsonObj[i]["アクセスキーID"] = []
jsonObj[i]["アクティブなキーが作成されてから経過した期間"] = []
jsonObj[i]["最後に使用したアクセスキー"] = []
accesskeymetadatas = iam_client.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
for accesskeymetadata in accesskeymetadatas:
jsonObj[i]["アクセスキーID"].append(accesskeymetadata["Status"] + " - " + accesskeymetadata["AccessKeyId"])
if accesskeymetadata["Status"] == "Active":
jsonObj[i]["アクティブなキーが作成されてから経過した期間"].append(str((datetime.now(timezone.utc) - accesskeymetadata["CreateDate"]).days) + "日前")
accesskeylastused = iam_client.get_access_key_last_used(AccessKeyId=accesskeymetadata["AccessKeyId"])["AccessKeyLastUsed"]
if accesskeylastused.get("LastUsedDate"):
jsonObj[i]["最後に使用したアクセスキー"].append(str((datetime.now(timezone.utc) - accesskeylastused["LastUsedDate"]).days) + "日前")
jsonObj[i]["最後のアクティビティ"].append(str((datetime.now(timezone.utc) - accesskeylastused["LastUsedDate"]).days) + "日前")
if len(jsonObj[i]["最後のアクティビティ"]) != 0:
jsonObj[i]["最後のアクティビティ"] = min(jsonObj[i]["最後のアクティビティ"])
else:
jsonObj[i]["最後のアクティビティ"] = "-"
jsonObj[i]["Arn"] = user["Arn"]
jsonObj[i]["作成時刻"] = str((datetime.now(timezone.utc) - user["CreateDate"]).days) + "日前"
# 「コンソールを通じたアクセス」はget_login_profileがcreateされていれば有効
jsonObj[i]["コンソールを通じたアクセス"] = "-"
if loginprofile.get("CreateDate"):
jsonObj[i]["コンソールを通じたアクセス"] = "有効"
else:
jsonObj[i]["コンソールを通じたアクセス"] = "無効"
jsonObj[i]["署名証明書"] = []
listsigningcertificates = iam_client.list_signing_certificates(UserName=user["UserName"])["Certificates"]
for listsigningcertificate in listsigningcertificates:
jsonObj[i]["署名証明書"].append(listsigningcertificate["Status"])
i = i + 1
scripts = """
<style type="text/css">
.design10 {
width: 100%;
text-align: center;
border-collapse: collapse;
border-spacing: 0;
}
.design10 th {
padding: 10px;
border-bottom: solid 4px #778ca3;
color: #778ca3
}
.design10 td {
padding: 10px;
border-bottom: solid 1px #778ca3;
}
</style>
"""
df = pd.DataFrame(jsonObj)
html_table = scripts + df.to_html(index=False, justify='center', classes='design10')
with open('/tmp/table.html', 'w', encoding='utf-8') as file:
file.write(html_table)
print(html_table)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'text/html; charset=UTF-8'
},
'body': html_table
}
def getTimeFromUser(user, key):
try:
return str((user[key] + timedelta(hours=9)).strftime('%Y/%m/%d %H:%M:%S'))
except (KeyError):
return "None"
さいごに
- 今回はBoto3を使ってIAMの情報取得をしましたが、IAMの追加、更新、削除もできます。使い方はBoto3のマニュアルとにらめっこ。
- 他のサービスについてもやっていく予定です。
- 少しでもみなさんの参考になりますように!