#!/bin/bash

# ==========================================================================
# SCRIPT: s3_public_access_auditor.sh
# DESCRIPTION: Audits Amazon S3 buckets in a given region for public access
#              configurations (e.g., Block Public Access settings, bucket policies,
#              ACLs). If a bucket is found to be publicly accessible, it can
#              optionally apply a specific tag to it.
#
# USE CASE SCENARIO:
# A security team needs to regularly audit S3 buckets to ensure that sensitive
# data is not inadvertently exposed to the public internet. This script helps
# identify publicly accessible buckets and can enforce tagging for better visibility.
#
# PREREQUISITES:
# 1.  **AWS CLI:** The AWS Command Line Interface must be installed and configured
#     with credentials that have the necessary permissions.
# 2.  **jq:** The `jq` command-line JSON processor must be installed (`sudo apt-get install jq` or `brew install jq`).
# 3.  **IAM Permissions:** The principal executing this script must have:
#     - `s3:ListAllMyBuckets`
#     - `s3:GetBucketPublicAccessBlock`
#     - `s3:GetBucketPolicy`
#     - `s3:GetBucketAcl`
#     - `s3:GetBucketTagging`
#     - `s3:PutBucketTagging` (if `APPLY_TAG_TO_PUBLIC_BUCKETS` is true)
#
# HOW TO USE:
# 1.  **Save the script:** Save this content as `s3_public_access_auditor.sh`.
# 2.  **Make it executable:** `chmod +x s3_public_access_auditor.sh`
# 3.  **Configure variables:** Open the script and update the `--- Configuration Variables ---
#     section with your specific environment details.
# 4.  **Run from your terminal:** `./s3_public_access_auditor.sh`
#     - To save the report to a file: `./s3_public_access_auditor.sh > s3_public_access_report.txt`
#
# IMPORTANT CONSIDERATIONS:
# - This script performs a best-effort check for public access. A truly comprehensive
#   audit might require more advanced tools like AWS Config or AWS Security Hub.
# - Applying tags requires `s3:PutBucketTagging` permissions.
# - The script checks for public read access via ACLs and bucket policies, and also
#   the overall Block Public Access settings.
# ==========================================================================

# --- Configuration Variables (REPLACE with your actual values) ---
AWS_REGION="us-east-1"                    # AWS region to audit
APPLY_TAG_TO_PUBLIC_BUCKETS="true"      # Set to "true" to apply a tag to public buckets
PUBLIC_TAG_KEY="public"                 # Tag key to apply
PUBLIC_TAG_VALUE="true"               # Tag value to apply
# ----------------------------------------------------------------

PUBLIC_BUCKETS=()

echo "Starting S3 public access audit in region ${AWS_REGION}...\n"

# ==========================================================================
# STEP 1: List all S3 buckets in the account.
# ==========================================================================
echo ">>> Step 1: Listing all S3 buckets..."
BUCKETS_JSON=$(aws s3api list-buckets \
    --query "Buckets[*].Name" \
    --output json)

if [ $? -ne 0 ]; then
    echo "Error listing S3 buckets. Exiting."
    exit 1
fi

BUCKET_COUNT=$(echo "${BUCKETS_JSON}" | jq '. | length')
echo "   Found ${BUCKET_COUNT} S3 buckets.\n"

# ==========================================================================
# STEP 2: Check each bucket's public access settings.
# This involves checking Block Public Access, Bucket Policies, and ACLs.
# ==========================================================================
echo ">>> Step 2: Checking public access settings for each bucket..."

