#!/bin/bash

INPUT="${1:-/mnt/c/Users/my_ch/.claude/20260511_progtu/delete_candidates.txt}"
OUTPUT="/mnt/c/Users/my_ch/.claude/20260511_progtu/aws_cost_estimate_$(date +%Y%m%d_%H%M%S).txt"
exec > >(tee "$OUTPUT") 2>&1

echo "======================================"
echo " AWS コスト試算スクリプト"
echo " 入力ファイル: $INPUT"
echo " 出力ファイル: $OUTPUT"
echo " ※AWS Pricing API から単価を取得"
echo "======================================"
echo ""
printf "%-25s %-45s %-20s %-15s\n" "リソース種類" "リソースID" "計算用情報" "月額削減試算"
echo "-----------------------------------------------------------------------------------------------"

TOTAL=0

# リージョン名 → Pricing API のロケーション名に変換
get_location() {
  case "$1" in
    ap-northeast-1) echo "Asia Pacific (Tokyo)" ;;
    ap-northeast-2) echo "Asia Pacific (Seoul)" ;;
    ap-northeast-3) echo "Asia Pacific (Osaka)" ;;
    ap-southeast-1) echo "Asia Pacific (Singapore)" ;;
    ap-southeast-2) echo "Asia Pacific (Sydney)" ;;
    ap-south-1)     echo "Asia Pacific (Mumbai)" ;;
    us-east-1)      echo "US East (N. Virginia)" ;;
    us-east-2)      echo "US East (Ohio)" ;;
    us-west-1)      echo "US West (N. California)" ;;
    us-west-2)      echo "US West (Oregon)" ;;
    eu-west-1)      echo "Europe (Ireland)" ;;
    eu-west-2)      echo "Europe (London)" ;;
    eu-central-1)   echo "Europe (Frankfurt)" ;;
    sa-east-1)      echo "South America (Sao Paulo)" ;;
    ca-central-1)   echo "Canada (Central)" ;;
    *)              echo "Asia Pacific (Tokyo)" ;;
  esac
}

# EC2 単価取得($/時間)
get_ec2_price() {
  local INSTANCE_TYPE=$1
  local LOCATION=$2
  aws pricing get-products \
    --region us-east-1 \
    --service-code AmazonEC2 \
    --filters \
      "Type=TERM_MATCH,Field=instanceType,Value=$INSTANCE_TYPE" \
      "Type=TERM_MATCH,Field=location,Value=$LOCATION" \
      "Type=TERM_MATCH,Field=operatingSystem,Value=Linux" \
      "Type=TERM_MATCH,Field=tenancy,Value=Shared" \
      "Type=TERM_MATCH,Field=capacitystatus,Value=Used" \
      "Type=TERM_MATCH,Field=preInstalledSw,Value=NA" \
    --query "PriceList[0]" \
    --output text 2>/dev/null | \
    python3 -c "
import sys, json
data = json.load(sys.stdin)
od = data['terms']['OnDemand']
key1 = list(od.keys())[0]
key2 = list(od[key1]['priceDimensions'].keys())[0]
print(od[key1]['priceDimensions'][key2]['pricePerUnit']['USD'])
" 2>/dev/null
}

# RDS 単価取得($/時間)
get_rds_price() {
  local INSTANCE_TYPE=$1
  local LOCATION=$2
  aws pricing get-products \
    --region us-east-1 \
    --service-code AmazonRDS \
    --filters \
      "Type=TERM_MATCH,Field=instanceType,Value=$INSTANCE_TYPE" \
      "Type=TERM_MATCH,Field=location,Value=$LOCATION" \
      "Type=TERM_MATCH,Field=databaseEngine,Value=MySQL" \
      "Type=TERM_MATCH,Field=deploymentOption,Value=Single-AZ" \
    --query "PriceList[0]" \
    --output text 2>/dev/null | \
    python3 -c "
import sys, json
data = json.load(sys.stdin)
od = data['terms']['OnDemand']
key1 = list(od.keys())[0]
key2 = list(od[key1]['priceDimensions'].keys())[0]
print(od[key1]['priceDimensions'][key2]['pricePerUnit']['USD'])
" 2>/dev/null
}

