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.
345 lines
9.5 KiB
Bash
345 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Wazuh API Backup
|
|
#
|
|
# Backs up Wazuh configuration objects via the REST API.
|
|
# Exports agents, groups, rules, decoders, CDB lists, manager
|
|
# configuration, API users, roles, policies, and cluster status
|
|
# to individual JSON files in a dated directory.
|
|
#
|
|
# Usage:
|
|
# WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh
|
|
# WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh --dry-run
|
|
# WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh --install
|
|
#
|
|
# Parameters:
|
|
# --dry-run Show what would be backed up without writing files
|
|
# --install Create cron job for daily backup at 3am
|
|
# --help Show usage
|
|
#
|
|
# Environment:
|
|
# WAZUH_API Wazuh API base URL (default: https://localhost:55000)
|
|
# WAZUH_USER API username (default: wazuh-wui)
|
|
# WAZUH_PASS API password (default: wazuh)
|
|
# BACKUP_DIR Base backup directory (default: /backup/wazuh-api)
|
|
# RETENTION_DAYS Delete backups older than this many days (default: 30)
|
|
# CURL_TIMEOUT API request timeout in seconds (default: 15)
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
# Version: 1.01
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration ---
|
|
readonly VERSION="1.0"
|
|
readonly SCRIPT_NAME="$(basename "$0")"
|
|
WAZUH_API="${WAZUH_API:-https://localhost:55000}"
|
|
WAZUH_USER="${WAZUH_USER:-wazuh-wui}"
|
|
WAZUH_PASS="${WAZUH_PASS:-wazuh}"
|
|
BACKUP_DIR="${BACKUP_DIR:-/backup/wazuh-api}"
|
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
|
CURL_TIMEOUT="${CURL_TIMEOUT:-15}"
|
|
DRY_RUN=false
|
|
TOKEN=""
|
|
|
|
# Backup endpoints: "api_path output_filename"
|
|
readonly ENDPOINTS=(
|
|
"agents?limit=500 agents.json"
|
|
"groups groups.json"
|
|
"rules?limit=500 rules.json"
|
|
"decoders?limit=500 decoders.json"
|
|
"lists cdb-lists.json"
|
|
"manager/configuration manager-config.json"
|
|
"manager/status manager-status.json"
|
|
"security/users users.json"
|
|
"security/roles roles.json"
|
|
"security/policies policies.json"
|
|
"security/rules security-rules.json"
|
|
"cluster/status cluster-status.json"
|
|
"syscheck syscheck-config.json"
|
|
"active-response active-response.json"
|
|
)
|
|
|
|
# Custom rule/decoder files to export (raw XML)
|
|
readonly CUSTOM_FILES=(
|
|
"rules/files/local_rules.xml local_rules.xml"
|
|
"decoders/files/local_decoder.xml local_decoder.xml"
|
|
)
|
|
|
|
# --- Functions ---
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [OPTIONS]
|
|
|
|
Wazuh API Backup
|
|
|
|
Options:
|
|
--dry-run Show what would be backed up without writing files
|
|
--install Create cron job for daily backup at 3am
|
|
--help Show this help message
|
|
|
|
Environment Variables:
|
|
WAZUH_API Wazuh API base URL (default: https://localhost:55000)
|
|
WAZUH_USER API username (default: wazuh-wui)
|
|
WAZUH_PASS API password (default: wazuh)
|
|
BACKUP_DIR Base backup directory (default: /backup/wazuh-api)
|
|
RETENTION_DAYS Delete backups older than N days (default: 30)
|
|
CURL_TIMEOUT Request timeout in seconds (default: 15)
|
|
|
|
Examples:
|
|
WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME
|
|
WAZUH_API="https://wazuh.example.com:55000" WAZUH_USER="automation" WAZUH_PASS="secret" $SCRIPT_NAME
|
|
WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME --dry-run
|
|
WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME --install
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
check_dependencies() {
|
|
local missing=()
|
|
for cmd in curl jq; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
missing+=("$cmd")
|
|
fi
|
|
done
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo "ERROR: Missing required commands: ${missing[*]}" >&2
|
|
echo "Install with: apt install ${missing[*]} OR dnf install ${missing[*]}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_config() {
|
|
# Strip trailing slash
|
|
WAZUH_API="${WAZUH_API%/}"
|
|
}
|
|
|
|
get_token() {
|
|
local response
|
|
response=$(curl -sf -k --max-time "$CURL_TIMEOUT" \
|
|
-u "${WAZUH_USER}:${WAZUH_PASS}" \
|
|
-X POST "${WAZUH_API}/security/user/authenticate" 2>/dev/null) || {
|
|
echo "ERROR: Failed to authenticate to Wazuh API at ${WAZUH_API}" >&2
|
|
echo "Check WAZUH_USER, WAZUH_PASS, and API connectivity" >&2
|
|
exit 1
|
|
}
|
|
|
|
TOKEN=$(echo "$response" | jq -r '.data.token // empty')
|
|
|
|
if [[ -z "$TOKEN" ]]; then
|
|
echo "ERROR: Authentication succeeded but no token returned" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
api_get() {
|
|
local endpoint="$1"
|
|
curl -sf -k --max-time "$CURL_TIMEOUT" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"${WAZUH_API}/${endpoint}" 2>/dev/null || echo ""
|
|
}
|
|
|
|
backup_endpoint() {
|
|
local endpoint="$1"
|
|
local filename="$2"
|
|
local output_dir="$3"
|
|
local response
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
printf " %-40s → %s (dry-run)\n" "$endpoint" "$filename"
|
|
return 0
|
|
fi
|
|
|
|
response=$(api_get "$endpoint")
|
|
|
|
if [[ -z "$response" ]]; then
|
|
printf " %-40s → %-30s FAIL\n" "$endpoint" "$filename"
|
|
return 1
|
|
fi
|
|
|
|
echo "$response" | jq '.' > "${output_dir}/${filename}" 2>/dev/null || {
|
|
# Not JSON (raw XML file) — write as-is
|
|
echo "$response" > "${output_dir}/${filename}"
|
|
}
|
|
local size
|
|
size=$(du -h "${output_dir}/${filename}" | cut -f1)
|
|
printf " %-40s → %-30s OK %s\n" "$endpoint" "$filename" "$size"
|
|
return 0
|
|
}
|
|
|
|
backup_group_configs() {
|
|
local output_dir="$1"
|
|
local groups_response
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
printf " %-40s → %s (dry-run)\n" "groups/*/configuration" "group-*-config.json"
|
|
return 0
|
|
fi
|
|
|
|
groups_response=$(api_get "groups")
|
|
|
|
if [[ -z "$groups_response" ]]; then
|
|
printf " %-40s → %-30s FAIL\n" "groups/*/configuration" "group configs"
|
|
return 1
|
|
fi
|
|
|
|
local group_names
|
|
group_names=$(echo "$groups_response" | jq -r '.data.affected_items[].name' 2>/dev/null)
|
|
|
|
if [[ -z "$group_names" ]]; then
|
|
printf " %-40s → %-30s SKIP (no groups)\n" "groups/*/configuration" "group configs"
|
|
return 0
|
|
fi
|
|
|
|
local count=0
|
|
while IFS= read -r group; do
|
|
local config
|
|
config=$(api_get "groups/${group}/configuration")
|
|
if [[ -n "$config" ]]; then
|
|
echo "$config" | jq '.' > "${output_dir}/group-${group}-config.json" 2>/dev/null
|
|
((count++)) || true
|
|
fi
|
|
done <<< "$group_names"
|
|
|
|
printf " %-40s → %-30s OK %d groups\n" "groups/*/configuration" "group-*-config.json" "$count"
|
|
return 0
|
|
}
|
|
|
|
cleanup_old_backups() {
|
|
if [[ ! -d "$BACKUP_DIR" ]]; then
|
|
return
|
|
fi
|
|
|
|
local removed=0
|
|
while IFS= read -r dir; do
|
|
rm -rf "$dir"
|
|
((removed++)) || true
|
|
done < <(find "$BACKUP_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +"$RETENTION_DAYS" 2>/dev/null)
|
|
|
|
if [[ $removed -gt 0 ]]; then
|
|
echo "Retention: removed $removed backup(s) older than ${RETENTION_DAYS} days"
|
|
fi
|
|
}
|
|
|
|
install_cron() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "ERROR: --install requires root" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local script_path
|
|
script_path=$(readlink -f "$0")
|
|
|
|
cat > /etc/cron.d/wazuh-api-backup <<EOF
|
|
# Wazuh API Backup — runs daily at 3am
|
|
WAZUH_API=${WAZUH_API}
|
|
WAZUH_USER=${WAZUH_USER}
|
|
WAZUH_PASS=${WAZUH_PASS}
|
|
BACKUP_DIR=${BACKUP_DIR}
|
|
RETENTION_DAYS=${RETENTION_DAYS}
|
|
0 3 * * * root ${script_path} 2>/dev/null
|
|
EOF
|
|
|
|
chmod 644 /etc/cron.d/wazuh-api-backup
|
|
echo "Installed cron job: /etc/cron.d/wazuh-api-backup"
|
|
echo "Backups will be written to: ${BACKUP_DIR}/<date>"
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
main() {
|
|
# Parse arguments
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run) DRY_RUN=true ;;
|
|
--install)
|
|
check_dependencies
|
|
validate_config
|
|
install_cron
|
|
exit 0
|
|
;;
|
|
--help|-h) usage ;;
|
|
*) echo "Unknown option: $arg" >&2; usage ;;
|
|
esac
|
|
done
|
|
|
|
check_dependencies
|
|
validate_config
|
|
|
|
local today
|
|
today=$(date +%Y%m%d)
|
|
local output_dir="${BACKUP_DIR}/${today}"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo "Wazuh API Backup v${VERSION} (dry-run)"
|
|
echo "Target: ${output_dir}"
|
|
echo ""
|
|
else
|
|
mkdir -p "$output_dir"
|
|
echo "Wazuh API Backup v${VERSION}"
|
|
echo "Target: ${output_dir}"
|
|
echo ""
|
|
fi
|
|
|
|
# Authenticate
|
|
if [[ "$DRY_RUN" != true ]]; then
|
|
get_token
|
|
fi
|
|
|
|
local ok=0
|
|
local fail=0
|
|
|
|
# Back up standard endpoints
|
|
for entry in "${ENDPOINTS[@]}"; do
|
|
local endpoint filename
|
|
endpoint="${entry%% *}"
|
|
filename="${entry##* }"
|
|
|
|
if backup_endpoint "$endpoint" "$filename" "$output_dir"; then
|
|
((ok++)) || true
|
|
else
|
|
((fail++)) || true
|
|
fi
|
|
done
|
|
|
|
# Back up custom rule/decoder files (raw XML)
|
|
for entry in "${CUSTOM_FILES[@]}"; do
|
|
local endpoint filename
|
|
endpoint="${entry%% *}"
|
|
filename="${entry##* }"
|
|
|
|
if backup_endpoint "$endpoint" "$filename" "$output_dir"; then
|
|
((ok++)) || true
|
|
else
|
|
((fail++)) || true
|
|
fi
|
|
done
|
|
|
|
# Back up per-group configurations
|
|
if backup_group_configs "$output_dir"; then
|
|
((ok++)) || true
|
|
else
|
|
((fail++)) || true
|
|
fi
|
|
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
local total=$((${#ENDPOINTS[@]} + ${#CUSTOM_FILES[@]} + 1))
|
|
echo "Dry-run complete: ${total} backup targets"
|
|
else
|
|
local total_size
|
|
total_size=$(du -sh "$output_dir" | cut -f1)
|
|
echo "Complete: ${ok} OK, ${fail} failed, ${total_size} total"
|
|
|
|
cleanup_old_backups
|
|
fi
|
|
}
|
|
|
|
main "$@"
|