# Loop through each bucket name
echo "${BUCKETS_JSON}" | jq -r '.[]' | while read BUCKET_NAME;
    do
    IS_PUBLIC="false"
    PUBLIC_REASON=""

    # Check Block Public Access settings
    BPA_CONFIG=$(aws s3api get-public-access-block \
        --bucket "${BUCKET_NAME}" \
        --region "${AWS_REGION}" \
        --query "PublicAccessBlockConfiguration" \
        --output json 2>/dev/null)

    if [ $? -eq 0 ]; then
        BLOCK_ACLS=$(echo "${BPA_CONFIG}" | jq -r '.BlockPublicAcls')
        IGNORE_ACLS=$(echo "${BPA_CONFIG}" | jq -r '.IgnorePublicAcls')
        BLOCK_POLICY=$(echo "${BPA_CONFIG}" | jq -r '.BlockPublicPolicy')
        RESTRICT_BUCKETS=$(echo "${BPA_CONFIG}" | jq -r '.RestrictPublicBuckets')

        if [ "${BLOCK_ACLS}" = "false" ] || [ "${IGNORE_ACLS}" = "false" ] || \
           [ "${BLOCK_POLICY}" = "false" ] || [ "${RESTRICT_BUCKETS}" = "false" ]; then
            IS_PUBLIC="true"
            PUBLIC_REASON="Block Public Access settings are not fully enabled."
        fi
    else
        # No BPA configuration means it's potentially public
        IS_PUBLIC="true"
        PUBLIC_REASON="No Block Public Access configuration found."
    fi

    # Check Bucket Policy (if not already identified as public)
    if [ "${IS_PUBLIC}" = "false" ]; then
        BUCKET_POLICY=$(aws s3api get-bucket-policy \
            --bucket "${BUCKET_NAME}" \
            --region "${AWS_REGION}" \
            --query "Policy" \
            --output text 2>/dev/null)

        if [ $? -eq 0 ]; then
            # Check if policy allows public read access
            if echo "${BUCKET_POLICY}" | jq -e '. | select(.Statement[] | .Effect == "Allow" and .Principal == "*" and (.Action | contains("s3:GetObject") or .Action == "s3:*"))' > /dev/null; then
                IS_PUBLIC="true"
                PUBLIC_REASON="Bucket policy allows public read access."
            fi
        fi
    fi

    # Check Bucket ACLs (legacy, if not already identified as public)
    if [ "${IS_PUBLIC}" = "false" ]; then
        ACL_GRANTS=$(aws s3api get-bucket-acl \
            --bucket "${BUCKET_NAME}" \
            --region "${AWS_REGION}" \
            --query "Grants" \
            --output json 2>/dev/null)

        if [ $? -eq 0 ]; then
            if echo "${ACL_GRANTS}" | jq -e '.[] | select(.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" and (.Permission == "READ" or .Permission == "FULL_CONTROL"))' > /dev/null; then
                IS_PUBLIC="true"
                PUBLIC_REASON="Bucket ACL allows public read access."
            fi
        fi
    fi

    if [ "${IS_PUBLIC}" = "true" ]; then
        PUBLIC_BUCKETS+=("{ \"BucketName\": \"${BUCKET_NAME}\", \"PublicReason\": \"${PUBLIC_REASON}\" }")
        echo "   Bucket '${BUCKET_NAME}' is PUBLIC. Reason: ${PUBLIC_REASON}"

        # Optionally apply tag
        if [ "${APPLY_TAG_TO_PUBLIC_BUCKETS}" = "true" ]; then
            echo "      Applying tag ${PUBLIC_TAG_KEY}=${PUBLIC_TAG_VALUE} to bucket '${BUCKET_NAME}'..."
            aws s3api put-bucket-tagging \
                --bucket "${BUCKET_NAME}" \
                --tagging "TagSet=[{Key=${PUBLIC_TAG_KEY},Value=${PUBLIC_TAG_VALUE}}]" \
                --region "${AWS_REGION}" || { echo "Error applying tag to ${BUCKET_NAME}. Continuing."; }
            echo "      Tag ${PUBLIC_TAG_KEY}=${PUBLIC_TAG_VALUE} applied to '${BUCKET_NAME}'."
        fi
    else
        echo "   Bucket '${BUCKET_NAME}' is private."
    fi
done

# ==========================================================================
# STEP 3: Generate Report.
# ==========================================================================
echo "\n>>> Step 3: Generating report..."

if [ ${#PUBLIC_BUCKETS[@]} -gt 0 ]; then
    echo "--- S3 Buckets with Public Access ---"
    for BUCKET_INFO_JSON in "${PUBLIC_BUCKETS[@]}"; do
        BUCKET_NAME=$(echo "${BUCKET_INFO_JSON}" | jq -r '.BucketName')
        PUBLIC_REASON=$(echo "${BUCKET_INFO_JSON}" | jq -r '.PublicReason')

        echo "  Bucket Name: ${BUCKET_NAME}"
        echo "  Reason: ${PUBLIC_REASON}"
        echo "----------------------------------------"
    done
else
    echo "No S3 buckets found with public access."
fi

echo "\n=== S3 public access audit completed. ===\n"