# EBS 単価取得($/GB/月)
get_ebs_price() {
  local VOL_TYPE=$1
  local LOCATION=$2
  local API_VOL_TYPE
  case "$VOL_TYPE" in
    gp3) API_VOL_TYPE="General Purpose" ;;
    gp2) API_VOL_TYPE="General Purpose" ;;
    io1) API_VOL_TYPE="Provisioned IOPS" ;;
    st1) API_VOL_TYPE="Throughput Optimized HDD" ;;
    sc1) API_VOL_TYPE="Cold HDD" ;;
    *)   API_VOL_TYPE="General Purpose" ;;
  esac
  aws pricing get-products \
    --region us-east-1 \
    --service-code AmazonEC2 \
    --filters \
      "Type=TERM_MATCH,Field=productFamily,Value=Storage" \
      "Type=TERM_MATCH,Field=location,Value=$LOCATION" \
      "Type=TERM_MATCH,Field=volumeApiName,Value=$VOL_TYPE" \
    --query "PriceList[0]" \
    --output text 2>/dev/null | \
    python3 -c "
import sys, json
data = json.load(sys.stdin)
od = data['terms']['OnDemand']
key1 = list(od.keys())[0]
key2 = list(od[key1]['priceDimensions'].keys())[0]
print(od[key1]['priceDimensions'][key2]['pricePerUnit']['USD'])
" 2>/dev/null
}

while IFS=$'\t' read -r TYPE ID REGION INFO; do
  # コメント行・空行スキップ
  [[ "$TYPE" =~ ^#.*$ ]] || [ -z "$TYPE" ] && continue

  COST=0
  LOCATION=$(get_location "$REGION")

  case "$TYPE" in

    EC2)
      HOURLY=$(get_ec2_price "$INFO" "$LOCATION")
      [ -z "$HOURLY" ] && HOURLY=0
      COST=$(awk "BEGIN {printf \"%.2f\", $HOURLY * 24 * 30}")
      ;;

    EBS)
      SIZE=$(echo "$INFO" | cut -d'/' -f1 | tr -d 'GB')
      VOL_TYPE=$(echo "$INFO" | cut -d'/' -f2)
      PRICE_PER_GB=$(get_ebs_price "$VOL_TYPE" "$LOCATION")
      [ -z "$PRICE_PER_GB" ] && PRICE_PER_GB=0
      COST=$(awk "BEGIN {printf \"%.2f\", $SIZE * $PRICE_PER_GB}")
      ;;

    RDS)
      HOURLY=$(get_rds_price "$INFO" "$LOCATION")
      [ -z "$HOURLY" ] && HOURLY=0
      COST=$(awk "BEGIN {printf \"%.2f\", $HOURLY * 24 * 30}")
      ;;

    NAT)
      # Pricing API 非対応のため固定単価(全リージョンほぼ同額)
      COST=$(awk "BEGIN {printf \"%.2f\", 0.062 * 24 * 30}")
      ;;

    EIP)
      # Pricing API 非対応のため固定単価
      COST=$(awk "BEGIN {printf \"%.2f\", 0.005 * 24 * 30}")
      ;;

    LB)
      # Pricing API 非対応のため固定単価
      COST=$(awk "BEGIN {printf \"%.2f\", 0.0243 * 24 * 30}")
      ;;

    EC2スナップショット)
      # Pricing API 非対応のため固定単価($0.05/GB/月)
      SIZE=$(echo "$INFO" | tr -d 'GB')
      COST=$(awk "BEGIN {printf \"%.2f\", $SIZE * 0.05}")
      ;;

    RDSスナップショット)
      # Pricing API 非対応のため固定単価($0.095/GB/月)
      SIZE=$(echo "$INFO" | tr -d 'GB')
      COST=$(awk "BEGIN {printf \"%.2f\", $SIZE * 0.095}")
      ;;

    "CloudWatch Logs")
      # 保存期間設定で削減(削除ではなく設定変更のため試算対象外)
      COST=0
      INFO="保存期間設定で対応"
      ;;

  esac

  TOTAL=$(awk "BEGIN {printf \"%.2f\", $TOTAL + $COST}")
  printf "%-25s %-45s %-20s \$%-15s\n" "$TYPE" "$ID" "$INFO" "$COST"

done < "$INPUT"

echo "-----------------------------------------------------------------------------------------------"
printf "%-25s %-45s %-20s \$%-15s\n" "合計削減試算" "" "" "$TOTAL"
echo ""
echo "======================================"
echo " 完了"
echo "======================================"
#!/bin/bash

