#!/usr/bin/env bash

#########################################################################################
#### iam-audit.sh — Audit AWS IAM users, roles, policies, and access keys            ####
#### Finds stale access keys, users without MFA, unused roles, overly broad policies ####
#### Requires: bash 4+, aws-cli v2, jq                                              ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.01                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   export AWS_PROFILE="production"                                               ####
####   ./iam-audit.sh --full                                                         ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
AWS_REGION="${AWS_REGION:-}"
KEY_AGE_WARN_DAYS="${KEY_AGE_WARN_DAYS:-90}"
KEY_AGE_CRIT_DAYS="${KEY_AGE_CRIT_DAYS:-180}"
UNUSED_DAYS="${UNUSED_DAYS:-90}"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
RUN_MODE=""
START_TIME=""
WARNINGS=0
TOTAL_PASS=0
TOTAL_WARN=0
TOTAL_CRIT=0

# ── Colors ────────────────────────────────────────────────────────────
setup_colors() {
    if [[ "$COLOR" == "never" ]]; then
        RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
        return
    fi
    if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        BOLD='\033[1m'
        RESET='\033[0m'
    else
        RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
    fi
}

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; ((WARNINGS++)) || true; }
err()     { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; }

pass()    { ((TOTAL_PASS++)) || true; }
flag_warn() { ((TOTAL_WARN++)) || true; }
flag_crit() { ((TOTAL_CRIT++)) || true; }

# ── AWS CLI wrapper ───────────────────────────────────────────────────
aws_cmd() {
    local args=("$@")
    [[ -n "$AWS_REGION" ]] && args+=(--region "$AWS_REGION")
    verbose "aws ${args[*]}"
    aws "${args[@]}"
}

# ── Date math (portable) ─────────────────────────────────────────────
now_epoch() {
    date +%s
}

iso_to_epoch() {
    local iso_date="$1"
    if date -d "$iso_date" +%s &>/dev/null; then
        date -d "$iso_date" +%s
    elif date -jf "%Y-%m-%dT%H:%M:%S" "${iso_date%%+*}" +%s &>/dev/null; then
        date -jf "%Y-%m-%dT%H:%M:%S" "${iso_date%%+*}" +%s
    else
        # Fallback: parse YYYY-MM-DD with awk
        echo "$iso_date" | awk -F'[-T:+]' '{
            y=$1; m=$2; d=$3
            t = mktime(y " " m " " d " 0 0 0")
            print t
        }'
    fi
}

days_since() {
    local iso_date="$1"
    local then_epoch now_epoch_val
    then_epoch=$(iso_to_epoch "$iso_date")
    now_epoch_val=$(now_epoch)
    echo $(( (now_epoch_val - then_epoch) / 86400 ))
}

# ── Credential check ─────────────────────────────────────────────────
check_deps() {
    for cmd in aws jq; do
        if ! command -v "$cmd" &>/dev/null; then
            err "${cmd} is required but not installed"
            exit 1
        fi
    done

    local identity
    identity=$(aws sts get-caller-identity 2>&1) || {
        err "AWS credentials not configured, expired, or invalid"
        echo "" >&2
        echo "Supported credential methods:" >&2
        echo "  • AWS_PROFILE          — named profile from ~/.aws/credentials" >&2
        echo "  • AWS SSO              — run 'aws sso login --profile your-profile'" >&2
        echo "  • Environment vars     — AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_SESSION_TOKEN" >&2
        echo "  • Instance profile     — automatic on EC2/ECS" >&2
        echo "  • AWS_ROLE_ARN         — assume role via STS" >&2
        exit 1
    }

    local arn
    arn=$(echo "$identity" | jq -r '.Arn')
    verbose "Identity: ${arn}"

    if [[ -n "${AWS_SESSION_TOKEN:-}" ]]; then
        verbose "Using temporary credentials (session token present)"
    fi

    if [[ -z "$AWS_REGION" ]]; then
        AWS_REGION=$(aws configure get region 2>/dev/null || echo "")
        if [[ -n "$AWS_REGION" ]]; then
            verbose "Using region from config: ${AWS_REGION}"
        fi
    fi

    log "Authenticated as ${arn}"
}

