#!/bin/bash

# ==========================================================================
# SCRIPT: sg_public_access_scanner.sh
# DESCRIPTION: Scans AWS Security Groups in a given region to identify inbound
#              rules that allow public access (0.0.0.0/0 or ::/0) to common
#              sensitive ports. This helps in identifying potential security
#              vulnerabilities due to overly permissive security group rules.
#
# USE CASE SCENARIO:
# A security team needs to regularly audit AWS environments to ensure that
# security groups are not inadvertently exposing sensitive services to the
# public internet. This script provides a quick way to generate a report
# of such findings.
#
# 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:
#     - `ec2:DescribeSecurityGroups`
#
# HOW TO USE:
# 1.  **Save the script:** Save this content as `sg_public_access_scanner.sh`.
# 2.  **Make it executable:** `chmod +x sg_public_access_scanner.sh`
# 3.  **Configure variables:** Open the script and update the `--- Configuration Variables ---
#     section with your specific environment details.
# 4.  **Run from your terminal:** `./sg_public_access_scanner.sh`
#     - To save the report to a file: `./sg_public_access_scanner.sh > security_group_report.txt`
#
# IMPORTANT CONSIDERATIONS:
# - This script identifies rules open to 0.0.0.0/0 or ::/0. It does not evaluate
#   rules that are open to specific IP ranges or other security groups.
# - The list of `SENSITIVE_PORTS` can be customized based on your organization's needs.
# - For comprehensive security posture management, consider using AWS Security Hub
#   and AWS Config.
# ==========================================================================

# --- Configuration Variables (REPLACE with your actual values) ---
AWS_REGION="us-east-1"                    # AWS region to scan
# ----------------------------------------------------------------

# Define common sensitive ports that should generally not be open to the public
# Format: "port:description"
SENSITIVE_PORTS_LIST=(
    "20:FTP Data" 
    "21:FTP Control" 
    "22:SSH" 
    "23:Telnet" 
    "25:SMTP" 
    "3306:MySQL" 
    "3389:RDP" 
    "5432:PostgreSQL" 
    "1433:SQL Server" 
    "1521:Oracle" 
    "27017:MongoDB" 
    "27018:MongoDB" 
    "27019:MongoDB" 
    "6379:Redis" 
    "9200:Elasticsearch/OpenSearch" 
    "9300:Elasticsearch/OpenSearch" 
)

