#!/bin/bash # vault-bootstrap.sh — Full Vault HA cluster bootstrap # iklim-app-03 (transit node + manager) uzerinde calistirilir. SSH gerektirmez. set -euo pipefail # ─── Configuration ─────────────────────────────────────────────────── STACK_NAME="iklimco" STACK_FILE="$(cd "$(dirname "$0")" && pwd)/docker-stack-vault.yml" TRANSIT_NODE="iklim-app-03" OUT_DIR="/tmp/vault-bootstrap" # ───────────────────────────────────────────────────────────────────── mkdir -p "$OUT_DIR" TRANSIT_INIT_FILE="$OUT_DIR/transit-init.txt" MAIN_INIT_FILE="$OUT_DIR/main-vault-init.txt" AUTOUNSEAL_TOKEN_FILE="$OUT_DIR/autounseal-token.txt" # ─── Logging ───────────────────────────────────────────────────────── step() { echo; echo "════════════════════════════════════════════════"; echo " [$(date '+%H:%M:%S')] $*"; echo "════════════════════════════════════════════════"; } ok() { echo " [OK] $*"; } info() { echo " --> $*"; } fail() { echo; echo " [HATA] $*" >&2; exit 1; } trap 'echo; echo " [HATA] Script satir $LINENO'"'"'de beklenmedik sekilde sonlandi" >&2' ERR # ───────────────────────────────────────────────────────────────────── # ─── Helpers ───────────────────────────────────────────────────────── transit_id() { docker ps -q -f "name=${STACK_NAME}_vault-transit" | head -1 } transit_vault() { local cid cid=$(transit_id) || fail "Transit container bulunamadi" [ -n "$cid" ] || fail "Transit container bulunamadi" docker exec "$cid" sh -c "VAULT_ADDR=http://127.0.0.1:8200 $*" } secret_exists() { docker secret ls --format '{{.Name}}' | grep -q "^$1$" } transit_is_sealed() { ! transit_vault "vault status 2>/dev/null" | grep -q "Sealed.*false" } wait_service_running() { local svc="$1" expected="$2" timeout="${3:-180}" elapsed=0 info "Bekleniyor: $svc ($expected running task)..." while [ "$elapsed" -lt "$timeout" ]; do running=$(docker service ps "$svc" \ --filter "desired-state=running" \ --format '{{.CurrentState}}' 2>/dev/null \ | grep -c "^Running" || true) if [ "$running" -ge "$expected" ]; then ok "$svc hazir: $running/$expected" return 0 fi sleep 5; elapsed=$((elapsed+5)) echo " ${elapsed}s/${timeout}s — running: $running/$expected" done fail "$svc $timeout saniye icinde hazir olmadi" } # ───────────────────────────────────────────────────────────────────── # ━━━ ADIM 0 — On kosullar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 0 — On kosullar kontrol ediliyor" docker node ls &>/dev/null || fail "Swarm manager node gerekli" [ -f "$STACK_FILE" ] || fail "Stack dosyasi bulunamadi: $STACK_FILE" ok "On kosullar tamam" # ━━━ ADIM 1 — Placeholder secrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 1 — Placeholder secrets olusturuluyor" if secret_exists vault_transit_unseal_key; then info "vault_transit_unseal_key zaten var, atlaniyor" else echo "bootstrap" | docker secret create vault_transit_unseal_key - >/dev/null ok "vault_transit_unseal_key olusturuldu" fi if secret_exists transit_master_token; then info "transit_master_token zaten var, atlaniyor" else echo "bootstrap" | docker secret create transit_master_token - >/dev/null ok "transit_master_token olusturuldu" fi # ━━━ ADIM 2 — Node label ve stack deploy ━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 2 — Transit node etiketleniyor ve stack deploy ediliyor" docker node update --label-add vault_transit=true "$TRANSIT_NODE" >/dev/null ok "Node etiketi eklendi: $TRANSIT_NODE" docker stack deploy --with-registry-auth -c "$STACK_FILE" "$STACK_NAME" ok "Stack deploy edildi (ana vault node'lari transit hazir olana kadar crash loop'ta — beklenen)" # ━━━ ADIM 3 — Transit vault initialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 3 — Transit vault bekleniyor ve initialize ediliyor" wait_service_running "${STACK_NAME}_vault-transit" 1 120 sleep 10 if transit_vault "vault status 2>/dev/null" | grep -q "Initialized.*true"; then info "Transit zaten initialize edilmis" [ -f "$TRANSIT_INIT_FILE" ] && grep -q "Initial Root Token" "$TRANSIT_INIT_FILE" \ || fail "Transit initialize edilmis ama init dosyasi eksik — Unseal Key ve Root Token'i manuel olarak $TRANSIT_INIT_FILE dosyasina kaydedin" else info "Transit vault initialize ediliyor..." rm -f "$TRANSIT_INIT_FILE" transit_vault "vault operator init -key-shares=1 -key-threshold=1" | tee "$TRANSIT_INIT_FILE" ok "Transit init tamamlandi — cikti: $TRANSIT_INIT_FILE" fi # ━━━ ADIM 4 — Transit unseal ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 4 — Transit vault unseal ediliyor" if transit_is_sealed; then UNSEAL_KEY=$(awk '/Unseal Key 1:/{print $NF}' "$TRANSIT_INIT_FILE") [ -n "$UNSEAL_KEY" ] || fail "Unseal key '$TRANSIT_INIT_FILE' dosyasinda bulunamadi" transit_vault "vault operator unseal $UNSEAL_KEY" >/dev/null ok "Transit unsealed" else ok "Transit zaten unsealed" fi # ━━━ ADIM 5 — Transit engine kurulumu ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 5 — Transit secrets engine kuruluyor" ROOT_TOKEN=$(awk '/Initial Root Token:/{print $NF}' "$TRANSIT_INIT_FILE") [ -n "$ROOT_TOKEN" ] || fail "Root token '$TRANSIT_INIT_FILE' dosyasinda bulunamadi" # Policy dosyasini direkt container icine yaz docker exec "$(transit_id)" sh -c 'cat > /tmp/autounseal-policy.hcl << '"'"'EOF'"'"' path "transit/encrypt/autounseal" { capabilities = ["update"] } path "transit/decrypt/autounseal" { capabilities = ["update"] } EOF' ok "Policy dosyasi container icine yazildi" transit_vault "vault login $ROOT_TOKEN" >/dev/null ok "Transit root login OK" if transit_vault "vault secrets list 2>/dev/null" | grep -q "^transit/"; then info "Transit engine zaten etkin, atlaniyor" else transit_vault "vault secrets enable transit" >/dev/null ok "Transit engine etkinlestirildi" fi if transit_vault "vault read transit/keys/autounseal 2>/dev/null" | grep -q "autounseal"; then info "Autounseal key zaten var, atlaniyor" else transit_vault "vault write -f transit/keys/autounseal" >/dev/null ok "Autounseal key olusturuldu" fi transit_vault "vault policy write autounseal /tmp/autounseal-policy.hcl" >/dev/null ok "Autounseal policy yazildi" info "Autounseal policy token olusturuluyor..." AUTOUNSEAL_TOKEN=$(transit_vault "vault token create -policy=autounseal -period=768h -orphan -format=json" \ | grep '"client_token"' | awk -F'"' '{print $4}') [ -n "$AUTOUNSEAL_TOKEN" ] || fail "Autounseal token alinamadi" echo "$AUTOUNSEAL_TOKEN" > "$AUTOUNSEAL_TOKEN_FILE" ok "Autounseal token olusturuldu: ${AUTOUNSEAL_TOKEN:0:20}..." # ━━━ ADIM 6 — Secrets guncelle ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 6 — Docker secrets gercek degerlerle guncelleniyor" UNSEAL_KEY=$(awk '/Unseal Key 1:/{print $NF}' "$TRANSIT_INIT_FILE") # 6a — vault_transit_unseal_key info "vault_transit_unseal_key guncelleniyor..." docker service update --secret-rm vault_transit_unseal_key "${STACK_NAME}_vault-transit" >/dev/null docker secret rm vault_transit_unseal_key echo "$UNSEAL_KEY" | docker secret create vault_transit_unseal_key - >/dev/null docker service update --secret-add vault_transit_unseal_key "${STACK_NAME}_vault-transit" >/dev/null ok "vault_transit_unseal_key guncellendi" # 6b — transit auto-unseal dogrula info "Transit restart sonrasi unseal durumu bekleniyor (15s)..." sleep 15 if transit_is_sealed; then info "Transit hala sealed, manuel unseal yapiliyor..." transit_vault "vault operator unseal $UNSEAL_KEY" >/dev/null ok "Transit manuel unsealed" else ok "Transit auto-unseal basarili" fi # 6c — transit_master_token atomic swap (her calistirmada fresh token) info "transit_master_token atomic swap yapiliyor..." # Vault servisinde simdi hangi secret transit_master_token olarak mount edilmis? CURRENT_TOKEN_SECRET=$(docker service inspect "${STACK_NAME}_vault" \ --format '{{range .Spec.TaskTemplate.ContainerSpec.Secrets}}{{if eq .File.Name "transit_master_token"}}{{.SecretName}}{{end}}{{end}}' \ 2>/dev/null) [ -n "$CURRENT_TOKEN_SECRET" ] || CURRENT_TOKEN_SECRET="transit_master_token" info "Mevcut token secret: $CURRENT_TOKEN_SECRET" # Her calistirmada benzersiz isimli fresh secret olustur NEW_TOKEN_SECRET="transit_master_token_$(date +%s)" echo "$AUTOUNSEAL_TOKEN" | docker secret create "$NEW_TOKEN_SECRET" - >/dev/null ok "$NEW_TOKEN_SECRET olusturuldu" # Atomic swap: eski cikart, yenicisi ekle — tek komutta (vault hic token'siz restart olmaz) docker service update \ --secret-rm "$CURRENT_TOKEN_SECRET" \ --secret-add "source=${NEW_TOKEN_SECRET},target=transit_master_token" \ "${STACK_NAME}_vault" >/dev/null ok "Atomic swap tamamlandi: $CURRENT_TOKEN_SECRET -> $NEW_TOKEN_SECRET" # ━━━ ADIM 7 — Ana vault cluster initialize ━━━━━━━━━━━━━━━━━━━━━━━━━ step "ADIM 7 — Ana vault cluster bekleniyor ve initialize ediliyor" info "Vault node'larinin stabil olmasi bekleniyor..." wait_service_running "${STACK_NAME}_vault" 3 300 info "Ana vault cluster initialize ediliyor (lokal vault container)..." LOCAL_VAULT_ID=$(docker ps -q -f "name=${STACK_NAME}_vault\." | head -1) [ -n "$LOCAL_VAULT_ID" ] || fail "Lokal vault container bulunamadi — vault servisi bu node'da calismiyor olmali" docker exec "$LOCAL_VAULT_ID" vault operator init | tee "$MAIN_INIT_FILE" echo echo "════════════════════════════════════════════════" echo " BOOTSTRAP TAMAMLANDI" echo " Transit init : $TRANSIT_INIT_FILE" echo " Ana vault init: $MAIN_INIT_FILE" echo " ONEMLI: Bu dosyalari guvenli yere yedekle!" echo "════════════════════════════════════════════════"