audit-labs/audit-tools
A collection of scripts, queries, and other goodies you can use in an audit.
clone: git clone https://gitbay.org/audit-labs/audit-tools.git
d4aa657efe35412df09d1231e76bae1d18bba4b2
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-07-29T04:48:57Z
applications/aws/collectors/iam.py | 57 +++++++++++++++++++------------------- applications/aws/collectors/s3.py | 5 +++- tui/platforms.py | 10 +++++-- 3 files changed, 39 insertions(+), 33 deletions(-) @@ -16,38 +16,37 @@ def iam_users(cfg): iam = cfg["session"].client("iam") now = datetime.now(timezone.utc) rows = [] - for page in iam.get_paginator("list_users").paginate(): - for u in page["Users"]: - name = u["UserName"] - mfa = iam.list_mfa_devices(UserName=name).get("MFADevices", []) - keys = iam.list_access_keys(UserName=name).get("AccessKeyMetadata", []) - key_ages = [(now - k["CreateDate"]).days for k in keys] + rows.extend(_user_row(iam, u, now) for u in page["Users"]) + return rows - try: - iam.get_login_profile(UserName=name) - console = True - except ClientError as e: - if e.response["Error"]["Code"] == "NoSuchEntity": - console = False - else: - raise - last_used = u.get("PasswordLastUsed") - rows.append( - { - "user": name, - "mfa_enabled": bool(mfa), - "access_keys": len(keys), - "oldest_key_age_days": max(key_ages) if key_ages else "", - "console_password": console, - "password_last_used": last_used.isoformat() if last_used else "", - "created": u["CreateDate"].isoformat() - if u.get("CreateDate") - else "", - } - ) - return rows +def _user_row(iam, user, now): + name = user["UserName"] + mfa = iam.list_mfa_devices(UserName=name).get("MFADevices", []) + keys = iam.list_access_keys(UserName=name).get("AccessKeyMetadata", []) + key_ages = [(now - k["CreateDate"]).days for k in keys] + last_used = user.get("PasswordLastUsed") + created = user.get("CreateDate") + return { + "user": name, + "mfa_enabled": bool(mfa), + "access_keys": len(keys), + "oldest_key_age_days": max(key_ages) if key_ages else "", + "console_password": _has_console_password(iam, name), + "password_last_used": last_used.isoformat() if last_used else "", + "created": created.isoformat() if created else "", + } + + +def _has_console_password(iam, name): + try: + iam.get_login_profile(UserName=name) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "NoSuchEntity": + return False + raise def password_policy(cfg): @@ -7,7 +7,10 @@ bucket policy public, and whether the ACL grants access to AllUsers. from botocore.exceptions import ClientError -ALL_USERS = "http://acs.amazonaws.com/groups/global/AllUsers" +# AWS's fixed identifier for the "all users" ACL grantee. It is an opaque URI +# used as a group ID in ACL grants, not a network endpoint this tool connects +# to, so the http scheme is expected. NOSONAR: not an insecure URL. +ALL_USERS = "http://acs.amazonaws.com/groups/global/AllUsers" # NOSONAR def s3_public_access(cfg): @@ -43,6 +43,10 @@ class Platform: note: str = field(default="") +# Shared connection fields reused across platforms. +_OUT_FIELD = Field("out", "Output directory", default="./output") + + def _prefill(f: Field) -> str: if f.env: value = os.environ.get(f.env, "").strip() @@ -110,7 +114,7 @@ GITHUB = Platform( required=True, env="GITHUB_TOKEN", ), - Field("out", "Output directory", default="./output"), + _OUT_FIELD, Field("branch", "Branch (for commit history)", default="main"), ], checks=github_runner.CHECKS, @@ -145,7 +149,7 @@ GITLAB = Platform( default="https://gitlab.com/api/v4", env="GITLAB_URL", ), - Field("out", "Output directory", default="./output"), + _OUT_FIELD, ], checks=gitlab_runner.CHECKS, default_selection=gitlab_runner.DEFAULT_SELECTION, @@ -171,7 +175,7 @@ AWS = Platform( "optional; defaults to current account", env="AWS_AUDIT_ACCOUNT", ), - Field("out", "Output directory", default="./output"), + _OUT_FIELD, ], checks=aws_runner.CHECKS, default_selection=aws_runner.DEFAULT_SELECTION,