OUTPUT="/mnt/c/Users/my_ch/.claude/20260511_progtu/aws_cost_check_$(date +%Y%m%d_%H%M%S).txt"
exec > >(tee "$OUTPUT") 2>&1

START_TIME=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)

echo "======================================"
echo " AWS リソース洗い出しスクリプト"
echo " 出力ファイル: $OUTPUT"
echo " 調査期間: 過去30日間"
echo "======================================"
echo ""
printf "%-25s %-45s %-20s %-30s %-20s\n" "リソース種類" "リソース名/ID" "リージョン" "未使用判定" "計算用情報"
echo "--------------------------------------------------------------------------------------------------------------------"

REGIONS=$(aws ec2 describe-regions --query "Regions[*].RegionName" --output text)

# S3(グローバル)
# CLI: list-buckets → s3api list-objects-v2 でオブジェクト数・最終更新日を確認
# 判定: 空 = 未使用、最終更新が90日以上前 = 長期未使用の可能性、それ以外 = 使用中
THRESHOLD_DAYS=90
NOW_EPOCH=$(date +%s)
for BUCKET in $(aws s3api list-buckets --query "Buckets[*].Name" --output text); do
  COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET" --query "length(Contents)" --output text 2>/dev/null)
  if [ "$COUNT" = "None" ] || [ "$COUNT" = "0" ] || [ -z "$COUNT" ]; then
    UNUSED="未使用(空)"
  else
    LAST_MODIFIED=$(aws s3api list-objects-v2 --bucket "$BUCKET" \
      --query "sort_by(Contents, &LastModified)[-1].LastModified" \
      --output text 2>/dev/null)
    if [ -n "$LAST_MODIFIED" ] && [ "$LAST_MODIFIED" != "None" ]; then
      LAST_EPOCH=$(date -d "${LAST_MODIFIED%%.*}" +%s 2>/dev/null)
      DAYS=$(( (NOW_EPOCH - LAST_EPOCH) / 86400 ))
      if [ "$DAYS" -ge "$THRESHOLD_DAYS" ]; then
        UNUSED="要確認(最終更新:${DAYS}日前)"
      else
        UNUSED="使用中(最終更新:${DAYS}日前)"
      fi
    else
      UNUSED="使用中"
    fi
  fi
  printf "%-25s %-45s %-20s %-30s %-20s\n" "S3バケット" "$BUCKET" "global" "$UNUSED" "${COUNT:-0}オブジェクト"
done

# Route53(グローバル)
# 判定: レコード数が2以下(デフォルトのNSとSOAレコードのみ)= カスタムレコードなし = 未使用の可能性
aws route53 list-hosted-zones \
  --query "HostedZones[*].[Name,ResourceRecordSetCount]" \
  --output text 2>/dev/null | while IFS=$'\t' read -r NAME COUNT; do
  if [ "$COUNT" -le 2 ] 2>/dev/null; then UNUSED="未使用の可能性"; else UNUSED="使用中"; fi
  printf "%-25s %-45s %-20s %-30s %-20s\n" "Route53ホストゾーン" "$NAME" "global" "$UNUSED" "-"
done

