a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
636 lines
26 KiB
Bash
636 lines
26 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
#########################################################################################
|
|
#### gcp-firewall-auditor.sh — Audit GCP VPC firewall rules for risky configs ####
|
|
#### Finds 0.0.0.0/0 rules, dangerous ports, overly permissive access, unused rules ####
|
|
#### Requires: bash 4+, gcloud CLI, jq ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.01 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### ./gcp-firewall-auditor.sh --full ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
#########################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Colors (pre-initialized) ─────────────────────────────────────────
|
|
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
|
|
|
setup_colors() {
|
|
if [[ "${COLOR:-auto}" == "never" ]]; then
|
|
return
|
|
fi
|
|
if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
RESET='\033[0m'
|
|
fi
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────
|
|
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
|
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
|
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
|
|
die() { err "$*"; exit 1; }
|
|
|
|
# ── Severity counters ────────────────────────────────────────────────
|
|
TOTAL_CRIT=0
|
|
TOTAL_WARN=0
|
|
TOTAL_INFO=0
|
|
TOTAL_OK=0
|
|
|
|
flag_crit() { ((TOTAL_CRIT++)) || true; }
|
|
flag_warn() { ((TOTAL_WARN++)) || true; }
|
|
flag_info() { ((TOTAL_INFO++)) || true; }
|
|
flag_ok() { ((TOTAL_OK++)) || true; }
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
RUN_MODE=""
|
|
DANGEROUS_PORTS="${DANGEROUS_PORTS:-22,3389,3306,5432,1433,6379,27017,9200,8080,8443}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
COLOR="${COLOR:-auto}"
|
|
GCP_PROJECT=""
|
|
VPC_NETWORK=""
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
START_TIME=""
|
|
|
|
# ── Dependency and credential checks ────────────────────────────────
|
|
check_deps() {
|
|
command -v gcloud &>/dev/null || die "gcloud CLI is required (install: https://cloud.google.com/sdk/docs/install)"
|
|
command -v jq &>/dev/null || die "jq is required"
|
|
}
|
|
|
|
check_credentials() {
|
|
local account
|
|
account=$(gcloud auth list --filter="status:ACTIVE" --format="value(account)" 2>/dev/null)
|
|
[[ -z "$account" ]] && die "No active gcloud credentials — run 'gcloud auth login'"
|
|
|
|
if [[ -n "$GCP_PROJECT" ]]; then
|
|
gcloud config set project "$GCP_PROJECT" --quiet 2>/dev/null \
|
|
|| die "Cannot set project: ${GCP_PROJECT}"
|
|
else
|
|
GCP_PROJECT=$(gcloud config get-value project 2>/dev/null)
|
|
[[ -z "$GCP_PROJECT" || "$GCP_PROJECT" == "(unset)" ]] && die "No project set — use --project or 'gcloud config set project'"
|
|
fi
|
|
|
|
verbose "Account: ${account}"
|
|
log "Project: ${GCP_PROJECT}"
|
|
}
|
|
|
|
# ── gcloud wrapper ───────────────────────────────────────────────────
|
|
gc_cmd() {
|
|
local args=("$@")
|
|
[[ -n "$GCP_PROJECT" ]] && args+=(--project "$GCP_PROJECT")
|
|
verbose "gcloud ${args[*]}"
|
|
gcloud "${args[@]}"
|
|
}
|
|
|
|
# ── Port-to-service mapping ─────────────────────────────────────────
|
|
port_to_service() {
|
|
local port="$1"
|
|
case "$port" in
|
|
22) echo "SSH" ;;
|
|
80) echo "HTTP" ;;
|
|
443) echo "HTTPS" ;;
|
|
3306) echo "MySQL" ;;
|
|
5432) echo "PostgreSQL" ;;
|
|
1433) echo "MSSQL" ;;
|
|
3389) echo "RDP" ;;
|
|
6379) echo "Redis" ;;
|
|
27017) echo "MongoDB" ;;
|
|
9200) echo "Elasticsearch" ;;
|
|
8080) echo "HTTP-Alt" ;;
|
|
8443) echo "HTTPS-Alt" ;;
|
|
53) echo "DNS" ;;
|
|
25) echo "SMTP" ;;
|
|
5900) echo "VNC" ;;
|
|
11211) echo "Memcached" ;;
|
|
2379) echo "etcd" ;;
|
|
9090) echo "Prometheus" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
|
|
# ── Check if port is in dangerous list ───────────────────────────────
|
|
is_dangerous_port() {
|
|
local port="$1"
|
|
local IFS=','
|
|
for dp in $DANGEROUS_PORTS; do
|
|
if [[ "$port" == "$dp" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ── Check if port falls in a range ───────────────────────────────────
|
|
port_in_range() {
|
|
local port="$1" range="$2"
|
|
if [[ "$range" == *-* ]]; then
|
|
local start="${range%-*}"
|
|
local end="${range#*-}"
|
|
[[ "$port" -ge "$start" && "$port" -le "$end" ]]
|
|
else
|
|
[[ "$port" == "$range" ]]
|
|
fi
|
|
}
|
|
|
|
# ── Fetch firewall rules ────────────────────────────────────────────
|
|
fetch_rules() {
|
|
local args=(compute firewall-rules list --format=json)
|
|
if [[ -n "$VPC_NETWORK" ]]; then
|
|
args+=(--filter="network~${VPC_NETWORK}")
|
|
fi
|
|
gc_cmd "${args[@]}" 2>/dev/null
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# OPEN PORTS AUDIT
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
audit_open_ports() {
|
|
log "Auditing firewall rules for dangerous open ports..."
|
|
log "Dangerous ports: ${DANGEROUS_PORTS}"
|
|
echo ""
|
|
|
|
printf " %-28s %-14s %-8s %-8s %-18s %s\n" \
|
|
"RULE_NAME" "NETWORK" "PROTO" "PORT" "SOURCE" "SEVERITY"
|
|
printf " %s\n" "$(printf '%.0s─' {1..95})"
|
|
|
|
local rules_json
|
|
rules_json=$(fetch_rules)
|
|
|
|
echo "$rules_json" | jq -c '.[] | select(.direction == "INGRESS" and .disabled != true)' 2>/dev/null | while IFS= read -r rule; do
|
|
local rule_name network
|
|
rule_name=$(echo "$rule" | jq -r '.name')
|
|
network=$(echo "$rule" | jq -r '.network' | rev | cut -d/ -f1 | rev)
|
|
|
|
local has_open="false"
|
|
while IFS= read -r src; do
|
|
if [[ "$src" == "0.0.0.0/0" ]]; then
|
|
has_open="true"
|
|
break
|
|
fi
|
|
done < <(echo "$rule" | jq -r '.sourceRanges[]? // empty' 2>/dev/null)
|
|
|
|
[[ "$has_open" != "true" ]] && continue
|
|
|
|
echo "$rule" | jq -c '.allowed[]? // empty' 2>/dev/null | while IFS= read -r allowed; do
|
|
local protocol
|
|
protocol=$(echo "$allowed" | jq -r '.IPProtocol')
|
|
|
|
local ports
|
|
ports=$(echo "$allowed" | jq -r '.ports[]? // empty' 2>/dev/null)
|
|
|
|
if [[ -z "$ports" ]]; then
|
|
if [[ "$protocol" == "all" ]]; then
|
|
printf " %-28s %-14s %-8s %-8s %-18s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "all" "all" \
|
|
"0.0.0.0/0" "$RED" "CRITICAL" "$RESET"
|
|
flag_crit
|
|
else
|
|
local IFS=','
|
|
for dp in $DANGEROUS_PORTS; do
|
|
local svc
|
|
svc=$(port_to_service "$dp")
|
|
printf " %-28s %-14s %-8s %-8s %-18s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "$protocol" "$dp" \
|
|
"0.0.0.0/0" "$RED" "CRITICAL" "$RESET"
|
|
flag_crit
|
|
done
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
while IFS= read -r port_spec; do
|
|
[[ -z "$port_spec" ]] && continue
|
|
|
|
local IFS=','
|
|
for dp in $DANGEROUS_PORTS; do
|
|
if port_in_range "$dp" "$port_spec"; then
|
|
local svc severity color
|
|
svc=$(port_to_service "$dp")
|
|
if [[ "$dp" == "80" || "$dp" == "443" ]]; then
|
|
severity="INFO"; color="$CYAN"; flag_info
|
|
else
|
|
severity="CRITICAL"; color="$RED"; flag_crit
|
|
fi
|
|
printf " %-28s %-14s %-8s %-8s %-18s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "$protocol" \
|
|
"${dp} (${svc})" "0.0.0.0/0" "$color" "$severity" "$RESET"
|
|
fi
|
|
done
|
|
done <<< "$ports"
|
|
done
|
|
done
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# PERMISSIVE RULES AUDIT
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
audit_permissive() {
|
|
log "Auditing overly permissive firewall rules..."
|
|
echo ""
|
|
|
|
printf " %-28s %-14s %-14s %-18s %s\n" \
|
|
"RULE_NAME" "NETWORK" "PROTOCOLS" "SOURCE" "SEVERITY"
|
|
printf " %s\n" "$(printf '%.0s─' {1..85})"
|
|
|
|
local rules_json
|
|
rules_json=$(fetch_rules)
|
|
|
|
echo "$rules_json" | jq -c '.[] | select(.direction == "INGRESS" and .disabled != true)' 2>/dev/null | while IFS= read -r rule; do
|
|
local rule_name network
|
|
rule_name=$(echo "$rule" | jq -r '.name')
|
|
network=$(echo "$rule" | jq -r '.network' | rev | cut -d/ -f1 | rev)
|
|
|
|
local has_open="false"
|
|
while IFS= read -r src; do
|
|
if [[ "$src" == "0.0.0.0/0" ]]; then
|
|
has_open="true"
|
|
break
|
|
fi
|
|
done < <(echo "$rule" | jq -r '.sourceRanges[]? // empty' 2>/dev/null)
|
|
|
|
[[ "$has_open" != "true" ]] && continue
|
|
|
|
local has_all_traffic="false"
|
|
while IFS= read -r allowed; do
|
|
local proto
|
|
proto=$(echo "$allowed" | jq -r '.IPProtocol')
|
|
local port_count
|
|
port_count=$(echo "$allowed" | jq '.ports // [] | length')
|
|
|
|
if [[ "$proto" == "all" ]]; then
|
|
has_all_traffic="true"
|
|
elif [[ "$port_count" -eq 0 ]]; then
|
|
has_all_traffic="true"
|
|
fi
|
|
done < <(echo "$rule" | jq -c '.allowed[]? // empty' 2>/dev/null)
|
|
|
|
if [[ "$has_all_traffic" == "true" ]]; then
|
|
local proto_list
|
|
proto_list=$(echo "$rule" | jq -r '[.allowed[]?.IPProtocol] | join(",")' 2>/dev/null)
|
|
printf " %-28s %-14s %-14s %-18s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "${proto_list:0:13}" \
|
|
"0.0.0.0/0" "$RED" "CRITICAL" "$RESET"
|
|
flag_crit
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# EGRESS AUDIT
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
audit_egress() {
|
|
log "Auditing egress firewall rules..."
|
|
echo ""
|
|
|
|
printf " %-28s %-14s %-14s %-18s %s\n" \
|
|
"RULE_NAME" "NETWORK" "PROTOCOLS" "DESTINATION" "SEVERITY"
|
|
printf " %s\n" "$(printf '%.0s─' {1..85})"
|
|
|
|
local rules_json
|
|
rules_json=$(fetch_rules)
|
|
|
|
echo "$rules_json" | jq -c '.[] | select(.direction == "EGRESS" and .disabled != true)' 2>/dev/null | while IFS= read -r rule; do
|
|
local rule_name network
|
|
rule_name=$(echo "$rule" | jq -r '.name')
|
|
network=$(echo "$rule" | jq -r '.network' | rev | cut -d/ -f1 | rev)
|
|
|
|
local has_wide="false"
|
|
while IFS= read -r dest; do
|
|
if [[ "$dest" == "0.0.0.0/0" ]]; then
|
|
has_wide="true"
|
|
break
|
|
fi
|
|
done < <(echo "$rule" | jq -r '.destinationRanges[]? // empty' 2>/dev/null)
|
|
|
|
[[ "$has_wide" != "true" ]] && continue
|
|
|
|
local proto_list
|
|
proto_list=$(echo "$rule" | jq -r '[.allowed[]?.IPProtocol] | join(",")' 2>/dev/null)
|
|
|
|
local severity="WARN" color="$YELLOW"
|
|
if [[ "$proto_list" == "all" ]]; then
|
|
severity="WARN"; color="$YELLOW"; flag_warn
|
|
else
|
|
severity="INFO"; color="$CYAN"; flag_info
|
|
fi
|
|
|
|
printf " %-28s %-14s %-14s %-18s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "${proto_list:0:13}" \
|
|
"0.0.0.0/0" "$color" "$severity" "$RESET"
|
|
done
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# UNUSED RULES AUDIT
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
audit_unused() {
|
|
log "Checking for disabled or potentially unused firewall rules..."
|
|
echo ""
|
|
|
|
printf " %-28s %-14s %-10s %-10s %s\n" \
|
|
"RULE_NAME" "NETWORK" "DIRECTION" "DISABLED" "SEVERITY"
|
|
printf " %s\n" "$(printf '%.0s─' {1..80})"
|
|
|
|
local rules_json
|
|
rules_json=$(fetch_rules)
|
|
|
|
echo "$rules_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r rule; do
|
|
local rule_name network direction disabled
|
|
rule_name=$(echo "$rule" | jq -r '.name')
|
|
network=$(echo "$rule" | jq -r '.network' | rev | cut -d/ -f1 | rev)
|
|
direction=$(echo "$rule" | jq -r '.direction')
|
|
disabled=$(echo "$rule" | jq -r '.disabled // false')
|
|
|
|
if [[ "$disabled" == "true" ]]; then
|
|
printf " %-28s %-14s %-10s %-10s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "$direction" "YES" \
|
|
"$YELLOW" "WARN — disabled" "$RESET"
|
|
flag_warn
|
|
continue
|
|
fi
|
|
|
|
local target_tags
|
|
target_tags=$(echo "$rule" | jq -r '.targetTags // [] | join(",")' 2>/dev/null)
|
|
|
|
if [[ -n "$target_tags" && "$target_tags" != "null" ]]; then
|
|
local first_tag="${target_tags%%,*}"
|
|
local instance_count
|
|
instance_count=$(gcloud compute instances list \
|
|
--filter="tags.items=${first_tag}" \
|
|
--format="value(name)" 2>/dev/null | wc -l)
|
|
|
|
if [[ "$instance_count" -eq 0 ]]; then
|
|
printf " %-28s %-14s %-10s %-10s %b%s%b\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "$direction" "NO" \
|
|
"$YELLOW" "WARN — no targets" "$RESET"
|
|
flag_warn
|
|
else
|
|
verbose "Rule ${rule_name}: ${instance_count} matching instance(s)"
|
|
flag_ok
|
|
fi
|
|
else
|
|
flag_ok
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# LIST ALL RULES
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
list_rules() {
|
|
log "Listing all firewall rules..."
|
|
echo ""
|
|
|
|
printf " %-28s %-14s %-10s %-8s %-12s %-18s %s\n" \
|
|
"RULE_NAME" "NETWORK" "DIR" "PROTO" "PORTS" "SOURCE/DEST" "PRIORITY"
|
|
printf " %s\n" "$(printf '%.0s─' {1..105})"
|
|
|
|
local rules_json
|
|
rules_json=$(fetch_rules)
|
|
|
|
echo "$rules_json" | jq -c '.[]' 2>/dev/null | while IFS= read -r rule; do
|
|
local rule_name network direction priority
|
|
rule_name=$(echo "$rule" | jq -r '.name')
|
|
network=$(echo "$rule" | jq -r '.network' | rev | cut -d/ -f1 | rev)
|
|
direction=$(echo "$rule" | jq -r '.direction')
|
|
priority=$(echo "$rule" | jq -r '.priority')
|
|
|
|
local cidr_list
|
|
if [[ "$direction" == "INGRESS" ]]; then
|
|
cidr_list=$(echo "$rule" | jq -r '.sourceRanges[0]? // "any"' 2>/dev/null)
|
|
else
|
|
cidr_list=$(echo "$rule" | jq -r '.destinationRanges[0]? // "any"' 2>/dev/null)
|
|
fi
|
|
|
|
echo "$rule" | jq -c '.allowed[]? // empty' 2>/dev/null | while IFS= read -r allowed; do
|
|
local proto port_str
|
|
proto=$(echo "$allowed" | jq -r '.IPProtocol')
|
|
port_str=$(echo "$allowed" | jq -r '.ports // ["all"] | join(",")' 2>/dev/null)
|
|
[[ "$port_str" == "null" ]] && port_str="all"
|
|
|
|
local dir_color="$CYAN"
|
|
[[ "$direction" == "EGRESS" ]] && dir_color="$YELLOW"
|
|
|
|
printf " %-28s %-14s %b%-10s%b %-8s %-12s %-18s %s\n" \
|
|
"${rule_name:0:27}" "${network:0:13}" "$dir_color" "$direction" "$RESET" \
|
|
"$proto" "${port_str:0:11}" "${cidr_list:0:17}" "$priority"
|
|
done
|
|
done
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# SUMMARY
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
print_summary() {
|
|
local elapsed
|
|
elapsed=$(( $(date +%s) - START_TIME ))
|
|
|
|
echo ""
|
|
echo " ══════════════════════════════════════════"
|
|
echo " Firewall Audit Summary"
|
|
echo " ══════════════════════════════════════════"
|
|
printf " %-20s %b%d%b\n" "CRITICAL:" "$RED" "$TOTAL_CRIT" "$RESET"
|
|
printf " %-20s %b%d%b\n" "WARN:" "$YELLOW" "$TOTAL_WARN" "$RESET"
|
|
printf " %-20s %b%d%b\n" "INFO:" "$CYAN" "$TOTAL_INFO" "$RESET"
|
|
printf " %-20s %b%d%b\n" "OK:" "$GREEN" "$TOTAL_OK" "$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 " • Close 0.0.0.0/0 rules on SSH (22), RDP (3389), and database ports"
|
|
echo " • Replace all-protocol allow rules with specific port lists"
|
|
echo " • Use target tags or service accounts to scope rules"
|
|
echo " • Delete disabled rules that are no longer needed"
|
|
echo ""
|
|
elif [[ "$TOTAL_WARN" -gt 0 ]]; then
|
|
echo -e " ${YELLOW}Review recommended:${RESET} ${TOTAL_WARN} warning(s)"
|
|
echo ""
|
|
echo " Suggestions:"
|
|
echo " • Review disabled rules for deletion"
|
|
echo " • Check rules with no matching target instances"
|
|
echo " • Restrict egress where applicable"
|
|
echo ""
|
|
else
|
|
echo -e " ${GREEN}All checks passed${RESET}"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# USAGE
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
show_help() {
|
|
cat <<EOF
|
|
${BOLD}${SCRIPT_NAME}${RESET} — GCP Firewall Auditor
|
|
|
|
Audit GCP VPC firewall rules across all networks for risky
|
|
configurations via gcloud CLI.
|
|
|
|
${BOLD}MODES${RESET}
|
|
--full Run all audits
|
|
--open-ports Find dangerous ports open to 0.0.0.0/0
|
|
--permissive Find overly broad allow rules
|
|
--unused Find disabled or targetless rules
|
|
--egress Audit wide egress rules
|
|
--rules List all firewall rules
|
|
|
|
${BOLD}OPTIONS${RESET}
|
|
--project PROJECT GCP project ID
|
|
--network NETWORK Limit to specific VPC network
|
|
--ports PORTS Override dangerous ports (comma-separated)
|
|
--verbose Debug output
|
|
--no-color Disable colored output
|
|
--help Show this help message
|
|
|
|
${BOLD}ENVIRONMENT VARIABLES${RESET}
|
|
DANGEROUS_PORTS Comma-separated ports to flag (default: 22,3389,...)
|
|
VERBOSE Enable verbose output (true/false)
|
|
COLOR Color mode: auto, always, never
|
|
|
|
${BOLD}EXAMPLES${RESET}
|
|
# Full audit
|
|
${SCRIPT_NAME} --full
|
|
|
|
# Check open ports only
|
|
${SCRIPT_NAME} --open-ports
|
|
|
|
# Audit a specific project
|
|
${SCRIPT_NAME} --full --project my-prod-project
|
|
|
|
# Filter by VPC network
|
|
${SCRIPT_NAME} --open-ports --network prod-vpc
|
|
|
|
# Custom dangerous ports
|
|
${SCRIPT_NAME} --open-ports --ports "22,3389,5432,6379"
|
|
|
|
# List all firewall rules
|
|
${SCRIPT_NAME} --rules
|
|
|
|
${BOLD}EXIT CODES${RESET}
|
|
0 All checks passed
|
|
1 Warnings found (review recommended)
|
|
2 Critical findings (action required)
|
|
EOF
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# PARSE ARGS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
parse_args() {
|
|
local modes=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--full)
|
|
modes=(open-ports permissive unused egress)
|
|
shift ;;
|
|
--open-ports)
|
|
modes+=(open-ports); shift ;;
|
|
--permissive)
|
|
modes+=(permissive); shift ;;
|
|
--unused)
|
|
modes+=(unused); shift ;;
|
|
--egress)
|
|
modes+=(egress); shift ;;
|
|
--rules)
|
|
modes+=(rules); shift ;;
|
|
--project)
|
|
GCP_PROJECT="${2:?--project requires a value}"; shift 2 ;;
|
|
--network)
|
|
VPC_NETWORK="${2:?--network requires a value}"; shift 2 ;;
|
|
--ports)
|
|
DANGEROUS_PORTS="${2:?--ports requires a value}"; shift 2 ;;
|
|
--verbose)
|
|
VERBOSE="true"; shift ;;
|
|
--no-color)
|
|
COLOR="never"; shift ;;
|
|
--help|-h)
|
|
setup_colors; show_help; exit 0 ;;
|
|
*)
|
|
die "Unknown option: $1 (see --help)" ;;
|
|
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
|
|
check_credentials
|
|
|
|
START_TIME=$(date +%s)
|
|
|
|
echo ""
|
|
echo -e "${BOLD}GCP Firewall Auditor${RESET}"
|
|
echo -e "Project: ${GCP_PROJECT}"
|
|
echo -e "Mode: ${RUN_MODE}"
|
|
if [[ -n "$VPC_NETWORK" ]]; then
|
|
echo -e "Network: ${VPC_NETWORK}"
|
|
fi
|
|
echo -e "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo ""
|
|
|
|
for mode in $RUN_MODE; do
|
|
case "$mode" in
|
|
open-ports) audit_open_ports ;;
|
|
permissive) audit_permissive ;;
|
|
unused) audit_unused ;;
|
|
egress) audit_egress ;;
|
|
rules) list_rules ;;
|
|
esac
|
|
done
|
|
|
|
print_summary
|
|
|
|
if [[ "$TOTAL_CRIT" -gt 0 ]]; then
|
|
exit 2
|
|
elif [[ "$TOTAL_WARN" -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
main "$@"
|