Sync all scripts from website downloads — 352 scripts total
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.
This commit is contained in:
Executable
+614
@@ -0,0 +1,614 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### bastion-hardener.sh — Harden SSH bastion/jump hosts with audit and rollback ####
|
||||
#### Disables password auth, restricts ciphers, sets idle timeout, fail2ban config ####
|
||||
#### Requires: bash 4+, root privileges ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.01 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### sudo ./bastion-hardener.sh --audit ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
# v1.01 changes:
|
||||
# - Fixed: ((0++)) returns 1 under set -e; added || true guards
|
||||
# - Fixed: grep in pipeline crashes under set -euo pipefail when no matches found. Added || true guard
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
RUN_MODE=""
|
||||
SSHD_CONFIG="${SSHD_CONFIG:-/etc/ssh/sshd_config}"
|
||||
BACKUP_ROOT="${BACKUP_ROOT:-/etc/ssh}"
|
||||
ALLOW_USERS="${ALLOW_USERS:-}"
|
||||
ALLOW_GROUPS="${ALLOW_GROUPS:-}"
|
||||
IDLE_TIMEOUT="${IDLE_TIMEOUT:-300}"
|
||||
MAX_AUTH_TRIES="${MAX_AUTH_TRIES:-3}"
|
||||
MAX_SESSIONS="${MAX_SESSIONS:-2}"
|
||||
SESSION_LOG_DIR="${SESSION_LOG_DIR:-/var/log/bastion-sessions}"
|
||||
FAIL2BAN_BANTIME="${FAIL2BAN_BANTIME:-3600}"
|
||||
FAIL2BAN_MAXRETRY="${FAIL2BAN_MAXRETRY:-3}"
|
||||
DRY_RUN="${DRY_RUN:-false}"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
ENABLE_SESSION_LOGGING="false"
|
||||
CONFIGURE_FAIL2BAN="false"
|
||||
ROLLBACK_DIR=""
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
START_TIME=""
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
WARN_COUNT=0
|
||||
CHANGES=0
|
||||
|
||||
# ── Hardening settings ───────────────────────────────────────────────
|
||||
readonly RECOMMENDED_CIPHERS="chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com"
|
||||
readonly RECOMMENDED_MACS="hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com"
|
||||
readonly RECOMMENDED_KEX="curve25519-sha256,curve25519-sha256@libssh.org"
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" 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'
|
||||
# shellcheck disable=SC2034
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
# shellcheck disable=SC2034
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
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; }
|
||||
|
||||
elapsed() {
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
echo "$(( end_time - START_TIME ))s"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
die "This operation requires root privileges. Run with sudo."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── SSHD config helpers ──────────────────────────────────────────────
|
||||
get_sshd_setting() {
|
||||
local key="$1"
|
||||
local val
|
||||
val=$({ grep -i "^[[:space:]]*${key}[[:space:]]" "$SSHD_CONFIG" 2>/dev/null || true; } | tail -1 | awk '{print $2}')
|
||||
if [[ -z "$val" ]]; then
|
||||
echo "(not set)"
|
||||
else
|
||||
echo "$val"
|
||||
fi
|
||||
}
|
||||
|
||||
set_sshd_config() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$3"
|
||||
|
||||
if grep -qi "^[[:space:]]*${key}[[:space:]]" "$file" 2>/dev/null; then
|
||||
sed -i "s|^[[:space:]]*${key}[[:space:]].*|${key} ${value}|i" "$file"
|
||||
elif grep -qi "^[[:space:]]*#[[:space:]]*${key}[[:space:]]" "$file" 2>/dev/null; then
|
||||
sed -i "s|^[[:space:]]*#[[:space:]]*${key}[[:space:]].*|${key} ${value}|i" "$file"
|
||||
else
|
||||
echo "${key} ${value}" >> "$file"
|
||||
fi
|
||||
verbose "Set ${key} = ${value}"
|
||||
}
|
||||
|
||||
# ── Audit check helper ───────────────────────────────────────────────
|
||||
check_setting() {
|
||||
local name="$1"
|
||||
local current="$2"
|
||||
local recommended="$3"
|
||||
local is_warn="${4:-false}"
|
||||
|
||||
local status_icon
|
||||
if [[ "${current,,}" == "${recommended,,}" ]]; then
|
||||
status_icon="${GREEN}✓ PASS${RESET}"
|
||||
((PASS_COUNT++)) || true
|
||||
elif [[ "$is_warn" == "true" ]]; then
|
||||
status_icon="${YELLOW}! WARN${RESET}"
|
||||
((WARN_COUNT++)) || true
|
||||
else
|
||||
status_icon="${RED}✗ FAIL${RESET}"
|
||||
((FAIL_COUNT++)) || true
|
||||
fi
|
||||
|
||||
printf " %-34s %-16s %-16s %b\n" "$name" "$current" "$recommended" "$status_icon"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# AUDIT MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_audit() {
|
||||
if [[ ! -f "$SSHD_CONFIG" ]]; then
|
||||
die "sshd_config not found at ${SSHD_CONFIG}"
|
||||
fi
|
||||
|
||||
log "Auditing SSH configuration..."
|
||||
echo ""
|
||||
echo -e " ${BOLD}SSH Configuration Audit${RESET}"
|
||||
printf " ${BOLD}%-34s %-16s %-16s %s${RESET}\n" "SETTING" "CURRENT" "RECOMMENDED" "STATUS"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..78})"
|
||||
|
||||
# Core auth settings
|
||||
check_setting "PermitRootLogin" "$(get_sshd_setting PermitRootLogin)" "no"
|
||||
check_setting "PasswordAuthentication" "$(get_sshd_setting PasswordAuthentication)" "no"
|
||||
check_setting "ChallengeResponseAuthentication" "$(get_sshd_setting ChallengeResponseAuthentication)" "no"
|
||||
check_setting "PubkeyAuthentication" "$(get_sshd_setting PubkeyAuthentication)" "yes"
|
||||
|
||||
# Limits
|
||||
check_setting "MaxAuthTries" "$(get_sshd_setting MaxAuthTries)" "$MAX_AUTH_TRIES"
|
||||
check_setting "MaxSessions" "$(get_sshd_setting MaxSessions)" "$MAX_SESSIONS"
|
||||
|
||||
# Timeouts
|
||||
check_setting "ClientAliveInterval" "$(get_sshd_setting ClientAliveInterval)" "$IDLE_TIMEOUT"
|
||||
|
||||
local cac_current
|
||||
cac_current=$(get_sshd_setting ClientAliveCountMax)
|
||||
if [[ "$cac_current" == "3" ]]; then
|
||||
check_setting "ClientAliveCountMax" "$cac_current" "2" "true"
|
||||
else
|
||||
check_setting "ClientAliveCountMax" "$cac_current" "2"
|
||||
fi
|
||||
|
||||
# Forwarding
|
||||
check_setting "X11Forwarding" "$(get_sshd_setting X11Forwarding)" "no"
|
||||
check_setting "AllowTcpForwarding" "$(get_sshd_setting AllowTcpForwarding)" "no"
|
||||
check_setting "AllowAgentForwarding" "$(get_sshd_setting AllowAgentForwarding)" "no"
|
||||
check_setting "PermitTunnel" "$(get_sshd_setting PermitTunnel)" "no"
|
||||
|
||||
# Crypto
|
||||
local ciphers_current
|
||||
ciphers_current=$(get_sshd_setting Ciphers)
|
||||
if [[ "$ciphers_current" == "(not set)" ]]; then
|
||||
check_setting "Ciphers" "(default)" "(restricted)"
|
||||
elif [[ "$ciphers_current" == "$RECOMMENDED_CIPHERS" ]]; then
|
||||
check_setting "Ciphers" "(restricted)" "(restricted)"
|
||||
else
|
||||
check_setting "Ciphers" "(custom)" "(restricted)"
|
||||
fi
|
||||
|
||||
local macs_current
|
||||
macs_current=$(get_sshd_setting MACs)
|
||||
if [[ "$macs_current" == "(not set)" ]]; then
|
||||
check_setting "MACs" "(default)" "(restricted)"
|
||||
elif [[ "$macs_current" == "$RECOMMENDED_MACS" ]]; then
|
||||
check_setting "MACs" "(restricted)" "(restricted)"
|
||||
else
|
||||
check_setting "MACs" "(custom)" "(restricted)"
|
||||
fi
|
||||
|
||||
local kex_current
|
||||
kex_current=$(get_sshd_setting KexAlgorithms)
|
||||
if [[ "$kex_current" == "(not set)" ]]; then
|
||||
check_setting "KexAlgorithms" "(default)" "(restricted)"
|
||||
elif [[ "$kex_current" == "$RECOMMENDED_KEX" ]]; then
|
||||
check_setting "KexAlgorithms" "(restricted)" "(restricted)"
|
||||
else
|
||||
check_setting "KexAlgorithms" "(custom)" "(restricted)"
|
||||
fi
|
||||
|
||||
# Logging and misc
|
||||
check_setting "LogLevel" "$(get_sshd_setting LogLevel)" "VERBOSE"
|
||||
check_setting "LoginGraceTime" "$(get_sshd_setting LoginGraceTime)" "30"
|
||||
|
||||
# AllowUsers / AllowGroups (warn if not set)
|
||||
local au_current ag_current
|
||||
au_current=$(get_sshd_setting AllowUsers)
|
||||
ag_current=$(get_sshd_setting AllowGroups)
|
||||
if [[ "$au_current" == "(not set)" ]]; then
|
||||
check_setting "AllowUsers" "(not set)" "(recommended)" "true"
|
||||
else
|
||||
check_setting "AllowUsers" "(configured)" "(recommended)"
|
||||
fi
|
||||
if [[ "$ag_current" == "(not set)" ]]; then
|
||||
check_setting "AllowGroups" "(not set)" "(recommended)" "true"
|
||||
else
|
||||
check_setting "AllowGroups" "(configured)" "(recommended)"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
local total_checks=$((PASS_COUNT + FAIL_COUNT + WARN_COUNT))
|
||||
local score=0
|
||||
if [[ "$total_checks" -gt 0 ]]; then
|
||||
score=$(( PASS_COUNT * 100 / total_checks ))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e " ${BOLD}Summary${RESET}"
|
||||
echo " Total checks: ${total_checks}"
|
||||
echo -e " Passed: ${GREEN}${PASS_COUNT}${RESET}"
|
||||
echo -e " Failed: ${RED}${FAIL_COUNT}${RESET}"
|
||||
echo -e " Warnings: ${YELLOW}${WARN_COUNT}${RESET}"
|
||||
echo " Score: ${score} / 100"
|
||||
|
||||
# Extra warnings
|
||||
echo ""
|
||||
if ! command -v fail2ban-client &>/dev/null; then
|
||||
warn "Fail2ban not installed — brute-force protection unavailable"
|
||||
fi
|
||||
if [[ "$au_current" == "(not set)" && "$ag_current" == "(not set)" ]]; then
|
||||
warn "No AllowUsers/AllowGroups configured — all users can SSH in"
|
||||
fi
|
||||
|
||||
log "Run with --apply to harden this host"
|
||||
log "Completed in $(elapsed)"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# APPLY MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_apply() {
|
||||
require_root
|
||||
|
||||
if [[ ! -f "$SSHD_CONFIG" ]]; then
|
||||
die "sshd_config not found at ${SSHD_CONFIG}"
|
||||
fi
|
||||
|
||||
# Create backup
|
||||
local backup_dir
|
||||
backup_dir="${BACKUP_ROOT}/bastion-hardener-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
log "Backing up ${SSHD_CONFIG} → ${backup_dir}/sshd_config"
|
||||
mkdir -p "$backup_dir"
|
||||
cp -p "$SSHD_CONFIG" "${backup_dir}/sshd_config"
|
||||
[[ -f /etc/ssh/banner.txt ]] && cp -p /etc/ssh/banner.txt "${backup_dir}/banner.txt"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
log "${YELLOW}DRY RUN${RESET} — previewing changes (no files will be modified)"
|
||||
local tmp_config
|
||||
tmp_config=$(mktemp)
|
||||
cp "$SSHD_CONFIG" "$tmp_config"
|
||||
apply_settings "$tmp_config"
|
||||
echo ""
|
||||
log "Diff preview:"
|
||||
diff "$SSHD_CONFIG" "$tmp_config" || true
|
||||
rm -f "$tmp_config"
|
||||
log "Run without --dry-run to apply changes"
|
||||
return
|
||||
fi
|
||||
|
||||
log "Applying SSH hardening..."
|
||||
apply_settings "$SSHD_CONFIG"
|
||||
|
||||
# Create banner
|
||||
if [[ ! -f /etc/ssh/banner.txt ]]; then
|
||||
cat > /etc/ssh/banner.txt <<'BANNER'
|
||||
***************************************************************************
|
||||
* AUTHORIZED ACCESS ONLY *
|
||||
* *
|
||||
* This system is restricted to authorized users. All activities are *
|
||||
* monitored and logged. Unauthorized access is prohibited and subject *
|
||||
* to prosecution under applicable law. *
|
||||
* *
|
||||
* By proceeding, you acknowledge that you have read and agree to the *
|
||||
* organization's acceptable use policies. *
|
||||
***************************************************************************
|
||||
BANNER
|
||||
log "Created warning banner at /etc/ssh/banner.txt"
|
||||
fi
|
||||
|
||||
# Session logging directory
|
||||
if [[ "$ENABLE_SESSION_LOGGING" == "true" ]]; then
|
||||
mkdir -p "$SESSION_LOG_DIR"
|
||||
chmod 700 "$SESSION_LOG_DIR"
|
||||
log "Session log directory: ${SESSION_LOG_DIR}"
|
||||
fi
|
||||
|
||||
# Fail2ban configuration
|
||||
if [[ "$CONFIGURE_FAIL2BAN" == "true" ]]; then
|
||||
configure_fail2ban
|
||||
fi
|
||||
|
||||
# Validate config
|
||||
log "Validating sshd configuration..."
|
||||
if sshd -t -f "$SSHD_CONFIG" 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} sshd -t passed"
|
||||
else
|
||||
err "sshd -t validation failed — restoring backup"
|
||||
cp -p "${backup_dir}/sshd_config" "$SSHD_CONFIG"
|
||||
die "Config validation failed. Original config restored."
|
||||
fi
|
||||
|
||||
# Restart sshd
|
||||
log "Restarting sshd..."
|
||||
if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} sshd restarted successfully"
|
||||
else
|
||||
warn "Could not restart sshd — restart manually"
|
||||
fi
|
||||
|
||||
# Write audit report
|
||||
local report_file
|
||||
report_file="/var/log/bastion-hardener-$(date +%Y%m%d-%H%M%S).log"
|
||||
{
|
||||
echo "Bastion Hardener — Apply Report"
|
||||
echo "Time: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo "Host: $(hostname -f 2>/dev/null || hostname)"
|
||||
echo "Changes: ${CHANGES}"
|
||||
echo "Backup: ${backup_dir}"
|
||||
} > "$report_file" 2>/dev/null || true
|
||||
log "Writing audit report → ${report_file}"
|
||||
|
||||
log "Changes applied: ${CHANGES}, skipped: 0"
|
||||
log "Backup directory: ${backup_dir}"
|
||||
log "To rollback: ./${SCRIPT_NAME} --rollback"
|
||||
log "Completed in $(elapsed)"
|
||||
}
|
||||
|
||||
apply_settings() {
|
||||
local config_file="$1"
|
||||
|
||||
local settings=(
|
||||
"PermitRootLogin no"
|
||||
"PasswordAuthentication no"
|
||||
"ChallengeResponseAuthentication no"
|
||||
"KbdInteractiveAuthentication no"
|
||||
"PubkeyAuthentication yes"
|
||||
"MaxAuthTries ${MAX_AUTH_TRIES}"
|
||||
"MaxSessions ${MAX_SESSIONS}"
|
||||
"ClientAliveInterval ${IDLE_TIMEOUT}"
|
||||
"ClientAliveCountMax 2"
|
||||
"X11Forwarding no"
|
||||
"AllowTcpForwarding no"
|
||||
"AllowAgentForwarding no"
|
||||
"PermitTunnel no"
|
||||
"Ciphers ${RECOMMENDED_CIPHERS}"
|
||||
"MACs ${RECOMMENDED_MACS}"
|
||||
"KexAlgorithms ${RECOMMENDED_KEX}"
|
||||
"LoginGraceTime 30"
|
||||
"LogLevel VERBOSE"
|
||||
"Banner /etc/ssh/banner.txt"
|
||||
)
|
||||
|
||||
for setting in "${settings[@]}"; do
|
||||
local key value
|
||||
key="${setting%% *}"
|
||||
value="${setting#* }"
|
||||
set_sshd_config "$key" "$value" "$config_file"
|
||||
echo -e " ${GREEN}✓${RESET} ${key} → ${value}"
|
||||
((CHANGES++)) || true
|
||||
done
|
||||
|
||||
# AllowUsers
|
||||
if [[ -n "$ALLOW_USERS" ]]; then
|
||||
local users_val="${ALLOW_USERS//,/ }"
|
||||
set_sshd_config "AllowUsers" "$users_val" "$config_file"
|
||||
echo -e " ${GREEN}✓${RESET} AllowUsers → ${users_val}"
|
||||
((CHANGES++)) || true
|
||||
fi
|
||||
|
||||
# AllowGroups
|
||||
if [[ -n "$ALLOW_GROUPS" ]]; then
|
||||
local groups_val="${ALLOW_GROUPS//,/ }"
|
||||
set_sshd_config "AllowGroups" "$groups_val" "$config_file"
|
||||
echo -e " ${GREEN}✓${RESET} AllowGroups → ${groups_val}"
|
||||
((CHANGES++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
configure_fail2ban() {
|
||||
if ! command -v fail2ban-client &>/dev/null; then
|
||||
warn "fail2ban not installed — skipping jail configuration"
|
||||
return
|
||||
fi
|
||||
|
||||
local jail_file="/etc/fail2ban/jail.d/bastion-ssh.conf"
|
||||
log "Configuring fail2ban SSH jail → ${jail_file}"
|
||||
|
||||
cat > "$jail_file" <<EOF
|
||||
# Bastion Hardener — SSH jail configuration
|
||||
# Generated: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = ${FAIL2BAN_MAXRETRY}
|
||||
bantime = ${FAIL2BAN_BANTIME}
|
||||
findtime = 600
|
||||
action = %(action_)s
|
||||
EOF
|
||||
|
||||
if systemctl restart fail2ban 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} fail2ban SSH jail configured and restarted"
|
||||
else
|
||||
warn "Could not restart fail2ban"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ROLLBACK MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_rollback() {
|
||||
require_root
|
||||
|
||||
local target_dir="$ROLLBACK_DIR"
|
||||
|
||||
if [[ -z "$target_dir" ]]; then
|
||||
# Find most recent backup
|
||||
target_dir=$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name "bastion-hardener-backup-*" 2>/dev/null | sort -r | head -1)
|
||||
if [[ -z "$target_dir" ]]; then
|
||||
die "No backup directories found in ${BACKUP_ROOT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -d "$target_dir" ]]; then
|
||||
die "Backup directory not found: ${target_dir}"
|
||||
fi
|
||||
|
||||
log "Restoring from ${target_dir}..."
|
||||
|
||||
if [[ -f "${target_dir}/sshd_config" ]]; then
|
||||
cp -p "${target_dir}/sshd_config" "$SSHD_CONFIG"
|
||||
echo -e " ${GREEN}✓${RESET} Restored sshd_config"
|
||||
else
|
||||
die "No sshd_config found in backup directory"
|
||||
fi
|
||||
|
||||
if [[ -f "${target_dir}/banner.txt" ]]; then
|
||||
cp -p "${target_dir}/banner.txt" /etc/ssh/banner.txt
|
||||
echo -e " ${GREEN}✓${RESET} Restored banner.txt"
|
||||
fi
|
||||
|
||||
# Validate
|
||||
log "Validating restored configuration..."
|
||||
if sshd -t -f "$SSHD_CONFIG" 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} sshd -t passed"
|
||||
else
|
||||
die "Restored config failed validation"
|
||||
fi
|
||||
|
||||
# Restart
|
||||
log "Restarting sshd..."
|
||||
if systemctl restart sshd 2>/dev/null || systemctl restart ssh 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} sshd restarted successfully"
|
||||
else
|
||||
warn "Could not restart sshd — restart manually"
|
||||
fi
|
||||
|
||||
log "Rollback complete from ${target_dir}"
|
||||
log "Completed in $(elapsed)"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# HELP
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [MODE] [OPTIONS]
|
||||
|
||||
Harden SSH bastion/jump hosts with audit, apply, and rollback.
|
||||
|
||||
MODES:
|
||||
--audit Read-only check of current SSH configuration
|
||||
--apply Apply hardening changes (backs up first)
|
||||
--rollback Restore configuration from most recent backup
|
||||
|
||||
OPTIONS:
|
||||
--dry-run Preview changes without writing to disk
|
||||
--allow-users USERS Comma-separated list of allowed SSH users
|
||||
--allow-groups GROUPS Comma-separated list of allowed SSH groups
|
||||
--idle-timeout SECS Client alive interval in seconds (default: $IDLE_TIMEOUT)
|
||||
--max-auth-tries N Maximum authentication attempts (default: $MAX_AUTH_TRIES)
|
||||
--max-sessions N Maximum sessions per connection (default: $MAX_SESSIONS)
|
||||
--session-logging Enable ForceCommand session recording
|
||||
--configure-fail2ban Configure fail2ban SSH jail
|
||||
--backup-dir DIR Specific backup directory for rollback
|
||||
--verbose Debug output
|
||||
--no-color Disable colored output
|
||||
--help, -h Show this help
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
SSHD_CONFIG Path to sshd_config (default: /etc/ssh/sshd_config)
|
||||
BACKUP_ROOT Backup storage root (default: /etc/ssh)
|
||||
ALLOW_USERS Comma-separated allowed users
|
||||
ALLOW_GROUPS Comma-separated allowed groups
|
||||
IDLE_TIMEOUT ClientAliveInterval in seconds (default: 300)
|
||||
MAX_AUTH_TRIES Maximum auth attempts (default: 3)
|
||||
MAX_SESSIONS Maximum sessions (default: 2)
|
||||
SESSION_LOG_DIR Session recording directory
|
||||
FAIL2BAN_BANTIME Ban duration in seconds (default: 3600)
|
||||
FAIL2BAN_MAXRETRY Max retries before ban (default: 3)
|
||||
DRY_RUN Preview without writing (default: false)
|
||||
VERBOSE Debug output (default: false)
|
||||
|
||||
EXAMPLES:
|
||||
# Audit current SSH configuration
|
||||
sudo $SCRIPT_NAME --audit
|
||||
|
||||
# Preview what would change
|
||||
sudo $SCRIPT_NAME --apply --dry-run
|
||||
|
||||
# Apply hardening with specific allowed users
|
||||
sudo $SCRIPT_NAME --apply --allow-users "deployer,jumpuser"
|
||||
|
||||
# Apply with fail2ban
|
||||
sudo $SCRIPT_NAME --apply --configure-fail2ban
|
||||
|
||||
# Rollback to previous config
|
||||
sudo $SCRIPT_NAME --rollback
|
||||
EOF
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
main() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--audit) RUN_MODE="audit"; shift ;;
|
||||
--apply) RUN_MODE="apply"; shift ;;
|
||||
--rollback) RUN_MODE="rollback"; shift ;;
|
||||
--dry-run) DRY_RUN="true"; shift ;;
|
||||
--allow-users) ALLOW_USERS="$2"; shift 2 ;;
|
||||
--allow-groups) ALLOW_GROUPS="$2"; shift 2 ;;
|
||||
--idle-timeout) IDLE_TIMEOUT="$2"; shift 2 ;;
|
||||
--max-auth-tries) MAX_AUTH_TRIES="$2"; shift 2 ;;
|
||||
--max-sessions) MAX_SESSIONS="$2"; shift 2 ;;
|
||||
--session-logging) ENABLE_SESSION_LOGGING="true"; shift ;;
|
||||
--configure-fail2ban) CONFIGURE_FAIL2BAN="true"; shift ;;
|
||||
--backup-dir) ROLLBACK_DIR="$2"; shift 2 ;;
|
||||
--verbose) VERBOSE="true"; shift ;;
|
||||
--no-color) COLOR="never"; shift ;;
|
||||
--help|-h) show_help; exit 0 ;;
|
||||
*) die "Unknown option: $1 (see --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
setup_colors
|
||||
|
||||
if [[ -z "$RUN_MODE" ]]; then err "No mode specified"; echo ""; show_help; exit 1; fi
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Bastion Hardener${RESET}"
|
||||
echo "Host: $(hostname -f 2>/dev/null || hostname)"
|
||||
echo "Mode: ${RUN_MODE}"
|
||||
echo "Time: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo ""
|
||||
|
||||
case "$RUN_MODE" in
|
||||
audit) do_audit ;;
|
||||
apply) do_apply ;;
|
||||
rollback) do_rollback ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user