Files
vault-backup/scripts/vault-backup-r2.sh

152 lines
4.2 KiB
Bash
Executable File

#!/bin/bash
# Vault Snapshot Backup to Cloudflare R2
# Usage: ./vault-backup-r2.sh [snapshot-name]
set -euo pipefail
# ============================================
# Configuration
# ============================================
VAULT_ADDR="${VAULT_ADDR:-https://hcv.inouter.com}"
VAULT_TOKEN="${VAULT_TOKEN:?VAULT_TOKEN is required}"
# R2 Configuration (wrangler 사용 - CF API 토큰으로 인증)
R2_BUCKET="${R2_BUCKET:-vault-backup}"
# Backup settings
BACKUP_DIR="${BACKUP_DIR:-/tmp/vault-backups}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
SNAPSHOT_NAME="${1:-vault-snapshot-${TIMESTAMP}}"
# ============================================
# Functions
# ============================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
error() {
log "ERROR: $*" >&2
exit 1
}
check_dependencies() {
for cmd in curl wrangler jq; do
if ! command -v "$cmd" &>/dev/null; then
error "$cmd is required but not installed"
fi
done
}
create_snapshot() {
log "Creating Vault snapshot..."
mkdir -p "$BACKUP_DIR"
local snapshot_file="${BACKUP_DIR}/${SNAPSHOT_NAME}.snap"
# Vault Raft snapshot (requires Raft storage backend)
curl -s -X POST \
-H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/sys/storage/raft/snapshot" \
-o "$snapshot_file" \
--fail || {
# Fallback: Export secrets as JSON (for non-Raft backends)
log "Raft snapshot failed, trying secrets export..."
export_secrets "$snapshot_file"
}
echo "$snapshot_file"
}
export_secrets() {
local output_file="$1"
local secrets_file="${output_file%.snap}.json"
log "Exporting secrets to JSON..."
# Get list of secret paths (requires list permission)
local paths=$(curl -s -X LIST \
-H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/metadata" | jq -r '.data.keys[]?' 2>/dev/null || echo "")
if [[ -z "$paths" ]]; then
log "No secrets found or no list permission"
echo '{"secrets": [], "timestamp": "'$(date -Iseconds)'", "note": "empty or no permission"}' > "$secrets_file"
else
echo '{"secrets": [' > "$secrets_file"
local first=true
for path in $paths; do
local secret=$(curl -s \
-H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/data/${path}" | jq '.data' 2>/dev/null)
if [[ -n "$secret" && "$secret" != "null" ]]; then
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> "$secrets_file"
fi
echo "{\"path\": \"${path}\", \"data\": ${secret}}" >> "$secrets_file"
fi
done
echo '], "timestamp": "'$(date -Iseconds)'"}' >> "$secrets_file"
fi
# Compress
gzip -c "$secrets_file" > "$output_file"
rm -f "$secrets_file"
}
upload_to_r2() {
local file="$1"
local filename=$(basename "$file")
log "Uploading to R2: ${R2_BUCKET}/${filename}"
# wrangler 사용 (CF API 토큰으로 인증)
wrangler r2 object put "${R2_BUCKET}/${filename}" --file="$file" --content-type="application/octet-stream" --remote
log "Upload complete: r2://${R2_BUCKET}/${filename}"
}
cleanup_old_backups() {
log "Cleanup skipped - wrangler doesn't support object listing"
log "Use Cloudflare dashboard or lifecycle rules for automatic cleanup"
}
cleanup_local() {
log "Cleaning up local backup files..."
find "$BACKUP_DIR" -name "vault-snapshot-*.snap" -mtime +1 -delete 2>/dev/null || true
find "$BACKUP_DIR" -name "vault-snapshot-*.gz" -mtime +1 -delete 2>/dev/null || true
}
# ============================================
# Main
# ============================================
main() {
log "=== Vault Backup to R2 Started ==="
log "Vault: $VAULT_ADDR"
log "R2 Bucket: $R2_BUCKET"
check_dependencies
local snapshot_file
snapshot_file=$(create_snapshot)
if [[ -f "$snapshot_file" ]]; then
upload_to_r2 "$snapshot_file"
cleanup_old_backups
cleanup_local
log "=== Backup Complete ==="
else
error "Snapshot file not created"
fi
}
main "$@"