Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
78
node-hardening/scripts/00_preflight.sh
Executable file
78
node-hardening/scripts/00_preflight.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
check_dependency() {
|
||||
if command -v "$1" &>/dev/null; then
|
||||
log_info "Found: $1 ($(command -v "$1"))"
|
||||
return 0
|
||||
else
|
||||
log_warn "Missing: $1"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
is_ssh_session() {
|
||||
[[ -n "${SSH_CONNECTION:-}" || -n "${SSH_CLIENT:-}" ]]
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
log_info "Starting $SCRIPT_NAME"
|
||||
|
||||
local missing=0
|
||||
|
||||
log_info "=== Required Dependencies ==="
|
||||
check_dependency sudo || ((missing++))
|
||||
check_dependency systemctl || ((missing++))
|
||||
check_dependency ss || check_dependency netstat || log_warn "No ss/netstat (network inspection limited)"
|
||||
check_dependency awk || ((missing++))
|
||||
check_dependency sed || ((missing++))
|
||||
check_dependency grep || ((missing++))
|
||||
|
||||
log_info "=== Hardening Tooling (may be installed during apply) ==="
|
||||
check_dependency ufw || log_warn "ufw not installed (apply can install)"
|
||||
check_dependency sshd || check_dependency ssh || log_warn "sshd binary not found (service may still exist)"
|
||||
check_dependency fail2ban-client || log_warn "fail2ban not installed (apply can install)"
|
||||
check_dependency auditctl || log_warn "auditd not installed (apply can install)"
|
||||
|
||||
log_info "=== Session Context ==="
|
||||
if is_ssh_session; then
|
||||
log_warn "Detected SSH session: ${SSH_CONNECTION:-${SSH_CLIENT:-unknown}}"
|
||||
log_warn "Recommendation: keep a second session open before applying changes."
|
||||
else
|
||||
log_info "No SSH session detected (console/local run)"
|
||||
fi
|
||||
|
||||
log_info "=== Privilege Check ==="
|
||||
if sudo -n true 2>/dev/null; then
|
||||
log_info "sudo is available without password prompt (non-interactive)"
|
||||
else
|
||||
log_info "sudo may prompt for password (interactive)"
|
||||
fi
|
||||
|
||||
log_info "=== Parameters (defaults if unset) ==="
|
||||
log_info "SSH_PORT=${SSH_PORT:-22}"
|
||||
log_info "ALLOW_HTTP=${ALLOW_HTTP:-true}"
|
||||
log_info "ALLOW_HTTPS=${ALLOW_HTTPS:-true}"
|
||||
log_info "DRY_RUN=${DRY_RUN:-1} (apply scripts require DRY_RUN=0)"
|
||||
|
||||
if [[ $missing -gt 0 ]]; then
|
||||
die "Missing $missing required dependencies. Install them before proceeding."
|
||||
fi
|
||||
|
||||
log_info "Completed $SCRIPT_NAME"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
64
node-hardening/scripts/10_ufw_plan.sh
Executable file
64
node-hardening/scripts/10_ufw_plan.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
detect_ssh_client_ip() {
|
||||
if [[ -n "${SSH_CLIENT:-}" ]]; then
|
||||
awk '{print $1}' <<<"$SSH_CLIENT"
|
||||
elif [[ -n "${SSH_CONNECTION:-}" ]]; then
|
||||
awk '{print $1}' <<<"$SSH_CONNECTION"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local ssh_port="${SSH_PORT:-22}"
|
||||
local allow_http="${ALLOW_HTTP:-true}"
|
||||
local allow_https="${ALLOW_HTTPS:-true}"
|
||||
local ssh_ip
|
||||
ssh_ip="$(detect_ssh_client_ip)"
|
||||
|
||||
log_info "UFW Plan (no changes applied)"
|
||||
log_info "Target SSH_PORT=$ssh_port"
|
||||
[[ -n "$ssh_ip" ]] && log_info "Detected SSH client IP=$ssh_ip" || log_warn "No SSH client IP detected"
|
||||
|
||||
echo
|
||||
echo "--- Intended policy ---"
|
||||
echo "Default: deny incoming"
|
||||
echo "Default: allow outgoing"
|
||||
echo
|
||||
echo "--- Intended allow rules ---"
|
||||
echo "1) Allow SSH: $ssh_port/tcp (always)"
|
||||
if [[ -n "$ssh_ip" ]]; then
|
||||
echo "2) Pin SSH from current client IP: from $ssh_ip to any port $ssh_port/tcp (optional safety)"
|
||||
else
|
||||
echo "2) No client IP detected; IP pinning skipped"
|
||||
fi
|
||||
if [[ "$allow_http" == "true" ]]; then
|
||||
echo "3) Allow HTTP: 80/tcp"
|
||||
else
|
||||
echo "3) HTTP not allowed"
|
||||
fi
|
||||
if [[ "$allow_https" == "true" ]]; then
|
||||
echo "4) Allow HTTPS: 443/tcp"
|
||||
else
|
||||
echo "4) HTTPS not allowed"
|
||||
fi
|
||||
echo
|
||||
echo "--- Safety notes ---"
|
||||
echo "- Apply script will refuse unless DRY_RUN=0"
|
||||
echo "- If you are on SSH, keep a second session open"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
100
node-hardening/scripts/11_ufw_apply.sh
Executable file
100
node-hardening/scripts/11_ufw_apply.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
require_confirm() {
|
||||
local require="${REQUIRE_CONFIRM:-1}"
|
||||
local phrase="${CONFIRM_PHRASE:-I UNDERSTAND THIS CAN LOCK ME OUT}"
|
||||
if [[ "$require" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo
|
||||
echo "CONFIRMATION REQUIRED"
|
||||
echo "Type the phrase exactly to continue:"
|
||||
echo " $phrase"
|
||||
read -r input
|
||||
[[ "$input" == "$phrase" ]] || die "Confirmation phrase mismatch; aborting."
|
||||
}
|
||||
|
||||
detect_ssh_client_ip() {
|
||||
if [[ -n "${SSH_CLIENT:-}" ]]; then
|
||||
awk '{print $1}' <<<"$SSH_CLIENT"
|
||||
elif [[ -n "${SSH_CONNECTION:-}" ]]; then
|
||||
awk '{print $1}' <<<"$SSH_CONNECTION"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_ufw() {
|
||||
if command -v ufw &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
log_warn "ufw not found; attempting install (Debian/Ubuntu)"
|
||||
if command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ufw
|
||||
else
|
||||
die "ufw not installed and apt-get not available. Install ufw manually."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
|
||||
local dry="${DRY_RUN:-1}"
|
||||
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
|
||||
|
||||
require_confirm
|
||||
|
||||
ensure_ufw
|
||||
|
||||
local ssh_port="${SSH_PORT:-22}"
|
||||
local allow_http="${ALLOW_HTTP:-true}"
|
||||
local allow_https="${ALLOW_HTTPS:-true}"
|
||||
local ssh_ip
|
||||
ssh_ip="$(detect_ssh_client_ip)"
|
||||
|
||||
log_info "Backing up current UFW status"
|
||||
sudo ufw status verbose >"$BACKUP_DIR/ufw_status_before.txt" || true
|
||||
sudo iptables -S >"$BACKUP_DIR/iptables_rules_before.txt" || true
|
||||
|
||||
log_info "Applying UFW policy (idempotent)"
|
||||
sudo ufw --force reset
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
|
||||
# Always allow SSH
|
||||
sudo ufw allow "$ssh_port"/tcp
|
||||
if [[ -n "$ssh_ip" ]]; then
|
||||
sudo ufw allow from "$ssh_ip" to any port "$ssh_port" proto tcp
|
||||
fi
|
||||
if [[ "$allow_http" == "true" ]]; then
|
||||
sudo ufw allow 80/tcp
|
||||
fi
|
||||
if [[ "$allow_https" == "true" ]]; then
|
||||
sudo ufw allow 443/tcp
|
||||
fi
|
||||
|
||||
log_info "Enabling UFW"
|
||||
sudo ufw --force enable
|
||||
|
||||
log_info "Writing post-state"
|
||||
sudo ufw status verbose >"$OUTPUT_DIR/ufw_status_after.txt"
|
||||
|
||||
log_info "UFW apply complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
42
node-hardening/scripts/20_ssh_plan.sh
Executable file
42
node-hardening/scripts/20_ssh_plan.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
render_template() {
|
||||
local tpl="$1"
|
||||
local port="$2"
|
||||
sed "s/{{SSH_PORT}}/$port/g" "$tpl"
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local ssh_port="${SSH_PORT:-22}"
|
||||
log_info "SSH Hardening Plan (no changes applied)"
|
||||
log_info "Target SSH_PORT=$ssh_port"
|
||||
|
||||
if [[ -f /etc/ssh/sshd_config ]]; then
|
||||
log_info "Current /etc/ssh/sshd_config exists"
|
||||
else
|
||||
log_warn "No /etc/ssh/sshd_config found; this host may use a different path"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "--- Proposed sshd_config (rendered from template) ---"
|
||||
render_template "$TEMPLATE_DIR/sshd_config.tpl" "$ssh_port"
|
||||
echo
|
||||
echo "--- Safety notes ---"
|
||||
echo "- Apply will backup /etc/ssh/sshd_config before writing"
|
||||
echo "- Apply will run 'sshd -t' (syntax check) before reloading"
|
||||
echo "- Apply will refuse unless DRY_RUN=0"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
92
node-hardening/scripts/21_ssh_apply.sh
Executable file
92
node-hardening/scripts/21_ssh_apply.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
require_confirm() {
|
||||
local require="${REQUIRE_CONFIRM:-1}"
|
||||
local phrase="${CONFIRM_PHRASE:-I UNDERSTAND THIS CAN LOCK ME OUT}"
|
||||
if [[ "$require" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo
|
||||
echo "CONFIRMATION REQUIRED"
|
||||
echo "Type the phrase exactly to continue:"
|
||||
echo " $phrase"
|
||||
read -r input
|
||||
[[ "$input" == "$phrase" ]] || die "Confirmation phrase mismatch; aborting."
|
||||
}
|
||||
|
||||
render_template() {
|
||||
local port="$1"
|
||||
sed "s/{{SSH_PORT}}/$port/g" "$TEMPLATE_DIR/sshd_config.tpl"
|
||||
}
|
||||
|
||||
sshd_binary() {
|
||||
if command -v sshd &>/dev/null; then
|
||||
echo "sshd"
|
||||
elif [[ -x /usr/sbin/sshd ]]; then
|
||||
echo "/usr/sbin/sshd"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
|
||||
sudo systemctl reload sshd
|
||||
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
|
||||
sudo systemctl reload ssh
|
||||
else
|
||||
die "Could not find ssh/sshd service under systemctl"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
|
||||
local dry="${DRY_RUN:-1}"
|
||||
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
|
||||
|
||||
require_confirm
|
||||
|
||||
local ssh_port="${SSH_PORT:-22}"
|
||||
local sshd
|
||||
sshd="$(sshd_binary)"
|
||||
[[ -n "$sshd" ]] || die "sshd binary not found"
|
||||
|
||||
[[ -f /etc/ssh/sshd_config ]] || die "/etc/ssh/sshd_config not found"
|
||||
|
||||
log_info "Backing up current sshd_config"
|
||||
sudo cp -a /etc/ssh/sshd_config "$BACKUP_DIR/sshd_config.before"
|
||||
|
||||
log_info "Writing new sshd_config from template"
|
||||
render_template "$ssh_port" | sudo tee /etc/ssh/sshd_config >/dev/null
|
||||
|
||||
log_info "Validating sshd config (sshd -t)"
|
||||
sudo "$sshd" -t || {
|
||||
log_error "sshd config validation failed; restoring backup"
|
||||
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
|
||||
die "Aborted due to invalid sshd_config"
|
||||
}
|
||||
|
||||
log_info "Reloading SSH service"
|
||||
reload_service
|
||||
|
||||
log_info "SSH hardening applied. Verify you still have access before closing sessions."
|
||||
log_info "If you are locked out, use scripts/rollback/emergency_restore.sh from console."
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
66
node-hardening/scripts/30_fail2ban_setup.sh
Executable file
66
node-hardening/scripts/30_fail2ban_setup.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
ensure_fail2ban() {
|
||||
if command -v fail2ban-client &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
log_warn "fail2ban not found; attempting install (Debian/Ubuntu)"
|
||||
if command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y fail2ban
|
||||
else
|
||||
die "fail2ban not installed and apt-get not available. Install manually."
|
||||
fi
|
||||
}
|
||||
|
||||
render_jail() {
|
||||
local port="$1"
|
||||
sed "s/{{SSH_PORT}}/$port/g" "$TEMPLATE_DIR/fail2ban_jail.tpl"
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
|
||||
local enabled="${FAIL2BAN_ENABLE:-true}"
|
||||
if [[ "$enabled" != "true" && "$enabled" != "1" ]]; then
|
||||
log_info "FAIL2BAN_ENABLE disabled; skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local dry="${DRY_RUN:-1}"
|
||||
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
|
||||
|
||||
ensure_fail2ban
|
||||
|
||||
local ssh_port="${SSH_PORT:-22}"
|
||||
|
||||
if [[ -f /etc/fail2ban/jail.local ]]; then
|
||||
sudo cp -a /etc/fail2ban/jail.local "$BACKUP_DIR/jail.local.before"
|
||||
fi
|
||||
|
||||
log_info "Writing /etc/fail2ban/jail.local"
|
||||
render_jail "$ssh_port" | sudo tee /etc/fail2ban/jail.local >/dev/null
|
||||
|
||||
log_info "Enabling and restarting fail2ban"
|
||||
sudo systemctl enable fail2ban
|
||||
sudo systemctl restart fail2ban
|
||||
|
||||
log_info "fail2ban setup complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
62
node-hardening/scripts/40_auditd_setup.sh
Executable file
62
node-hardening/scripts/40_auditd_setup.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
ensure_auditd() {
|
||||
if command -v auditctl &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
log_warn "auditd not found; attempting install (Debian/Ubuntu)"
|
||||
if command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y auditd audispd-plugins
|
||||
else
|
||||
die "auditd not installed and apt-get not available. Install manually."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
|
||||
local enabled="${AUDITD_ENABLE:-true}"
|
||||
if [[ "$enabled" != "true" && "$enabled" != "1" ]]; then
|
||||
log_info "AUDITD_ENABLE disabled; skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local dry="${DRY_RUN:-1}"
|
||||
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
|
||||
|
||||
ensure_auditd
|
||||
|
||||
sudo systemctl enable auditd
|
||||
|
||||
local rules_path="/etc/audit/rules.d/node-hardening.rules"
|
||||
if [[ -f "$rules_path" ]]; then
|
||||
sudo cp -a "$rules_path" "$BACKUP_DIR/node-hardening.rules.before"
|
||||
fi
|
||||
|
||||
log_info "Writing $rules_path"
|
||||
sudo install -m 0640 /dev/null "$rules_path"
|
||||
sudo cp "$TEMPLATE_DIR/auditd_rules.tpl" "$rules_path"
|
||||
|
||||
log_info "Loading audit rules"
|
||||
sudo augenrules --load || sudo service auditd restart || true
|
||||
|
||||
log_info "auditd setup complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
98
node-hardening/scripts/90_verify.sh
Executable file
98
node-hardening/scripts/90_verify.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
CHECKS_DIR="$SKILL_ROOT/checks"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
run_check_bool() {
|
||||
local script="$1"
|
||||
if [[ -x "$CHECKS_DIR/$script" ]]; then
|
||||
if "$CHECKS_DIR/$script" &>/dev/null; then
|
||||
echo "true"
|
||||
else
|
||||
echo "false"
|
||||
fi
|
||||
else
|
||||
echo "skip"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
local ufw_ok ssh_ok f2b_ok audit_ok
|
||||
ufw_ok=$(run_check_bool check_ufw.sh)
|
||||
ssh_ok=$(run_check_bool check_ssh.sh)
|
||||
f2b_ok=$(run_check_bool check_fail2ban.sh)
|
||||
audit_ok=$(run_check_bool check_auditd.sh)
|
||||
|
||||
local blockers=""
|
||||
local warnings=""
|
||||
local next_steps=""
|
||||
|
||||
if [[ "$ssh_ok" == "false" ]]; then
|
||||
blockers="${blockers}\"SSH hardening check failed\","
|
||||
fi
|
||||
if [[ "$ufw_ok" == "false" ]]; then
|
||||
warnings="${warnings}\"UFW not active\","
|
||||
fi
|
||||
if [[ "$f2b_ok" == "false" ]]; then
|
||||
warnings="${warnings}\"fail2ban not active\","
|
||||
fi
|
||||
if [[ "$audit_ok" == "false" ]]; then
|
||||
warnings="${warnings}\"auditd not active\","
|
||||
fi
|
||||
|
||||
next_steps="${next_steps}\"Run ./scripts/99_report.sh\","
|
||||
if [[ "$ssh_ok" == "true" && "$ufw_ok" == "true" ]]; then
|
||||
next_steps="${next_steps}\"Proceed to backup-sovereign skill\","
|
||||
fi
|
||||
|
||||
blockers="[${blockers%,}]"
|
||||
warnings="[${warnings%,}]"
|
||||
next_steps="[${next_steps%,}]"
|
||||
|
||||
cat > "$OUTPUT_DIR/status_matrix.json" <<EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"skill": "node-hardening",
|
||||
"node": "${NODE_NAME:-node-a}",
|
||||
"checks": {
|
||||
"ufw": $ufw_ok,
|
||||
"ssh": $ssh_ok,
|
||||
"fail2ban": $f2b_ok,
|
||||
"auditd": $audit_ok
|
||||
},
|
||||
"blockers": $blockers,
|
||||
"warnings": $warnings,
|
||||
"next_steps": $next_steps
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Status matrix written to $OUTPUT_DIR/status_matrix.json"
|
||||
|
||||
echo
|
||||
echo "============================================"
|
||||
echo " VERIFICATION SUMMARY"
|
||||
echo "============================================"
|
||||
echo
|
||||
echo " UFW: $ufw_ok"
|
||||
echo " SSH: $ssh_ok"
|
||||
echo " fail2ban: $f2b_ok"
|
||||
echo " auditd: $audit_ok"
|
||||
echo
|
||||
|
||||
if [[ "$ssh_ok" == "true" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
199
node-hardening/scripts/99_report.sh
Executable file
199
node-hardening/scripts/99_report.sh
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${NODE_NAME:=node-a}"
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
|
||||
get_ufw_status() {
|
||||
if command -v ufw &>/dev/null; then
|
||||
if sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||
echo "Active"
|
||||
else
|
||||
echo "Inactive"
|
||||
fi
|
||||
else
|
||||
echo "Not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
get_ssh_status() {
|
||||
if systemctl is-active ssh &>/dev/null || systemctl is-active sshd &>/dev/null; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
}
|
||||
|
||||
get_fail2ban_status() {
|
||||
if command -v fail2ban-client &>/dev/null; then
|
||||
if systemctl is-active fail2ban &>/dev/null; then
|
||||
echo "Active"
|
||||
else
|
||||
echo "Inactive"
|
||||
fi
|
||||
else
|
||||
echo "Not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
get_auditd_status() {
|
||||
if command -v auditctl &>/dev/null; then
|
||||
if systemctl is-active auditd &>/dev/null; then
|
||||
echo "Active"
|
||||
else
|
||||
echo "Inactive"
|
||||
fi
|
||||
else
|
||||
echo "Not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
list_backups() {
|
||||
if [[ -d "$BACKUP_DIR" ]]; then
|
||||
ls -1 "$BACKUP_DIR" 2>/dev/null | while read -r f; do
|
||||
echo "| $f | $(stat -c%s "$BACKUP_DIR/$f" 2>/dev/null || echo "?") bytes |"
|
||||
done
|
||||
else
|
||||
echo "| (no backups) | - |"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
log_info "Starting $SCRIPT_NAME..."
|
||||
|
||||
local report="$OUTPUT_DIR/audit_report.md"
|
||||
local status_file="$OUTPUT_DIR/status_matrix.json"
|
||||
|
||||
cat > "$report" <<EOF
|
||||
# Node Hardening Audit Report
|
||||
|
||||
**Generated:** $(date -Iseconds)
|
||||
**Node:** $NODE_NAME
|
||||
**Skill Version:** 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This report documents the hardening operations performed on **$NODE_NAME**
|
||||
for sovereign EU infrastructure security.
|
||||
|
||||
---
|
||||
|
||||
## Components Status
|
||||
|
||||
### 1. Firewall (UFW)
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| UFW | $(get_ufw_status) |
|
||||
|
||||
### 2. SSH Service
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| SSH Daemon | $(get_ssh_status) |
|
||||
| Config Backup | $([ -f "$BACKUP_DIR/sshd_config.before" ] && echo "Present" || echo "Not found") |
|
||||
|
||||
### 3. Intrusion Detection (fail2ban)
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| fail2ban | $(get_fail2ban_status) |
|
||||
|
||||
### 4. Audit Logging (auditd)
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| auditd | $(get_auditd_status) |
|
||||
|
||||
---
|
||||
|
||||
## Backups
|
||||
|
||||
| File | Size |
|
||||
|------|------|
|
||||
$(list_backups)
|
||||
|
||||
---
|
||||
|
||||
## Verification Results
|
||||
|
||||
$(if [[ -f "$status_file" ]]; then
|
||||
echo '```json'
|
||||
cat "$status_file"
|
||||
echo '```'
|
||||
else
|
||||
echo "Status matrix not found. Run 90_verify.sh first."
|
||||
fi)
|
||||
|
||||
---
|
||||
|
||||
## EU Compliance Declaration
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| Data Residency | EU (Ireland - Dublin) |
|
||||
| GDPR Applicable | Yes |
|
||||
| Jurisdiction | Irish Law |
|
||||
| Audit Logging | auditd (local only) |
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
If access is lost or changes need to be reverted:
|
||||
|
||||
1. **Emergency Restore (console):** \`./scripts/rollback/emergency_restore.sh\`
|
||||
2. **Undo SSH:** \`./scripts/rollback/undo_ssh.sh\`
|
||||
3. **Undo UFW:** \`./scripts/rollback/undo_ufw.sh\`
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Verify SSH access from a secondary session
|
||||
2. Test emergency rollback procedure (recommended)
|
||||
3. Proceed to **backup-sovereign** skill
|
||||
4. Document hardening in LAWCHAIN (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Artifact Locations
|
||||
|
||||
| Artifact | Path |
|
||||
|----------|------|
|
||||
| UFW Status (before) | $BACKUP_DIR/ufw_status_before.txt |
|
||||
| iptables Rules (before) | $BACKUP_DIR/iptables_rules_before.txt |
|
||||
| sshd_config (before) | $BACKUP_DIR/sshd_config.before |
|
||||
| UFW Status (after) | $OUTPUT_DIR/ufw_status_after.txt |
|
||||
| Status Matrix | $OUTPUT_DIR/status_matrix.json |
|
||||
| This Report | $OUTPUT_DIR/audit_report.md |
|
||||
|
||||
---
|
||||
|
||||
*Report generated by node-hardening skill v1.0.0*
|
||||
*$(date -Iseconds)*
|
||||
EOF
|
||||
|
||||
log_info "Audit report written to $report"
|
||||
|
||||
# Display the report
|
||||
echo ""
|
||||
cat "$report"
|
||||
|
||||
log_info "Completed $SCRIPT_NAME"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
47
node-hardening/scripts/rollback/emergency_restore.sh
Executable file
47
node-hardening/scripts/rollback/emergency_restore.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
reload_service() {
|
||||
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
|
||||
sudo systemctl restart sshd
|
||||
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
|
||||
sudo systemctl restart ssh
|
||||
else
|
||||
die "Could not find ssh/sshd service under systemctl"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
log_warn "EMERGENCY RESTORE: intended to be run from console if you are locked out"
|
||||
|
||||
if command -v ufw &>/dev/null; then
|
||||
log_warn "Disabling UFW"
|
||||
sudo ufw --force disable || true
|
||||
fi
|
||||
|
||||
if [[ -f "$BACKUP_DIR/sshd_config.before" ]]; then
|
||||
log_warn "Restoring sshd_config backup"
|
||||
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
|
||||
reload_service
|
||||
else
|
||||
log_warn "No sshd_config backup found; skipping SSH restore"
|
||||
fi
|
||||
|
||||
log_info "Emergency restore complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
38
node-hardening/scripts/rollback/undo_ssh.sh
Executable file
38
node-hardening/scripts/rollback/undo_ssh.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
reload_service() {
|
||||
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
|
||||
sudo systemctl reload sshd
|
||||
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
|
||||
sudo systemctl reload ssh
|
||||
else
|
||||
die "Could not find ssh/sshd service under systemctl"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
if [[ ! -f "$BACKUP_DIR/sshd_config.before" ]]; then
|
||||
die "No backup found at $BACKUP_DIR/sshd_config.before"
|
||||
fi
|
||||
log_info "Restoring /etc/ssh/sshd_config from backup"
|
||||
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
|
||||
log_info "Reloading SSH service"
|
||||
reload_service
|
||||
log_info "SSH rollback complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
32
node-hardening/scripts/rollback/undo_ufw.sh
Executable file
32
node-hardening/scripts/rollback/undo_ufw.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
|
||||
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
|
||||
if ! command -v ufw &>/dev/null; then
|
||||
die "ufw not installed"
|
||||
fi
|
||||
|
||||
log_info "Disabling UFW"
|
||||
sudo ufw --force disable || true
|
||||
|
||||
if [[ -f "$BACKUP_DIR/ufw_status_before.txt" ]]; then
|
||||
log_info "Backup exists at $BACKUP_DIR/ufw_status_before.txt (informational)"
|
||||
fi
|
||||
|
||||
log_info "UFW rollback complete"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
Reference in New Issue
Block a user