for REGION in $REGIONS; do

  # EC2
  # CLI: describe-instances → CloudWatch CPU使用率をインスタンスごと
  # 判定: CloudWatch CPU使用率の過去30日平均が5%未満 = ほぼ使われていない = 未使用
  aws ec2 describe-instances --region $REGION \
    --query "Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,Tags[?Key=='Name'].Value|[0]]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r INSTANCE_ID STATE TYPE NAME; do
    CPU=$(aws cloudwatch get-metric-statistics \
      --region $REGION \
      --namespace AWS/EC2 \
      --metric-name CPUUtilization \
      --dimensions Name=InstanceId,Value=$INSTANCE_ID \
      --start-time $START_TIME --end-time $END_TIME \
      --period 2592000 --statistics Average \
      --query "Datapoints[0].Average" --output text 2>/dev/null)
    [ "$CPU" = "None" ] || [ -z "$CPU" ] && CPU=0
    NAME=${NAME:-"(名前なし)"}
    if awk "BEGIN {exit !($CPU < 5)}"; then UNUSED="未使用(CPU:${CPU}%)"; else UNUSED="使用中(CPU:${CPU}%)"; fi
    printf "%-25s %-45s %-20s %-30s %-20s\n" "EC2" "$INSTANCE_ID($NAME)" "$REGION" "$UNUSED" "$TYPE"
  done

  # 未使用の EIP
  # 判定: AssociationId が null = EC2 にアタッチされていない = 未使用(アタッチなしでも課金される)
  aws ec2 describe-addresses --region $REGION \
    --query "Addresses[?AssociationId==null].[PublicIp,AllocationId]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r IP ALLOC_ID; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "EIP" "$IP" "$REGION" "未使用(未アタッチ)" "-"
  done

  # 未使用の EBS
  # 判定: status が available = EC2 にアタッチされていない = 未使用(存在するだけで課金される)
  aws ec2 describe-volumes --region $REGION \
    --filters Name=status,Values=available \
    --query "Volumes[*].[VolumeId,Size,VolumeType]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r VOL_ID SIZE TYPE; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "EBSボリューム" "$VOL_ID" "$REGION" "未使用(未アタッチ)" "${SIZE}GB/$TYPE"
  done

  # EC2 スナップショット
  # 判定: 自分のアカウントが所有するスナップショットを全件表示(古いものは要確認)
  # ※自動で未使用判定はできないため作成日を出力して手動確認
  aws ec2 describe-snapshots --region $REGION --owner-ids self \
    --query "Snapshots[*].[SnapshotId,VolumeSize,StartTime]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r SNAP_ID SIZE DATE; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "EC2スナップショット" "$SNAP_ID" "$REGION" "要確認(作成日:${DATE%%T*})" "${SIZE}GB"
  done

  # AMI
  # 判定: 自分のアカウントが所有する AMI を全件表示(古いものは要確認)
  # ※使用中かどうかはEC2の起動テンプレート等と照合が必要なため手動確認
  aws ec2 describe-images --region $REGION --owners self \
    --query "Images[*].[ImageId,Name,CreationDate]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r AMI_ID NAME DATE; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "AMI" "$AMI_ID($NAME)" "$REGION" "要確認(作成日:${DATE%%T*})" "-"
  done

  # NAT Gateway
  # CLI: describe-nat-gateways → CloudWatch 送信バイト数をNATごと
  # 判定: CloudWatch の送信バイト数(過去30日合計)が0 = 通信なし = 未使用
  aws ec2 describe-nat-gateways --region $REGION \
    --filter Name=state,Values=available \
    --query "NatGateways[*].[NatGatewayId,Tags[?Key=='Name'].Value|[0]]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r NAT_ID NAME; do
    BYTES=$(aws cloudwatch get-metric-statistics \
      --region $REGION \
      --namespace AWS/NATGateway \
      --metric-name BytesOutToDestination \
      --dimensions Name=NatGatewayId,Value=$NAT_ID \
      --start-time $START_TIME --end-time $END_TIME \
      --period 2592000 --statistics Sum \
      --query "Datapoints[0].Sum" --output text 2>/dev/null)
    [ "$BYTES" = "None" ] || [ -z "$BYTES" ] && BYTES=0
    NAME=${NAME:-"(名前なし)"}
    if awk "BEGIN {exit !($BYTES < 1)}"; then UNUSED="未使用(通信なし)"; else UNUSED="使用中"; fi
    printf "%-25s %-45s %-20s %-30s %-20s\n" "NATGateway" "$NAT_ID($NAME)" "$REGION" "$UNUSED" "-"
  done

  # CloudWatch Logs
  # 判定: retentionInDays が null = 保存期間が無期限 = ログが溜まり続けてコスト増加の可能性
  aws logs describe-log-groups --region $REGION \
    --query "logGroups[?retentionInDays==null].[logGroupName,storedBytes]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r LOG_NAME BYTES; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "CloudWatch Logs" "$LOG_NAME" "$REGION" "要確認(保存期間無期限)" "-"
  done

  # RDS
  # CLI: describe-db-instances → CloudWatch 接続数をDBごと
  # 判定: CloudWatch の接続数(過去30日平均)が1未満 = 接続なし = 未使用
  aws rds describe-db-instances --region $REGION \
    --query "DBInstances[*].[DBInstanceIdentifier,DBInstanceStatus,DBInstanceClass]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r DB_ID STATUS CLASS; do
    CONNECTIONS=$(aws cloudwatch get-metric-statistics \
      --region $REGION \
      --namespace AWS/RDS \
      --metric-name DatabaseConnections \
      --dimensions Name=DBInstanceIdentifier,Value=$DB_ID \
      --start-time $START_TIME --end-time $END_TIME \
      --period 2592000 --statistics Average \
      --query "Datapoints[0].Average" --output text 2>/dev/null)
    [ "$CONNECTIONS" = "None" ] || [ -z "$CONNECTIONS" ] && CONNECTIONS=0
    if awk "BEGIN {exit !($CONNECTIONS < 1)}"; then UNUSED="未使用(接続数:${CONNECTIONS})"; else UNUSED="使用中(接続数:${CONNECTIONS})"; fi
    printf "%-25s %-45s %-20s %-30s %-20s\n" "RDS" "$DB_ID" "$REGION" "$UNUSED" "$CLASS"
  done

  # RDS スナップショット
  # 判定: 手動スナップショットを全件表示(古いものは要確認)
  # ※自動スナップショットは除外(--snapshot-type manual)
  aws rds describe-db-snapshots --region $REGION \
    --snapshot-type manual \
    --query "DBSnapshots[*].[DBSnapshotIdentifier,DBInstanceIdentifier,SnapshotCreateTime,AllocatedStorage]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r SNAP_ID DB_ID DATE SIZE; do
    printf "%-25s %-45s %-20s %-30s %-20s\n" "RDSスナップショット" "$SNAP_ID" "$REGION" "要確認(作成日:${DATE%%T*})" "${SIZE}GB"
  done

  # ロードバランサー
  # CLI: describe-load-balancers → CloudWatch リクエスト数をLBごと
  # 判定: CloudWatch のリクエスト数(過去30日合計)が0 = リクエストなし = 未使用
  aws elbv2 describe-load-balancers --region $REGION \
    --query "LoadBalancers[*].[LoadBalancerName,State.Code,Type,LoadBalancerArn]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r LB_NAME STATE TYPE LB_ARN; do
    REQUESTS=$(aws cloudwatch get-metric-statistics \
      --region $REGION \
      --namespace AWS/ApplicationELB \
      --metric-name RequestCount \
      --dimensions Name=LoadBalancer,Value=$(echo $LB_ARN | sed 's/.*loadbalancer\///') \
      --start-time $START_TIME --end-time $END_TIME \
      --period 2592000 --statistics Sum \
      --query "Datapoints[0].Sum" --output text 2>/dev/null)
    [ "$REQUESTS" = "None" ] || [ -z "$REQUESTS" ] && REQUESTS=0
    if awk "BEGIN {exit !($REQUESTS < 1)}"; then UNUSED="未使用(リクエスト:${REQUESTS})"; else UNUSED="使用中(リクエスト:${REQUESTS})"; fi
    printf "%-25s %-45s %-20s %-30s %-20s\n" "ロードバランサー" "$LB_NAME" "$REGION" "$UNUSED" "$TYPE"
  done

  # ECR
  # CLI: describe-repositories → list-images をリポジトリごと
  # 判定: リポジトリ内のイメージ数が0 = イメージなし = 未使用
  aws ecr describe-repositories --region $REGION \
    --query "repositories[*].[repositoryName]" \
    --output text 2>/dev/null | while read -r REPO_NAME; do
    IMAGE_COUNT=$(aws ecr list-images --region $REGION \
      --repository-name $REPO_NAME \
      --query "length(imageIds)" --output text 2>/dev/null)
    if [ "$IMAGE_COUNT" = "0" ]; then UNUSED="未使用(イメージなし)"; else UNUSED="使用中(${IMAGE_COUNT}イメージ)"; fi
    printf "%-25s %-45s %-20s %-30s %-20s\n" "ECRリポジトリ" "$REPO_NAME" "$REGION" "$UNUSED" "-"
  done

  # Secrets Manager
  # 判定: LastAccessedDate が null = 一度もアクセスされていない = 未使用の可能性
  aws secretsmanager list-secrets --region $REGION \
    --query "SecretList[*].[Name,LastAccessedDate]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r SECRET_NAME LAST_ACCESS; do
    [ -z "$LAST_ACCESS" ] && UNUSED="未使用(アクセス履歴なし)" || UNUSED="要確認(最終:${LAST_ACCESS%%T*})"
    printf "%-25s %-45s %-20s %-30s %-20s\n" "SecretsManager" "$SECRET_NAME" "$REGION" "$UNUSED" "-"
  done

  # VPN
  # 判定: state が available = 起動中 = 存在するだけで課金されるため要確認
  aws ec2 describe-vpn-connections --region $REGION \
    --filters Name=state,Values=available \
    --query "VpnConnections[*].[VpnConnectionId,Tags[?Key=='Name'].Value|[0]]" \
    --output text 2>/dev/null | while IFS=$'\t' read -r VPN_ID NAME; do
    NAME=${NAME:-"(名前なし)"}
    printf "%-25s %-45s %-20s %-30s %-20s\n" "VPN接続" "$VPN_ID($NAME)" "$REGION" "要確認(起動中)" "-"
  done

