LoginSignup
0
0

【AWS】Boto3で攻略! IAMユーザのすべて

Last updated at Posted at 2024-06-04

はじめに

Boto3を使ってAWSコンソールのIAMユーザ一覧に表示されている情報取得をやっていく。
IAMユーザの情報がほしい時の参考に。

Boto3とは、PythonでAWSの各種サービスを簡単に扱えるようにするライブラリ。
今回はこちら:point_down:のBoto3 IAMを参照します。

構成

構成はこちら。

image.png

API Gatewayを使ったのは、Lambdaを実行する際にアクセス制限をしたかったからです。
下図は、API GatewayのリソースポリシーでIPアドレス制限を入れた例です。

image.png

LambdaでBoto3を使います。
Boto3を使うためにLambdaの先頭でimportします。

lambda_function.py
import boto3

iam_client = boto3.client('iam')

また、Lambdaのアクセス権限で付与されているロールに「IAMReadOnlyAccess」を追加します。

image.png

それではやっていきましょう!

IAMユーザのすべて

AWSコンソールのIAMユーザ一覧です。

image.png

IAMユーザ一覧の右上の :gear: マークをクリックすると一覧の表示項目がわかります。

image.png

それぞれの項目の値を取得していきます。

:o: ユーザ名

  • 「ユーザ名」はBoto3のメソッド「list_users」で取得した「Users」の「UserName」
lambda_function.py
    # プログラムは必要な個所のみ記載
    response = iam_client.list_users()
    for user in response["Users"]:
        jsonObj["ユーザ名"] = user["UserName"]

:o: パス

  • 「パス」はBoto3のメソッド「list_users」で取得した「Users」の「Path」
lambda_function.py
    # プログラムは必要な個所のみ記載
    response = iam_client.list_users()
    for user in response["Users"]:
        jsonObj["パス"] = user["Path"]

:o: グループ

  • 「グループ」はBoto3のメソッド「list_groups_for_user」で取得した「Groups」
  • UserNameに「list_users」で取得した「Users」の「UserName」を指定
  • IAMユーザ一覧ではIMAユーザが所属するグループの数が表示されている(lenで取得)
lambda_function.py
    # プログラムは必要な個所のみ記載
    response = iam_client.list_users()
    for user in response["Users"]:
        jsonObj["グループ"] = len(iam_client.list_groups_for_user(UserName=user["UserName"])["Groups"])
  • 実際に所属するグループ名を取得したければ、グループのリストにappend
lambda_function.py
    # プログラムは必要な個所のみ記載
    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"])

:o: 最後のアクティビティ

  • 「最後のアクティビティ」は「コンソールを通じたアクセス」と「アクティブなアクセスキーによるプログラムによるアクセス」の日付の若い方

(IAMユーザ一覧の「最後のアクティビティ」の項目をクリックすると以下の表示)

image.png

  • 「コンソールを通じたアクセス」はBoto3のメソッド「list_users」で取得した「PasswordLastUsed」
  • IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
    ※日付の差分はtimezoneを合わせないとエラーになるので注意。
  • あとで「アクティブなアクセスキーによるプログラムによるアクセス」の日付と比較するためリストにappend
lambda_function.py
    # プログラムは必要な個所のみ記載
    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
lambda_function.py
    # プログラムは必要な個所のみ記載
    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で取得)
  • そもそも「最後のアクティビティ」がない場合は「-」を表示
lambda_function.py
    # プログラムは必要な個所のみ記載
    if len(jsonObj["最後のアクティビティ"]) != 0:
        jsonObj["最後のアクティビティ"] = min(jsonObj["最後のアクティビティ"])
    else:
        jsonObj["最後のアクティビティ"] = "-"

:o: MFA

  • 「MFA」はBoto3のメソッド「list_mfa_devices」で取得した「MFADevices」
  • UserNameに「list_users」で取得した「Users」の「UserName」を指定
  • MFAデバイスも複数設定可能のためfor文で取得
  • IAMユーザ一覧では「仮想(x)」表示であるが、詳細を取得したければリストにappend
lambda_function.py
    # プログラムは必要な個所のみ記載
    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"])

:o: パスワードが作成されてから経過した期間

  • 「パスワードが作成されてから経過した期間」はBoto3のメソッド「get_login_profile」で取得した「LoginProfile」の「CreateDate」 もし値がなければ「Users」で取得した「CreateDate」
    つまり、LoginProfileが生成された日か、そうでなければIAMユーザが作成された日
  • LoginProfileはAWSコンソールログインを有効にした場合に生成
  • UserNameに「list_users」で取得した「Users」の「UserName」を指定
  • IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
    ※日付の差分はtimezoneを合わせないとエラーになるので注意。
  • 実データと比較すると一致しないケースがあるため別ロジックかもしれません。
    正確にはIAMユーザの認証情報レポートのpasswrd_last_changedから算出?
lambda_function.py
    # プログラムは必要な個所のみ記載
    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) + "日前"

:o: コンソールの最終サインイン

  • 「コンソールの最終サインイン」はBoto3のメソッド「list_users」で取得した「Users」の「PasswordLastUsed」
  • 日本時間で表示するためtimedeltaで+9時間
lambda_function.py
    # プログラムは必要な個所のみ記載
    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"

:o: アクセスキー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文で取得
lambda_function.py
    # プログラムは必要な個所のみ記載
    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) + "日前")

:o: ARN

  • 「ARN」はBoto3のメソッド「list_users」で取得した「Users」の「Arn」
lambda_function.py
    # プログラムは必要な個所のみ記載
    response = iam_client.list_users()
    for user in response["Users"]:
        jsonObj["Arn"] = user["Arn"]

:o: 作成時刻

  • 「作成時刻」はBoto3のメソッド「list_users」で取得した「Users」の「CreateDate」
  • IAMユーザ一覧では「x日前」表示のため、現在からの差分で取得
    ※日付の差分はtimezoneを合わせないとエラーになるので注意。
lambda_function.py
    # プログラムは必要な個所のみ記載
    from datetime import datetime, timezone

    response = iam_client.list_users()
    for user in response["Users"]:
        jsonObj["作成時刻"] = str((datetime.now(timezone.utc) - user["CreateDate"]).days) + "日前"

:o: コンソールを通じたアクセス

  • 「コンソールを通じたアクセス」はBoto3のメソッド「get_login_profile」で取得した「LoginProfile」の「CreateDate」の有無
  • IAMユーザ一覧では「有効」の表示のため、「CreateDate」があれば「有効」、それ以外は「無効」
  • UserNameに「list_users」で取得した「Users」の「UserName」を指定
lambda_function.py
    # プログラムは必要な個所のみ記載
    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["コンソールを通じたアクセス"] = "無効"

:o: 署名証明書

  • 「署名証明書」はBoto3のメソッド「list_signing_certificates」で取得した「Certificates」
  • UserNameに「list_users」で取得した「Users」の「UserName」を指定
  • 複数の証明書が考えられるためリストにappend
lambda_function.py
    # プログラムは必要な個所のみ記載
    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の設定方法はこちら:point_down:が参考になります。

jsonデータをhtmlに変換して返す。

lambda_function.py
    # プログラムは必要な個所のみ記載
    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
    }

ブラウザで確認するとこんな感じ。(マスクしてるためほとんどわからない:sweat_smile:

image.png

 LambdaのPythonコード全文です。
lambda_function.py
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のマニュアルとにらめっこ。
  • 他のサービスについてもやっていく予定です。
  • 少しでもみなさんの参考になりますように!
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0