diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 2edac30..d34a3b4 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -1,4 +1,4 @@ -name: Deploy Vault to Production Environment +name: Deploy Vault Stack to Production on: push: @@ -6,102 +6,39 @@ on: - prod-env concurrency: - group: prod-deploy + group: vault-prod-deploy cancel-in-progress: false jobs: deploy: runs-on: prod-runner steps: - - name: Checkout Branch + - name: Checkout uses: actions/checkout@v4 - - name: Connect Runner to Overlay Network - run: | - docker network connect iklimco-net $(hostname) || true - - name: Set up SSH Key and Add to known_hosts - run: | - mkdir -p ~/.ssh - echo "${{ secrets.STORAGEBOX_SSH_PRIV }}" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -p 23 ${{ vars.STORAGEBOX_USER }}.your-storagebox.de >> ~/.ssh/known_hosts + run: docker network connect iklimco-net $(hostname) || true - - name: Download Deploy Inputs + - name: Deploy Vault Stack run: | - mkdir -p deploy - # Fetch environment files from Storagebox - scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env ./.env - scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.forecast ./.env.secrets.forecast - - # Fetch base scripts and service manifests - curl -sL -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" \ - "https://git.tarla.io/iklim.co/BE_TopParent/raw/branch/prod-env/common-functions-base.sh" \ - -o /workspace/iklim.co/common-functions-base.sh - curl -sL -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" \ - "https://git.tarla.io/iklim.co/BE_TopParent/raw/branch/prod-env/common-functions-prod.sh" \ - -o common-functions-prod.sh - curl -sL -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" \ - "https://git.tarla.io/iklim.co/BE-Forecast/raw/branch/prod-env/deploy/prod.env" \ - -o deploy/prod.env - curl -sL -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" \ - "https://git.tarla.io/iklim.co/BE-Forecast/raw/branch/prod-env/docker-stack-service.yml" \ - -o deploy/docker-stack-service.yml - curl -sL -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" \ - "https://git.tarla.io/iklim.co/BE-Forecast/raw/branch/prod-env/docker-stack-service.prod.yml" \ - -o deploy/docker-stack-service.prod.yml - - test -s common-functions-prod.sh - test -s deploy/prod.env - test -s deploy/docker-stack-service.yml - test -s deploy/docker-stack-service.prod.yml + docker stack deploy \ + --with-registry-auth \ + -c docker-stack-vault.yml \ + iklimco - - name: Validate Deployment Manifest - id: manifest + - name: Run Bootstrap + env: + SKIP_DEPLOY: "true" + run: bash vault-bootstrap.sh + + - name: Verify Vault Cluster Health run: | - set -a; . ./deploy/prod.env; set +a - if [ -z "${SOURCE_IMAGE_DIGEST:-}" ] || [ -z "${PROD_IMAGE_TAG:-}" ]; then - echo "❌ SOURCE_IMAGE_DIGEST and PROD_IMAGE_TAG are required in deploy/prod.env" + SEALED=$(docker run --rm --network iklimco-net hashicorp/vault:2.0.1 \ + sh -c "VAULT_ADDR=https://vault.iklim.co:8200 VAULT_SKIP_VERIFY=true vault status 2>/dev/null" \ + | awk '/^Sealed/{print $2}' || echo "true") + if [ "$SEALED" = "false" ]; then + echo "Vault cluster is unsealed and healthy" + else + echo "ERROR: Vault cluster is sealed or unreachable" exit 1 fi - case "$SOURCE_IMAGE_DIGEST" in - registry.tarla.io/iklimco/forecast-service@sha256:*) ;; - *) echo "❌ SOURCE_IMAGE_DIGEST must be registry.tarla.io/iklimco/forecast-service@sha256:"; exit 1 ;; - esac - case "$PROD_IMAGE_TAG" in - *-rc*) echo "❌ PROD_IMAGE_TAG must not be an rc tag"; exit 1 ;; - esac - echo "source_image_digest=$SOURCE_IMAGE_DIGEST" >> $GITHUB_OUTPUT - echo "prod_image=registry.tarla.io/iklimco/forecast-service:$PROD_IMAGE_TAG" >> $GITHUB_OUTPUT - echo "🏷️ Promote: $SOURCE_IMAGE_DIGEST -> registry.tarla.io/iklimco/forecast-service:$PROD_IMAGE_TAG" - - - name: Promote Harbor Image - run: | - echo "${{ secrets.HARBOR_CI_TOKEN }}" | \ - docker login registry.tarla.io -u robot-ci-push-iklimco --password-stdin - docker pull "${{ steps.manifest.outputs.source_image_digest }}" - docker tag "${{ steps.manifest.outputs.source_image_digest }}" "${{ steps.manifest.outputs.prod_image }}" - docker push "${{ steps.manifest.outputs.prod_image }}" - echo "✅ Promoted: ${{ steps.manifest.outputs.prod_image }}" - - - name: Docker Login for Swarm Pull - run: | - echo "${{ secrets.HARBOR_PULL_TOKEN }}" | \ - docker login registry.tarla.io -u robot-swarm-pull-iklimco --password-stdin - - - name: Update Swarm Service - run: | - set -a; . ./.env; . ./.env.secrets.forecast; set +a - SERVICE_IMAGE="${{ steps.manifest.outputs.prod_image }}" \ - docker stack deploy \ - --with-registry-auth \ - -c deploy/docker-stack-service.yml \ - -c deploy/docker-stack-service.prod.yml \ - iklimco - - - name: Verify Deployment - run: | - sleep 15 - docker service ps iklimco_forecast-service \ - --filter "desired-state=running" \ - --format "table {{.Name}}\t{{.CurrentState}}\t{{.Image}}" | head -5 diff --git a/docker-stack-vault.yml b/docker-stack-vault.yml index e9bc5da..719981c 100644 --- a/docker-stack-vault.yml +++ b/docker-stack-vault.yml @@ -2,34 +2,37 @@ version: '3.8' services: # ------------------------------------------------------------------------- - # 1. PRIMARY VAULT CLUSTER (3 NODES - RAFT HIGH AVAILABILITY) + # VAULT CLUSTER (3 NODES - RAFT HIGH AVAILABILITY) # ------------------------------------------------------------------------- - # before deloy - ## echo "kendi_urettigin_guclu_token" | docker secret create transit_master_token - - # after deploy - ## docker exec -it $(docker ps -q -f name=iklimco_vault.1) vault operator init + # Bootstrap: vault_unseal_key is auto-managed by vault-bootstrap.sh. + # The script creates a placeholder on first deploy and replaces it with the + # real Shamir unseal key after 'vault operator init'. vault: image: hashicorp/vault:2.0.1 cap_add: - IPC_LOCK - # Overriding the default entrypoint to manipulate configuration strictly in RAM memory + # Overriding the default entrypoint to manipulate configuration strictly in RAM entrypoint: ["sh", "-c"] - # 1. Reads the transit token from Docker Secret into RAM - # 2. Substitutes TRANSIT_TOKEN_PLACEHOLDER and HOSTNAME_PLACEHOLDER via sed entirely in RAM - # 3. Writes the resolved config to /dev/shm (RAM-backed tmpfs) — no secret ever touches disk - # 4. Purges token variable, then execs vault as PID 1 with the resolved config + # 1. Resolves HOSTNAME_PLACEHOLDER via sed entirely in RAM (/dev/shm) — no secret touches disk + # 2. Starts vault server in background + # 3. Registers SIGTERM/SIGINT trap for graceful shutdown + # 4. Polls vault status; exit code 1 = not yet ready, 0 or 2 = vault is responding + # 5. Auto-unseals using vault_unseal_key Docker secret (no-op if key is wrong or file missing) + # 6. Waits for vault to exit and propagates exit code to Docker command: > - "export MASTER_TOKEN=$$(cat /run/secrets/transit_master_token); - cat /vault/config/vault.json | sed \"s/TRANSIT_TOKEN_PLACEHOLDER/$$MASTER_TOKEN/g\" | sed \"s/HOSTNAME_PLACEHOLDER/$$HOSTNAME/g\" > /dev/shm/vault.json; - unset MASTER_TOKEN; - exec vault server -config=/dev/shm/vault.json" + "cat /vault/config/vault.json | sed \"s/HOSTNAME_PLACEHOLDER/$$HOSTNAME/g\" > /dev/shm/vault.json; + vault server -config=/dev/shm/vault.json & + VAULT_PID=$$!; + trap 'kill -TERM $$VAULT_PID; wait $$VAULT_PID' TERM INT; + export VAULT_ADDR='https://127.0.0.1:8200' VAULT_SKIP_VERIFY='true'; + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do vault status > /dev/null 2>&1; [ $$? -ne 1 ] && break; sleep 2; done; + vault operator unseal $$(cat /run/secrets/vault_unseal_key 2>/dev/null) > /dev/null 2>&1 || true; + wait $$VAULT_PID" networks: iklimco-net: aliases: - vault.iklim.co environment: - # NOTE: VAULT_LOCAL_API_ADDR is intentionally omitted. - # Vault automatically discovers its own container IP and port (8200) from the listener block below. VAULT_ADDR: "https://127.0.0.1:8200" # PHASE 1 (Bootstrap): Keep it "true" so Vault CLI can operate on self-signed/temporary certificates. # PHASE 2 (Strict SSL): set this 'false' once you switch your configuration to v2. @@ -48,12 +51,12 @@ services: target: /vault/config/vault.json mode: 0444 secrets: - - transit_master_token + - vault_unseal_key deploy: mode: replicated replicas: 3 placement: - # High Availability rule: Ensures Vault nodes are strictly scheduled across different physical Swarm workers + # High Availability: each Vault node runs on a separate physical Swarm worker max_replicas_per_node: 1 constraints: - node.labels.type == service @@ -61,74 +64,6 @@ services: condition: any delay: 5s - # ------------------------------------------------------------------------- - # 2. AUTO-UNSEAL HELPER (1 NODE - PERSISTENT ON-PREMISE KMS) - # ------------------------------------------------------------------------- - # BOOTSTRAP (first deploy only — run these steps in order): - # Step 1: echo "bootstrap" | docker secret create vault_transit_unseal_key - - # Step 2: docker stack deploy ... (transit starts sealed and uninitialized) - # Step 3: docker exec -it $(docker ps -q -f name=iklimco_vault-transit) \ - # vault operator init -key-shares=1 -key-threshold=1 - # -> note the Unseal Key and Root Token from the output - # Step 4: docker service update --secret-rm vault_transit_unseal_key iklimco_vault-transit - # docker secret rm vault_transit_unseal_key - # echo "" | docker secret create vault_transit_unseal_key - - # docker service update --secret-add vault_transit_unseal_key iklimco_vault-transit - # Step 5: (exec into container) unseal, enable transit engine, create autounseal key, - # create a dedicated policy token, then update the transit_master_token secret: - # vault operator unseal - # vault login - # vault secrets enable transit - # vault write -f transit/keys/autounseal - # vault policy write autounseal - < docker secret rm transit_master_token - # -> echo "" | docker secret create transit_master_token - - # NORMAL OPERATION: auto-unseals on restart using vault_transit_unseal_key - # NODE PINNING (recommended): pin to one node to prevent volume data loss on rescheduling - # docker node update --label-add vault_transit=true - vault-transit: - image: hashicorp/vault:2.0.1 - cap_add: - - IPC_LOCK - entrypoint: ["sh", "-c"] - # 1. Starts vault in background with persistent file storage config - # 2. Registers SIGTERM/SIGINT trap so Docker stop triggers graceful vault shutdown - # 3. Polls vault status; exit 1 = not ready yet, exit 0/2 = vault is responding - # 4. Auto-unseals using vault_transit_unseal_key secret (no-op when uninitialized) - # 5. Waits for vault to exit and propagates its exit code to Docker - command: > - "vault server -config=/vault/config/transit.json & - VAULT_PID=$$!; - trap 'kill -TERM $$VAULT_PID; wait $$VAULT_PID' TERM INT; - export VAULT_ADDR='http://127.0.0.1:8200'; - for i in 1 2 3 4 5 6 7 8 9 10; do vault status > /dev/null 2>&1; [ $$? -ne 1 ] && break; sleep 2; done; - vault operator unseal $$(cat /run/secrets/vault_transit_unseal_key) > /dev/null 2>&1 || true; - wait $$VAULT_PID" - networks: - - iklimco-net - secrets: - - vault_transit_unseal_key - volumes: - - vault-transit-data-vl:/vault/file - configs: - - source: vault_transit_config - target: /vault/config/transit.json - mode: 0444 - deploy: - mode: replicated - replicas: 1 - placement: - constraints: - - node.labels.type == service - - node.labels.vault_transit == true - restart_policy: - condition: any - delay: 5s - configs: # ========================================================================= # CONFIG TEMPLATE PHASE 1: Bootstrap / Initial Discovery (No Verification) @@ -142,24 +77,13 @@ configs: vault_template_v2: file: ./vault-template-v2.json - # ========================================================================= - # TRANSIT VAULT CONFIG: Persistent file storage, TLS disabled (internal only) - # ========================================================================= - vault_transit_config: - file: ./vault-template-transit.json - volumes: vault-data-vl: vault-logs-vl: - vault-transit-data-vl: secrets: - # Must be provisioned via 'docker secret create transit_master_token -' before deployment - transit_master_token: - external: true - # First deploy: echo "bootstrap" | docker secret create vault_transit_unseal_key - - # After init: replace with the real unseal key from 'vault operator init' output - vault_transit_unseal_key: + # Managed by vault-bootstrap.sh: placeholder on first deploy, replaced with real unseal key after init. + vault_unseal_key: external: true networks: diff --git a/vault-bootstrap.sh b/vault-bootstrap.sh index be84008..789ca95 100755 --- a/vault-bootstrap.sh +++ b/vault-bootstrap.sh @@ -1,49 +1,28 @@ #!/bin/bash -# vault-bootstrap.sh — Full Vault HA cluster bootstrap -# iklim-app-03 (transit node + manager) uzerinde calistirilir. SSH gerektirmez. +# vault-bootstrap.sh — Vault HA cluster bootstrap (Shamir seal, Docker secret) +# Node-agnostic: uses docker exec for local replicas, falls back to the overlay +# network via docker run when no local replica is found on this Swarm manager. 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" +SKIP_DEPLOY="${SKIP_DEPLOY:-false}" # ───────────────────────────────────────────────────────────────────── 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)..." @@ -61,6 +40,22 @@ wait_service_running() { done fail "$svc $timeout saniye icinde hazir olmadi" } + +# Run a vault CLI command — uses docker exec if a vault replica is on this node, +# otherwise falls back to the overlay network via docker run. +VAULT_TOKEN="" +run_vault() { + local cmd="$*" + [ -n "$VAULT_TOKEN" ] && cmd="VAULT_TOKEN=$VAULT_TOKEN $cmd" + local cid + cid=$(docker ps -q -f "name=${STACK_NAME}_vault\." | head -1 || true) + if [ -n "$cid" ]; then + docker exec -i "$cid" sh -c "VAULT_ADDR=https://127.0.0.1:8200 VAULT_SKIP_VERIFY=true $cmd" + else + docker run --rm -i --network iklimco-net hashicorp/vault:2.0.1 \ + sh -c "VAULT_ADDR=https://vault.iklim.co:8200 VAULT_SKIP_VERIFY=true $cmd" + fi +} # ───────────────────────────────────────────────────────────────────── # ━━━ ADIM 0 — On kosullar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -69,154 +64,103 @@ 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" +# ━━━ ADIM 1 — Placeholder secret ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 1 — vault_unseal_key kontrol ediliyor" +if docker secret ls --format '{{.Name}}' | grep -q '^vault_unseal_key'; then + info "vault_unseal_key mevcut, 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" + echo "bootstrap" | docker secret create vault_unseal_key - >/dev/null + ok "vault_unseal_key (placeholder) 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" +# ━━━ ADIM 2 — Stack deploy ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 2 — Stack deploy" +if [ "$SKIP_DEPLOY" = "true" ]; then + info "SKIP_DEPLOY=true — atlaniyor" +else + docker stack deploy --with-registry-auth -c "$STACK_FILE" "$STACK_NAME" + ok "Stack deploy edildi" +fi -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 +# ━━━ ADIM 3 — Vault cluster bekleniyor ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 3 — Vault cluster bekleniyor" +wait_service_running "${STACK_NAME}_vault" 3 300 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" +# ━━━ ADIM 4 — Vault durum kontrolu ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 4 — Vault durum kontrolu" +VAULT_STATUS_OUT=$(run_vault "vault status 2>/dev/null" || true) +VAULT_INITIALIZED=$(echo "$VAULT_STATUS_OUT" | awk '/^Initialized/{print $2}') +VAULT_SEALED=$(echo "$VAULT_STATUS_OUT" | awk '/^Sealed/{print $2}') +info "Initialized: ${VAULT_INITIALIZED:-unknown}, Sealed: ${VAULT_SEALED:-unknown}" + +if [ "$VAULT_INITIALIZED" = "true" ] && [ "$VAULT_SEALED" = "false" ]; then + ok "Vault zaten initialize edilmis ve unsealed" + echo + echo "════════════════════════════════════════════════" + echo " BOOTSTRAP TAMAMLANDI (Vault saglıklı)" + echo "════════════════════════════════════════════════" + exit 0 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" +# ━━━ ADIM 5 — Vault initialize (gerekirse) ━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 5 — Vault initialize / unseal key hazirlaniyor" +if [ "$VAULT_INITIALIZED" = "true" ]; then + # Vault is sealed but initialized. This happens when the vault_unseal_key Docker secret + # contains the wrong value (e.g., placeholder was never replaced). Provide the init file + # so the real key can be extracted and pushed to the secret. + info "Vault sealed ama initialize edilmis — mevcut init dosyasi kullanilacak" + [ -f "$MAIN_INIT_FILE" ] && grep -q "Unseal Key 1" "$MAIN_INIT_FILE" \ + || fail "Init dosyasi eksik: $MAIN_INIT_FILE\nUnseal Key'i manuel olarak su formatta dosyaya ekleyin:\n Unseal Key 1: " + ok "Init dosyasi mevcut" else - ok "Transit zaten unsealed" + info "Vault initialize ediliyor..." + run_vault "vault operator init -key-shares=1 -key-threshold=1" | tee "$MAIN_INIT_FILE" + ok "Vault init tamamlandi: $MAIN_INIT_FILE" 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" +# ━━━ ADIM 6 — vault_unseal_key Docker secret guncelle ━━━━━━━━━━━━━━ +# Two-step update (delete + recreate with the same name) keeps the secret name +# consistent with the stack file so future 'docker stack deploy' runs do not +# trigger a service restart or revert to the placeholder. +step "ADIM 6 — vault_unseal_key Docker secret guncelleniyor" +UNSEAL_KEY=$(awk '/Unseal Key 1:/{print $NF}' "$MAIN_INIT_FILE") +[ -n "$UNSEAL_KEY" ] || fail "Unseal key '$MAIN_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" +info "Eski secret servis uzerinden kaldiriliyor (rolling restart 1/2)..." +docker service update --secret-rm vault_unseal_key "${STACK_NAME}_vault" >/dev/null +sleep 5 +docker secret rm vault_unseal_key || true -transit_vault "vault login $ROOT_TOKEN" >/dev/null -ok "Transit root login OK" +info "Gercek unseal key ile secret yeniden olusturuluyor (rolling restart 2/2)..." +echo "$UNSEAL_KEY" | docker secret create vault_unseal_key - >/dev/null +docker service update --secret-add vault_unseal_key "${STACK_NAME}_vault" >/dev/null +ok "vault_unseal_key gercek degerle guncellendi" -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 +# ━━━ ADIM 7 — Unseal dogrula ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +step "ADIM 7 — Vault unseal dogrulaniyor" +info "Rolling restart tamamlanmasi ve unseal bekleniyor (30s)..." +sleep 30 -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 +UNSEALED=0 +for i in $(seq 1 12); do + STATUS=$(run_vault "vault status 2>/dev/null" | awk '/^Sealed/{print $2}' || echo "true") + if [ "$STATUS" = "false" ]; then + ok "Vault cluster unsealed" + UNSEALED=1 + break + fi + [ "$i" -eq 12 ] && break + echo " ${i}/12 — Sealed: $STATUS, retrying in 5s..." + sleep 5 +done -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" +[ "$UNSEALED" -eq 1 ] || fail "Vault cluster unseal olmadi — 'docker service logs ${STACK_NAME}_vault' ile loglari kontrol edin" 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 " Init cikti: $MAIN_INIT_FILE" +echo " ONEMLI: Bu dosyayi guvenli yere yedekle ve" +echo " produksiyon ortamindan sil!" echo "════════════════════════════════════════════════" diff --git a/vault-template-transit.json b/vault-template-transit.json deleted file mode 100644 index 55c4754..0000000 --- a/vault-template-transit.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "storage": { - "file": { - "path": "/vault/file" - } - }, - "listener": { - "tcp": { - "address": "0.0.0.0:8200", - "tls_disable": 1 - } - }, - "disable_mlock": true, - "ui": false -} diff --git a/vault-template-v1.json b/vault-template-v1.json index b278f44..f5d836b 100644 --- a/vault-template-v1.json +++ b/vault-template-v1.json @@ -11,15 +11,6 @@ ] } }, - "seal": { - "transit": { - "address": "http://iklimco_vault-transit:8200", - "token": "TRANSIT_TOKEN_PLACEHOLDER", - "key_name": "autounseal", - "mount_path": "transit/", - "tls_skip_verify": true - } - }, "listener": { "tcp": { "address": "0.0.0.0:8200", diff --git a/vault-template-v2.json b/vault-template-v2.json index 2829297..2e60cad 100644 --- a/vault-template-v2.json +++ b/vault-template-v2.json @@ -15,15 +15,6 @@ ] } }, - "seal": { - "transit": { - "address": "http://iklimco_vault-transit:8200", - "token": "TRANSIT_TOKEN_PLACEHOLDER", - "key_name": "autounseal", - "mount_path": "transit/", - "tls_skip_verify": true - } - }, "listener": { "tcp": { "address": "0.0.0.0:8200",