done

echo ""
echo "======================================"
echo " 完了"
echo "======================================"
#!/bin/bash

# 使い方:
#   ./aws_iam_audit.sh                        # 通常実行
#   ./aws_iam_audit.sh --mock <csvファイル>   # モックCSVで実行(テスト用)
#   THRESHOLD=0 ./aws_iam_audit.sh            # しきい値を0日に変更して実行

MOCK_FILE=""
if [ "$1" = "--mock" ] && [ -n "$2" ]; then
  MOCK_FILE="$2"
fi

OUTPUT="/mnt/c/Users/my_ch/.claude/20260511_progtu/aws_iam_audit_$(date +%Y%m%d_%H%M%S).txt"
exec > >(tee "$OUTPUT") 2>&1

THRESHOLD="${THRESHOLD:-90}"  # 長期未使用・古いキー判定のしきい値(日数)。環境変数で上書き可能
TODAY=$(date +%s)

echo "======================================"
echo " AWS IAM 棚卸しスクリプト"
echo " 出力ファイル: $OUTPUT"
echo " しきい値: ${THRESHOLD}日"
[ -n "$MOCK_FILE" ] && echo " モード: モック ($MOCK_FILE)"
echo "======================================"
echo ""

printf "%-20s %-12s %-18s %-18s %-10s %-5s %-22s %-22s %-20s %-35s %-s\n" \
  "ユーザー名" "作成日" "最終ログイン" "PW最終変更" "コンソール" "MFA" \
  "キー1(状態/最終使用)" "キー2(状態/最終使用)" "所属グループ" "アタッチポリシー" "対応要否"