# ══════════════════════════════════════════════════════════════════════
#                       ACCESS KEY AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_access_keys() {
    log "Auditing access keys..."
    echo ""

    printf "  %-24s %-22s %-10s %-10s %-20s %s\n" \
        "USER" "KEY_ID" "STATUS" "AGE_DAYS" "LAST_USED" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..100})"

    local users_json
    users_json=$(aws_cmd iam list-users --output json)

    local user_count
    user_count=$(echo "$users_json" | jq '.Users | length')

    if [[ "$user_count" -eq 0 ]]; then
        log "No IAM users found"
        return
    fi

    local user_name
    echo "$users_json" | jq -r '.Users[].UserName' | while IFS= read -r user_name; do
        local keys_json
        keys_json=$(aws_cmd iam list-access-keys --user-name "$user_name" --output json 2>/dev/null) || continue

        local key_count
        key_count=$(echo "$keys_json" | jq '.AccessKeyMetadata | length')

        if [[ "$key_count" -eq 0 ]]; then
            verbose "User ${user_name}: no access keys"
            continue
        fi

        echo "$keys_json" | jq -c '.AccessKeyMetadata[]' | while IFS= read -r key; do
            local key_id status create_date age_days last_used_date severity last_used_display
            key_id=$(echo "$key" | jq -r '.AccessKeyId')
            status=$(echo "$key" | jq -r '.Status')
            create_date=$(echo "$key" | jq -r '.CreateDate')
            age_days=$(days_since "$create_date")

            # Get last used info
            local last_used_json
            last_used_json=$(aws_cmd iam get-access-key-last-used --access-key-id "$key_id" --output json 2>/dev/null) || true

            last_used_date=$(echo "$last_used_json" | jq -r '.AccessKeyLastUsed.LastUsedDate // "N/A"')
            if [[ "$last_used_date" == "N/A" ]]; then
                last_used_display="Never"
            else
                last_used_display="${last_used_date:0:10}"
            fi

            # Determine severity
            if [[ "$status" == "Inactive" ]]; then
                severity="INFO"
            elif [[ "$age_days" -ge "$KEY_AGE_CRIT_DAYS" ]]; then
                severity="CRITICAL"
                flag_crit
            elif [[ "$age_days" -ge "$KEY_AGE_WARN_DAYS" ]]; then
                severity="WARN"
                flag_warn
            elif [[ "$last_used_display" == "Never" && "$age_days" -ge 30 ]]; then
                severity="WARN"
                flag_warn
            else
                severity="OK"
                pass
            fi

            local color=""
            case "$severity" in
                CRITICAL) color="$RED" ;;
                WARN)     color="$YELLOW" ;;
                OK)       color="$GREEN" ;;
                *)        color="" ;;
            esac

            printf "  %-24s %-22s %-10s %-10s %-20s %b%s%b\n" \
                "$user_name" "$key_id" "$status" "$age_days" "$last_used_display" \
                "$color" "$severity" "$RESET"
        done
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       MFA AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_mfa() {
    log "Auditing MFA status..."
    echo ""

    printf "  %-24s %-18s %-14s %s\n" \
        "USER" "CONSOLE_ACCESS" "MFA_ENABLED" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..75})"

    local users_json
    users_json=$(aws_cmd iam list-users --output json)

    echo "$users_json" | jq -r '.Users[].UserName' | while IFS= read -r user_name; do
        local has_console="No"
        local has_mfa="No"
        local severity="OK"

        # Check for login profile (console access)
        if aws_cmd iam get-login-profile --user-name "$user_name" &>/dev/null; then
            has_console="Yes"
        fi

        # Check MFA devices
        local mfa_json
        mfa_json=$(aws_cmd iam list-mfa-devices --user-name "$user_name" --output json 2>/dev/null) || true
        local mfa_count
        mfa_count=$(echo "$mfa_json" | jq '.MFADevices | length')

        if [[ "$mfa_count" -gt 0 ]]; then
            has_mfa="Yes"
        fi

        # Severity: console access without MFA is critical
        if [[ "$has_console" == "Yes" && "$has_mfa" == "No" ]]; then
            severity="CRITICAL"
            flag_crit
        elif [[ "$has_console" == "Yes" && "$has_mfa" == "Yes" ]]; then
            severity="OK"
            pass
        else
            severity="OK"
            pass
        fi

        local color=""
        case "$severity" in
            CRITICAL) color="$RED" ;;
            WARN)     color="$YELLOW" ;;
            OK)       color="$GREEN" ;;
            *)        color="" ;;
        esac

        printf "  %-24s %-18s %-14s %b%s%b\n" \
            "$user_name" "$has_console" "$has_mfa" \
            "$color" "$severity" "$RESET"
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       UNUSED USERS AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_unused_users() {
    log "Auditing user activity (inactive > ${UNUSED_DAYS} days)..."
    echo ""

    printf "  %-24s %-16s %-16s %s\n" \
        "USER" "LAST_CONSOLE" "LAST_API" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..75})"

    local users_json
    users_json=$(aws_cmd iam list-users --output json)

    echo "$users_json" | jq -c '.Users[]' | while IFS= read -r user; do
        local user_name password_last_used last_console last_api severity
        user_name=$(echo "$user" | jq -r '.UserName')
        password_last_used=$(echo "$user" | jq -r '.PasswordLastUsed // "N/A"')

        if [[ "$password_last_used" == "N/A" ]]; then
            last_console="Never"
        else
            last_console="${password_last_used:0:10}"
        fi

        # Check access key last used
        last_api="Never"
        local keys_json
        keys_json=$(aws_cmd iam list-access-keys --user-name "$user_name" --output json 2>/dev/null) || true

        echo "$keys_json" | jq -r '.AccessKeyMetadata[].AccessKeyId' 2>/dev/null | while IFS= read -r key_id; do
            local lu
            lu=$(aws_cmd iam get-access-key-last-used --access-key-id "$key_id" --output json 2>/dev/null | \
                jq -r '.AccessKeyLastUsed.LastUsedDate // "N/A"') || true
            if [[ "$lu" != "N/A" ]]; then
                echo "${lu:0:10}"
            fi
        done | sort -r | head -1 | read -r latest_api || true

        if [[ -n "${latest_api:-}" ]]; then
            last_api="$latest_api"
        fi

        # Determine severity
        severity="OK"
        local console_inactive=false api_inactive=false

        if [[ "$last_console" == "Never" ]]; then
            console_inactive=true
        else
            local console_days
            console_days=$(days_since "$password_last_used")
            if [[ "$console_days" -ge "$UNUSED_DAYS" ]]; then
                console_inactive=true
            fi
        fi

        if [[ "$last_api" == "Never" ]]; then
            api_inactive=true
        else
            local api_days
            api_days=$(days_since "${last_api}T00:00:00Z")
            if [[ "$api_days" -ge "$UNUSED_DAYS" ]]; then
                api_inactive=true
            fi
        fi

        if [[ "$console_inactive" == "true" && "$api_inactive" == "true" ]]; then
            severity="WARN"
            flag_warn
        else
            pass
        fi

        local color=""
        case "$severity" in
            WARN) color="$YELLOW" ;;
            OK)   color="$GREEN" ;;
            *)    color="" ;;
        esac

        printf "  %-24s %-16s %-16s %b%s%b\n" \
            "$user_name" "$last_console" "$last_api" \
            "$color" "$severity" "$RESET"
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       UNUSED ROLES AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_unused_roles() {
    log "Auditing role usage (inactive > ${UNUSED_DAYS} days)..."
    echo ""

    printf "  %-40s %-16s %-10s %s\n" \
        "ROLE" "LAST_USED" "AGE_DAYS" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..80})"

    local roles_json
    roles_json=$(aws_cmd iam list-roles --output json)

    echo "$roles_json" | jq -c '.Roles[]' | while IFS= read -r role; do
        local role_name role_path last_used create_date age_days severity

        role_name=$(echo "$role" | jq -r '.RoleName')
        role_path=$(echo "$role" | jq -r '.Path')
        create_date=$(echo "$role" | jq -r '.CreateDate')
        age_days=$(days_since "$create_date")

        # Skip service-linked roles
        if [[ "$role_path" == /aws-service-role/* ]]; then
            verbose "Skipping service-linked role: ${role_name}"
            continue
        fi

        # Skip AWS-managed roles
        if [[ "$role_name" == AWS* || "$role_name" == aws* ]]; then
            verbose "Skipping AWS-managed role: ${role_name}"
            continue
        fi

        # Check last used
        local last_used_date
        last_used_date=$(echo "$role" | jq -r '.RoleLastUsed.LastUsedDate // "N/A"')

        if [[ "$last_used_date" == "N/A" ]]; then
            last_used="Never"
            if [[ "$age_days" -ge "$UNUSED_DAYS" ]]; then
                severity="WARN"
                flag_warn
            else
                severity="OK"
                pass
            fi
        else
            last_used="${last_used_date:0:10}"
            local used_days
            used_days=$(days_since "$last_used_date")
            if [[ "$used_days" -ge "$UNUSED_DAYS" ]]; then
                severity="WARN"
                flag_warn
            else
                severity="OK"
                pass
            fi
        fi

        local color=""
        case "$severity" in
            WARN) color="$YELLOW" ;;
            OK)   color="$GREEN" ;;
            *)    color="" ;;
        esac

        printf "  %-40s %-16s %-10s %b%s%b\n" \
            "$role_name" "$last_used" "$age_days" \
            "$color" "$severity" "$RESET"
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       POLICY AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_policies() {
    log "Auditing IAM policies..."
    echo ""

    printf "  %-28s %-10s %-30s %s\n" \
        "ENTITY" "TYPE" "POLICY" "ISSUE"
    printf "  %s\n" "$(printf '%.0s─' {1..90})"

    local users_json
    users_json=$(aws_cmd iam list-users --output json)

    # Check users with direct policy attachments
    echo "$users_json" | jq -r '.Users[].UserName' | while IFS= read -r user_name; do
        # Attached managed policies directly on user
        local attached
        attached=$(aws_cmd iam list-attached-user-policies --user-name "$user_name" --output json 2>/dev/null) || continue

        echo "$attached" | jq -c '.AttachedPolicies[]' 2>/dev/null | while IFS= read -r pol; do
            local policy_name
            policy_name=$(echo "$pol" | jq -r '.PolicyName')

            # Flag direct attachment
            printf "  %-28s %-10s %-30s %b%s%b\n" \
                "$user_name" "User" "$policy_name" \
                "$YELLOW" "Direct attachment (use groups)" "$RESET"
            flag_warn

            # Flag admin access
            if [[ "$policy_name" == "AdministratorAccess" ]]; then
                printf "  %-28s %-10s %-30s %b%s%b\n" \
                    "$user_name" "User" "$policy_name" \
                    "$RED" "Full admin access" "$RESET"
                flag_crit
            fi
        done

        # Inline policies on user
        local inline
        inline=$(aws_cmd iam list-user-policies --user-name "$user_name" --output json 2>/dev/null) || continue

        echo "$inline" | jq -r '.PolicyNames[]' 2>/dev/null | while IFS= read -r pol_name; do
            local pol_doc
            pol_doc=$(aws_cmd iam get-user-policy --user-name "$user_name" --policy-name "$pol_name" --output json 2>/dev/null) || continue

            # Check for wildcard
            local has_star_action has_star_resource
            has_star_action=$(echo "$pol_doc" | jq '[.PolicyDocument.Statement[] | select(.Effect == "Allow") | .Action] | flatten | any(. == "*")' 2>/dev/null) || has_star_action="false"
            has_star_resource=$(echo "$pol_doc" | jq '[.PolicyDocument.Statement[] | select(.Effect == "Allow") | .Resource] | flatten | any(. == "*")' 2>/dev/null) || has_star_resource="false"

            if [[ "$has_star_action" == "true" && "$has_star_resource" == "true" ]]; then
                printf "  %-28s %-10s %-30s %b%s%b\n" \
                    "$user_name" "Inline" "$pol_name" \
                    "$RED" "Action:* Resource:*" "$RESET"
                flag_crit
            fi
        done
    done

    # Check roles with AdministratorAccess
    local roles_json
    roles_json=$(aws_cmd iam list-roles --output json)

    echo "$roles_json" | jq -c '.Roles[]' | while IFS= read -r role; do
        local role_name role_path
        role_name=$(echo "$role" | jq -r '.RoleName')
        role_path=$(echo "$role" | jq -r '.Path')

        [[ "$role_path" == /aws-service-role/* ]] && continue

        local attached
        attached=$(aws_cmd iam list-attached-role-policies --role-name "$role_name" --output json 2>/dev/null) || continue

        echo "$attached" | jq -c '.AttachedPolicies[]' 2>/dev/null | while IFS= read -r pol; do
            local policy_name
            policy_name=$(echo "$pol" | jq -r '.PolicyName')

            if [[ "$policy_name" == "AdministratorAccess" ]]; then
                printf "  %-28s %-10s %-30s %b%s%b\n" \
                    "$role_name" "Role" "$policy_name" \
                    "$RED" "Full admin access" "$RESET"
                flag_crit
            fi
        done
    done

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       ROOT ACCOUNT AUDIT
# ══════════════════════════════════════════════════════════════════════

audit_root_account() {
    log "Auditing root account and account settings..."
    echo ""

    # Account summary
    local summary
    summary=$(aws_cmd iam get-account-summary --output json 2>/dev/null) || {
        warn "Unable to retrieve account summary (may require root or admin permissions)"
        return
    }

    local root_keys root_mfa
    root_keys=$(echo "$summary" | jq '.SummaryMap.AccountAccessKeysPresent')
    root_mfa=$(echo "$summary" | jq '.SummaryMap.AccountMFAEnabled')

    echo "  Root Account Status:"
    echo "  $(printf '%.0s─' {1..50})"

    if [[ "$root_keys" -gt 0 ]]; then
        printf "  %-30s %b%s%b\n" "Root access keys:" "$RED" "PRESENT (remove them!)" "$RESET"
        flag_crit
    else
        printf "  %-30s %b%s%b\n" "Root access keys:" "$GREEN" "None" "$RESET"
        pass
    fi

    if [[ "$root_mfa" -eq 1 ]]; then
        printf "  %-30s %b%s%b\n" "Root MFA:" "$GREEN" "Enabled" "$RESET"
        pass
    else
        printf "  %-30s %b%s%b\n" "Root MFA:" "$RED" "DISABLED" "$RESET"
        flag_crit
    fi

    # Password policy
    echo ""
    log "Checking password policy..."

    local policy
    if policy=$(aws_cmd iam get-account-password-policy --output json 2>/dev/null); then
        local min_len require_upper require_lower require_numbers require_symbols max_age
        min_len=$(echo "$policy" | jq '.PasswordPolicy.MinimumPasswordLength')
        require_upper=$(echo "$policy" | jq '.PasswordPolicy.RequireUppercaseCharacters')
        require_lower=$(echo "$policy" | jq '.PasswordPolicy.RequireLowercaseCharacters')
        require_numbers=$(echo "$policy" | jq '.PasswordPolicy.RequireNumbers')
        require_symbols=$(echo "$policy" | jq '.PasswordPolicy.RequireSymbols')
        max_age=$(echo "$policy" | jq '.PasswordPolicy.MaxPasswordAge // 0')

        printf "  %-30s %s\n" "Min password length:" "$min_len"
        printf "  %-30s %s\n" "Require uppercase:" "$require_upper"
        printf "  %-30s %s\n" "Require lowercase:" "$require_lower"
        printf "  %-30s %s\n" "Require numbers:" "$require_numbers"
        printf "  %-30s %s\n" "Require symbols:" "$require_symbols"

        if [[ "$max_age" -gt 0 ]]; then
            printf "  %-30s %s days\n" "Max password age:" "$max_age"
        else
            printf "  %-30s %s\n" "Max password age:" "No expiry"
        fi

        if [[ "$min_len" -lt 14 ]]; then
            flag_warn
            printf "\n  %b%s%b\n" "$YELLOW" "Recommendation: Set minimum password length to 14+" "$RESET"
        else
            pass
        fi
    else
        printf "  %-30s %b%s%b\n" "Password policy:" "$YELLOW" "Not configured (using AWS defaults)" "$RESET"
        flag_warn
    fi

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       SUMMARY
# ══════════════════════════════════════════════════════════════════════

print_summary() {
    local elapsed
    elapsed=$(( $(now_epoch) - START_TIME ))

    echo ""
    echo "  ══════════════════════════════════════════"
    echo "  IAM Audit Summary"
    echo "  ══════════════════════════════════════════"
    printf "  %-20s %b%d%b\n" "PASS:" "$GREEN" "$TOTAL_PASS" "$RESET"
    printf "  %-20s %b%d%b\n" "WARN:" "$YELLOW" "$TOTAL_WARN" "$RESET"
    printf "  %-20s %b%d%b\n" "CRITICAL:" "$RED" "$TOTAL_CRIT" "$RESET"
    echo "  ──────────────────────────────────────────"
    printf "  Completed in %ds\n" "$elapsed"
    echo ""

    if [[ "$TOTAL_CRIT" -gt 0 ]]; then
        echo -e "  ${RED}${BOLD}Action required:${RESET} ${TOTAL_CRIT} critical finding(s)"
        echo ""
        echo "  Top recommendations:"
        echo "    • Remove root access keys immediately"
        echo "    • Enable MFA for all console users"
        echo "    • Rotate access keys older than ${KEY_AGE_CRIT_DAYS} days"
        echo "    • Replace Action:*/Resource:* policies with least-privilege"
        echo ""
    elif [[ "$TOTAL_WARN" -gt 0 ]]; then
        echo -e "  ${YELLOW}Review recommended:${RESET} ${TOTAL_WARN} warning(s)"
        echo ""
        echo "  Suggestions:"
        echo "    • Rotate access keys older than ${KEY_AGE_WARN_DAYS} days"
        echo "    • Remove or disable inactive users"
        echo "    • Use groups instead of direct policy attachments"
        echo ""
    else
        echo -e "  ${GREEN}All checks passed${RESET}"
        echo ""
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════

usage() {
    cat <<EOF
${SCRIPT_NAME} — Audit AWS IAM users, roles, policies, and access keys

USAGE:
  ${SCRIPT_NAME} [OPTIONS]

MODES:
  --full                Run all audits
  --keys                Audit access keys (age, usage, status)
  --mfa                 Audit MFA status for all users
  --users               Find inactive users
  --roles               Find unused roles
  --policies            Audit policy attachments and wildcards
  --root                Audit root account and password policy

OPTIONS:
  --key-age-warn DAYS   Warn threshold for key age (default: ${KEY_AGE_WARN_DAYS})
  --key-age-crit DAYS   Critical threshold for key age (default: ${KEY_AGE_CRIT_DAYS})
  --unused-days DAYS    Inactive threshold for users/roles (default: ${UNUSED_DAYS})
  --format FORMAT       Output format: text, csv, json (default: text)
  --verbose             Enable debug output
  --no-color            Disable colored output
  --help                Show this help

ENVIRONMENT VARIABLES:
  AWS_PROFILE           AWS CLI profile name
  AWS_REGION            AWS region
  AWS_ACCESS_KEY_ID     Access key (with AWS_SECRET_ACCESS_KEY)
  AWS_SESSION_TOKEN     Session token for temporary credentials
  KEY_AGE_WARN_DAYS     Warn threshold (default: 90)
  KEY_AGE_CRIT_DAYS     Critical threshold (default: 180)
  UNUSED_DAYS           Inactive threshold (default: 90)
  OUTPUT_FORMAT         Output format (default: text)

EXAMPLES:
  # Full audit with default thresholds
  AWS_PROFILE=prod ./iam-audit.sh --full

  # Check only access keys, flag anything older than 60 days
  ./iam-audit.sh --keys --key-age-warn 60 --key-age-crit 120

  # Check MFA status only
  ./iam-audit.sh --mfa

  # Find users and roles inactive for 60+ days
  ./iam-audit.sh --users --roles --unused-days 60
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════

parse_args() {
    local modes=()

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --full)
                modes=(keys mfa users roles policies root)
                shift ;;
            --keys)
                modes+=(keys); shift ;;
            --mfa)
                modes+=(mfa); shift ;;
            --users)
                modes+=(users); shift ;;
            --roles)
                modes+=(roles); shift ;;
            --policies)
                modes+=(policies); shift ;;
            --root)
                modes+=(root); shift ;;
            --key-age-warn)
                KEY_AGE_WARN_DAYS="$2"; shift 2 ;;
            --key-age-crit)
                KEY_AGE_CRIT_DAYS="$2"; shift 2 ;;
            --unused-days)
                UNUSED_DAYS="$2"; shift 2 ;;
            --format)
                OUTPUT_FORMAT="$2"; shift 2 ;;
            --verbose)
                VERBOSE="true"; shift ;;
            --no-color)
                COLOR="never"; shift ;;
            --help|-h)
                setup_colors
                usage
                exit 0 ;;
            *)
                err "Unknown option: $1"
                echo "Run ${SCRIPT_NAME} --help for usage" >&2
                exit 1 ;;
        esac
    done

    if [[ ${#modes[@]} -eq 0 ]]; then
        err "No audit mode specified"
        echo "Run ${SCRIPT_NAME} --help for usage" >&2
        exit 1
    fi

    RUN_MODE="${modes[*]}"
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════

main() {
    parse_args "$@"
    setup_colors
    check_deps

    START_TIME=$(now_epoch)

    echo ""
    echo -e "${BOLD}IAM Audit${RESET}"
    echo -e "Mode:   ${RUN_MODE}"
    echo -e "Time:   $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    echo -e "Thresholds: key-warn=${KEY_AGE_WARN_DAYS}d key-crit=${KEY_AGE_CRIT_DAYS}d unused=${UNUSED_DAYS}d"
    echo ""

    for mode in $RUN_MODE; do
        case "$mode" in
            keys)     audit_access_keys ;;
            mfa)      audit_mfa ;;
            users)    audit_unused_users ;;
            roles)    audit_unused_roles ;;
            policies) audit_policies ;;
            root)     audit_root_account ;;
        esac
    done

    print_summary

    if [[ "$TOTAL_CRIT" -gt 0 ]]; then
        exit 2
    elif [[ "$TOTAL_WARN" -gt 0 ]]; then
        exit 1
    fi
    exit 0
}

main "$@"