# Convert SENSITIVE_PORTS_LIST to an associative array for easier lookup
declare -A SENSITIVE_PORTS_MAP
for item in "${SENSITIVE_PORTS_LIST[@]}"; do
    KEY=${item%%:*}
    VALUE=${item##*:}
    SENSITIVE_PORTS_MAP["$KEY"]="$VALUE"
done

PUBLIC_ACCESS_SGS=()

echo "Starting Security Group scan for public access in region ${AWS_REGION}...\n"

# ==========================================================================
# STEP 1: Describe all security groups in the region.
# ==========================================================================
echo ">>> Step 1: Describing all security groups..."
SECURITY_GROUPS_JSON=$(aws ec2 describe-security-groups \
    --region "${AWS_REGION}" \
    --output json)

if [ $? -ne 0 ]; then
    echo "Error describing security groups. Exiting."
    exit 1
fi

SG_COUNT=$(echo "${SECURITY_GROUPS_JSON}" | jq '.SecurityGroups | length')
echo "   Found ${SG_COUNT} security groups.\n"

# ==========================================================================
# STEP 2: Analyze inbound rules for public access.
# Iterate through each security group and its inbound rules (IpPermissions).
# ==========================================================================
echo ">>> Step 2: Analyzing inbound rules for public access..."

# Loop through each security group
echo "${SECURITY_GROUPS_JSON}" | jq -c '.SecurityGroups[]' | while read SG_JSON; do
    SG_ID=$(echo "${SG_JSON}" | jq -r '.GroupId')
    SG_NAME=$(echo "${SG_JSON}" | jq -r '.GroupName')
    VPC_ID=$(echo "${SG_JSON}" | jq -r '.VpcId')

    # Loop through each inbound permission rule
    echo "${SG_JSON}" | jq -c '.IpPermissions[]' | while read IP_PERMISSION_JSON; do
        PROTOCOL=$(echo "${IP_PERMISSION_JSON}" | jq -r '.IpProtocol')
        FROM_PORT=$(echo "${IP_PERMISSION_JSON}" | jq -r '.FromPort')
        TO_PORT=$(echo "${IP_PERMISSION_JSON}" | jq -r '.ToPort')

        # Check IPv4 rules
        echo "${IP_PERMISSION_JSON}" | jq -c '.IpRanges[]' | while read IP_RANGE_JSON; do
            CIDR_IP=$(echo "${IP_RANGE_JSON}" | jq -r '.CidrIp')
            if [ "${CIDR_IP}" = "0.0.0.0/0" ]; then
                IS_SENSITIVE="false"
                if [ "${PROTOCOL}" = "tcp" ] || [ "${PROTOCOL}" = "-1" ]; then # -1 means all protocols
                    if [ -n "${FROM_PORT}" ] && [ -n "${TO_PORT}" ]; then
                        for (( P = FROM_PORT; P <= TO_PORT; P++ )); do
                            if [ -n "${SENSITIVE_PORTS_MAP["$P"]}" ]; then
                                IS_SENSITIVE="true"
                                break
                            fi
                        done
                    elif [ -n "${FROM_PORT}" ] && [ -n "${SENSITIVE_PORTS_MAP["${FROM_PORT}"]}" ]; then
                        IS_SENSITIVE="true"
                    fi
                fi
                PUBLIC_ACCESS_SGS+=("{ \"GroupId\": \"${SG_ID}\", \"GroupName\": \"${SG_NAME}\", \"VpcId\": \"${VPC_ID}\", \"Protocol\": \"${PROTOCOL}\", \"PortRange\": \"${FROM_PORT}-${TO_PORT}\", \"Source\": \"${CIDR_IP}\", \"IsSensitivePort\": ${IS_SENSITIVE} }")
            fi
        done

        # Check IPv6 rules
        echo "${IP_PERMISSION_JSON}" | jq -c '.Ipv6Ranges[]' | while read IPV6_RANGE_JSON; do
            CIDR_IPV6=$(echo "${IPV6_RANGE_JSON}" | jq -r '.CidrIpv6')
            if [ "${CIDR_IPV6}" = "::/0" ]; then
                IS_SENSITIVE="false"
                if [ "${PROTOCOL}" = "tcp" ] || [ "${PROTOCOL}" = "-1" ]; then
                    if [ -n "${FROM_PORT}" ] && [ -n "${TO_PORT}" ]; then
                        for (( P = FROM_PORT; P <= TO_PORT; P++ )); do
                            if [ -n "${SENSITIVE_PORTS_MAP["$P"]}" ]; then
                                IS_SENSITIVE="true"
                                break
                            fi
                        done
                    elif [ -n "${FROM_PORT}" ] && [ -n "${SENSITIVE_PORTS_MAP["${FROM_PORT}"]}" ]; then
                        IS_SENSITIVE="true"
                    fi
                fi
                PUBLIC_ACCESS_SGS+=("{ \"GroupId\": \"${SG_ID}\", \"GroupName\": \"${SG_NAME}\", \"VpcId\": \"${VPC_ID}\", \"Protocol\": \"${PROTOCOL}\", \"PortRange\": \"${FROM_PORT}-${TO_PORT}\", \"Source\": \"${CIDR_IPV6}\", \"IsSensitivePort\": ${IS_SENSITIVE} }")
            fi
        done
    done
done

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

if [ ${#PUBLIC_ACCESS_SGS[@]} -gt 0 ]; then
    echo "--- Security Groups with Public Access ---"
    for SG_INFO_JSON in "${PUBLIC_ACCESS_SGS[@]}"; do
        SG_ID=$(echo "${SG_INFO_JSON}" | jq -r '.GroupId')
        SG_NAME=$(echo "${SG_INFO_JSON}" | jq -r '.GroupName')
        VPC_ID=$(echo "${SG_INFO_JSON}" | jq -r '.VpcId')
        PROTOCOL=$(echo "${SG_INFO_JSON}" | jq -r '.Protocol')
        PORT_RANGE=$(echo "${SG_INFO_JSON}" | jq -r '.PortRange')
        SOURCE=$(echo "${SG_INFO_JSON}" | jq -r '.Source')
        IS_SENSITIVE=$(echo "${SG_INFO_JSON}" | jq -r '.IsSensitivePort')

        SENSITIVE_WARNING=""
        if [ "${IS_SENSITIVE}" = "true" ]; then
            SENSITIVE_WARNING=" (SENSITIVE PORT!)"
        fi

        echo "  SG ID: ${SG_ID}"
        echo "  SG Name: ${SG_NAME}"
        echo "  VPC ID: ${VPC_ID}"
        echo "  Rule: Protocol=${PROTOCOL}, Port=${PORT_RANGE}, Source=${SOURCE}${SENSITIVE_WARNING}"
        echo "----------------------------------------"
    done
else
    echo "No security groups found with public access to sensitive ports."
fi

echo "\n=== Security Group scan completed. ===\n"