echo "$(printf '%0.s-' {1..220})"

if [ -n "$MOCK_FILE" ]; then
  # モックCSVをそのまま使う
  REPORT=$(cat "$MOCK_FILE")
else
  # クレデンシャルレポート生成リクエスト
  aws iam generate-credential-report --output text > /dev/null 2>&1

  # レポートが完成するまで待機(最大30秒)
  for i in $(seq 1 10); do
    STATE=$(aws iam generate-credential-report --query State --output text 2>/dev/null)
    [ "$STATE" = "COMPLETE" ] && break
    sleep 3
  done

  # レポート取得・デコード
  REPORT=$(aws iam get-credential-report --query Content --output text 2>/dev/null | base64 -d)
fi

# ヘッダー行スキップして1行ずつ処理(fd3を使いstdinとCSVを分離)
while IFS=',' read -r \
  USER ARN CREATION PASSWORD_ENABLED PASSWORD_LAST_USED PASSWORD_LAST_CHANGED \
  PASSWORD_NEXT_ROTATION MFA_ACTIVE \
  KEY1_ACTIVE KEY1_LAST_ROTATED KEY1_LAST_USED_DATE KEY1_LAST_USED_REGION KEY1_LAST_USED_SERVICE \
  KEY2_ACTIVE KEY2_LAST_ROTATED KEY2_LAST_USED_DATE KEY2_LAST_USED_REGION KEY2_LAST_USED_SERVICE \
  CERT1_ACTIVE CERT1_LAST_ROTATED CERT2_ACTIVE CERT2_LAST_ROTATED <&3; do

  # rootアカウントの表示名
  [ "$USER" = "<root_account>" ] && USER="(root)"

  # --- 作成日 ---
  CREATED="${CREATION%%T*}"

  # --- 最終ログイン ---
  if [ "$PASSWORD_LAST_USED" = "N/A" ] || [ "$PASSWORD_LAST_USED" = "no_information" ] || [ -z "$PASSWORD_LAST_USED" ]; then
    LAST_LOGIN="未ログイン"
    LAST_LOGIN_DAYS=9999
  else
    LAST_LOGIN_DATE="${PASSWORD_LAST_USED%%T*}"
    LAST_LOGIN_EPOCH=$(date -d "$LAST_LOGIN_DATE" +%s 2>/dev/null || echo 0)
    LAST_LOGIN_DAYS=$(( (TODAY - LAST_LOGIN_EPOCH) / 86400 ))
    LAST_LOGIN="${LAST_LOGIN_DATE}(${LAST_LOGIN_DAYS}日前)"
  fi

  # --- PW最終変更 ---
  if [ "$PASSWORD_LAST_CHANGED" = "N/A" ] || [ "$PASSWORD_LAST_CHANGED" = "not_supported" ] || [ -z "$PASSWORD_LAST_CHANGED" ]; then
    PW_CHANGED="N/A"
    PW_CHANGED_DAYS=0
  else
    PW_CHANGED_DATE="${PASSWORD_LAST_CHANGED%%T*}"
    PW_CHANGED_EPOCH=$(date -d "$PW_CHANGED_DATE" +%s 2>/dev/null || echo 0)
    PW_CHANGED_DAYS=$(( (TODAY - PW_CHANGED_EPOCH) / 86400 ))
    PW_CHANGED="${PW_CHANGED_DATE}(${PW_CHANGED_DAYS}日前)"
  fi

  # --- コンソールアクセス ---
  [ "$PASSWORD_ENABLED" = "true" ] && CONSOLE="有効" || CONSOLE="無効"

  # --- MFA ---
  [ "$MFA_ACTIVE" = "true" ] && MFA="有" || MFA="無"

  # --- アクセスキー1 ---
  KEY1_ROT_DAYS=0
  KEY1_LAST_DAYS=0
  if [ "$KEY1_ACTIVE" = "true" ] || [ "$KEY1_ACTIVE" = "false" ]; then
    KEY1_STATE=$([ "$KEY1_ACTIVE" = "true" ] && echo "有効" || echo "無効")
    # キー作成からの経過日数
    if [ "$KEY1_LAST_ROTATED" = "N/A" ] || [ -z "$KEY1_LAST_ROTATED" ]; then
      KEY1_ROT_DAYS=0
      KEY1_ROT_LABEL="作成日不明"
    else
      KEY1_ROT_DATE="${KEY1_LAST_ROTATED%%T*}"
      KEY1_ROT_EPOCH=$(date -d "$KEY1_ROT_DATE" +%s 2>/dev/null || echo 0)
      KEY1_ROT_DAYS=$(( (TODAY - KEY1_ROT_EPOCH) / 86400 ))
      KEY1_ROT_LABEL="作成${KEY1_ROT_DAYS}日前"
    fi
    # キー最終使用日
    if [ "$KEY1_LAST_USED_DATE" = "N/A" ] || [ -z "$KEY1_LAST_USED_DATE" ]; then
      KEY1_LAST_DAYS=9999
      KEY1="${KEY1_STATE}/未使用/${KEY1_ROT_LABEL}"
    else
      KEY1_DATE="${KEY1_LAST_USED_DATE%%T*}"
      KEY1_EPOCH=$(date -d "$KEY1_DATE" +%s 2>/dev/null || echo 0)
      KEY1_LAST_DAYS=$(( (TODAY - KEY1_EPOCH) / 86400 ))
      KEY1="${KEY1_STATE}/最終:${KEY1_DATE}(${KEY1_LAST_DAYS}日前)/${KEY1_ROT_LABEL}"
    fi
  else
    KEY1="なし"
  fi

  # --- アクセスキー2 ---
  KEY2_ROT_DAYS=0
  KEY2_LAST_DAYS=0
  if [ "$KEY2_ACTIVE" = "true" ] || [ "$KEY2_ACTIVE" = "false" ]; then
    KEY2_STATE=$([ "$KEY2_ACTIVE" = "true" ] && echo "有効" || echo "無効")
    if [ "$KEY2_LAST_ROTATED" = "N/A" ] || [ -z "$KEY2_LAST_ROTATED" ]; then
      KEY2_ROT_DAYS=0
      KEY2_ROT_LABEL="作成日不明"
    else
      KEY2_ROT_DATE="${KEY2_LAST_ROTATED%%T*}"
      KEY2_ROT_EPOCH=$(date -d "$KEY2_ROT_DATE" +%s 2>/dev/null || echo 0)
      KEY2_ROT_DAYS=$(( (TODAY - KEY2_ROT_EPOCH) / 86400 ))
      KEY2_ROT_LABEL="作成${KEY2_ROT_DAYS}日前"
    fi
    if [ "$KEY2_LAST_USED_DATE" = "N/A" ] || [ -z "$KEY2_LAST_USED_DATE" ]; then
      KEY2_LAST_DAYS=9999
      KEY2="${KEY2_STATE}/未使用/${KEY2_ROT_LABEL}"
    else
      KEY2_DATE="${KEY2_LAST_USED_DATE%%T*}"
      KEY2_EPOCH=$(date -d "$KEY2_DATE" +%s 2>/dev/null || echo 0)
      KEY2_LAST_DAYS=$(( (TODAY - KEY2_EPOCH) / 86400 ))
      KEY2="${KEY2_STATE}/最終:${KEY2_DATE}(${KEY2_LAST_DAYS}日前)/${KEY2_ROT_LABEL}"
    fi
  else
    KEY2="なし"
  fi

  # --- 所属グループ / アタッチポリシー(rootは対象外)---
  if [ "$USER" = "(root)" ]; then
    USER_GROUPS="-"
    POLICIES="-"
  else
    USER_GROUPS=$(aws iam list-groups-for-user --user-name "$USER" \
      --query "Groups[*].GroupName" --output text </dev/null 2>/dev/null | tr '\t' ',' | grep -v '^None$')
    [ -z "$USER_GROUPS" ] && USER_GROUPS="なし"

    # マネージドポリシー
    POLICIES=$(aws iam list-attached-user-policies --user-name "$USER" \
      --query "AttachedPolicies[*].PolicyName" --output text </dev/null 2>/dev/null | tr '\t' ',' | grep -v '^None$')
    # インラインポリシー
    INLINE=$(aws iam list-user-policies --user-name "$USER" \
      --query "PolicyNames" --output text </dev/null 2>/dev/null | tr '\t' ',' | grep -v '^None$')
    [ -n "$INLINE" ] && POLICIES="${POLICIES:+$POLICIES,}${INLINE}(inline)"
    [ -z "$POLICIES" ] && POLICIES="なし"
  fi

  # --- 対応要否判定 ---
  ACTIONS=""

  # MFA未設定 かつ コンソールアクセス有効
  if [ "$MFA_ACTIVE" = "false" ] && [ "$PASSWORD_ENABLED" = "true" ]; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要対応:MFA未設定"
  fi

  # コンソール有効なのに一度もログインしていない
  if [ "$LAST_LOGIN" = "未ログイン" ] && [ "$PASSWORD_ENABLED" = "true" ]; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要確認:未ログイン"
  fi

  # 最終ログインがしきい値超え
  if [ "$LAST_LOGIN_DAYS" -gt "$THRESHOLD" ] && [ "$LAST_LOGIN" != "未ログイン" ]; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要対応:長期未使用(${LAST_LOGIN_DAYS}日)"
  fi

  # キー1の作成からしきい値超え(古いキー)
  if [ "$KEY1_ROT_DAYS" -gt "$THRESHOLD" ] && [ "$KEY1" != "なし" ]; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要対応:キー1古い(${KEY1_ROT_DAYS}日)"
  fi

  # キー2の作成からしきい値超え
  if [ "$KEY2_ROT_DAYS" -gt "$THRESHOLD" ] && [ "$KEY2" != "なし" ]; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要対応:キー2古い(${KEY2_ROT_DAYS}日)"
  fi

  # 管理者権限チェック
  if echo "$POLICIES" | grep -qi "AdministratorAccess"; then
    ACTIONS="${ACTIONS:+$ACTIONS / }要確認:管理者権限"
  fi

  [ -z "$ACTIONS" ] && ACTIONS="問題なし"

  printf "%-20s %-12s %-18s %-18s %-10s %-5s %-22s %-22s %-20s %-35s %-s\n" \
    "$USER" "$CREATED" "$LAST_LOGIN" "$PW_CHANGED" "$CONSOLE" "$MFA" \
    "$KEY1" "$KEY2" "$USER_GROUPS" "$POLICIES" "$ACTIONS"

done 3< <(echo "$REPORT" | tail -n +2)

echo ""
echo "======================================"
echo " 完了"
echo "======================================"

コメント

タイトルとURLをコピーしました