refactor(vault): Replace transit auto-unseal with Shamir + Docker secret

Remove vault-transit service entirely. Each vault node now auto-unseals at
startup by reading the Shamir unseal key from a Docker secret managed by
vault-bootstrap.sh. Eliminates the transit token expiry failure mode and
removes the vault_transit node-pinning requirement.

Changes:
- docker-stack-vault.yml: remove vault-transit service, vault_transit_config,
  vault-transit-data-vl, transit_master_token / vault_transit_unseal_key
  secrets; add vault_unseal_key secret; rewrite vault entrypoint to background
  start + poll + auto-unseal loop
- vault-template-v1.json, vault-template-v2.json: remove seal.transit block
- vault-template-transit.json: deleted (vault-transit is gone)
- vault-bootstrap.sh: full rewrite — node-agnostic run_vault() helper (docker
  exec fallback to docker run over overlay network), 7-step Shamir flow with
  SKIP_DEPLOY support and early-exit when vault is already healthy
- deploy-prod.yml: replace BE-Forecast deploy with vault stack deploy +
  bootstrap (SKIP_DEPLOY=true) + cluster health check
This commit is contained in:
Murat ÖZDEMİR 2026-06-10 13:37:32 +03:00
parent bf81b6ebee
commit 508363fc75
6 changed files with 144 additions and 372 deletions

View File

@ -1,4 +1,4 @@
name: Deploy Vault to Production Environment name: Deploy Vault Stack to Production
on: on:
push: push:
@ -6,102 +6,39 @@ on:
- prod-env - prod-env
concurrency: concurrency:
group: prod-deploy group: vault-prod-deploy
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
deploy: deploy:
runs-on: prod-runner runs-on: prod-runner
steps: steps:
- name: Checkout Branch - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Connect Runner to Overlay Network - name: Connect Runner to Overlay Network
run: | run: docker network connect iklimco-net $(hostname) || true
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
- name: Download Deploy Inputs - name: Deploy Vault Stack
run: | 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
- name: Validate Deployment Manifest
id: manifest
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"
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:<digest>"; 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 \ docker stack deploy \
--with-registry-auth \ --with-registry-auth \
-c deploy/docker-stack-service.yml \ -c docker-stack-vault.yml \
-c deploy/docker-stack-service.prod.yml \
iklimco iklimco
- name: Verify Deployment - name: Run Bootstrap
env:
SKIP_DEPLOY: "true"
run: bash vault-bootstrap.sh
- name: Verify Vault Cluster Health
run: | run: |
sleep 15 SEALED=$(docker run --rm --network iklimco-net hashicorp/vault:2.0.1 \
docker service ps iklimco_forecast-service \ sh -c "VAULT_ADDR=https://vault.iklim.co:8200 VAULT_SKIP_VERIFY=true vault status 2>/dev/null" \
--filter "desired-state=running" \ | awk '/^Sealed/{print $2}' || echo "true")
--format "table {{.Name}}\t{{.CurrentState}}\t{{.Image}}" | head -5 if [ "$SEALED" = "false" ]; then
echo "Vault cluster is unsealed and healthy"
else
echo "ERROR: Vault cluster is sealed or unreachable"
exit 1
fi

View File

@ -2,34 +2,37 @@ version: '3.8'
services: services:
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# 1. PRIMARY VAULT CLUSTER (3 NODES - RAFT HIGH AVAILABILITY) # VAULT CLUSTER (3 NODES - RAFT HIGH AVAILABILITY)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# before deloy # Bootstrap: vault_unseal_key is auto-managed by vault-bootstrap.sh.
## echo "kendi_urettigin_guclu_token" | docker secret create transit_master_token - # The script creates a placeholder on first deploy and replaces it with the
# after deploy # real Shamir unseal key after 'vault operator init'.
## docker exec -it $(docker ps -q -f name=iklimco_vault.1) vault operator init
vault: vault:
image: hashicorp/vault:2.0.1 image: hashicorp/vault:2.0.1
cap_add: cap_add:
- IPC_LOCK - 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"] entrypoint: ["sh", "-c"]
# 1. Reads the transit token from Docker Secret into RAM # 1. Resolves HOSTNAME_PLACEHOLDER via sed entirely in RAM (/dev/shm) — no secret touches disk
# 2. Substitutes TRANSIT_TOKEN_PLACEHOLDER and HOSTNAME_PLACEHOLDER via sed entirely in RAM # 2. Starts vault server in background
# 3. Writes the resolved config to /dev/shm (RAM-backed tmpfs) — no secret ever touches disk # 3. Registers SIGTERM/SIGINT trap for graceful shutdown
# 4. Purges token variable, then execs vault as PID 1 with the resolved config # 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: > command: >
"export MASTER_TOKEN=$$(cat /run/secrets/transit_master_token); "cat /vault/config/vault.json | sed \"s/HOSTNAME_PLACEHOLDER/$$HOSTNAME/g\" > /dev/shm/vault.json;
cat /vault/config/vault.json | sed \"s/TRANSIT_TOKEN_PLACEHOLDER/$$MASTER_TOKEN/g\" | sed \"s/HOSTNAME_PLACEHOLDER/$$HOSTNAME/g\" > /dev/shm/vault.json; vault server -config=/dev/shm/vault.json &
unset MASTER_TOKEN; VAULT_PID=$$!;
exec vault server -config=/dev/shm/vault.json" 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: networks:
iklimco-net: iklimco-net:
aliases: aliases:
- vault.iklim.co - vault.iklim.co
environment: 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" 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 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. # PHASE 2 (Strict SSL): set this 'false' once you switch your configuration to v2.
@ -48,12 +51,12 @@ services:
target: /vault/config/vault.json target: /vault/config/vault.json
mode: 0444 mode: 0444
secrets: secrets:
- transit_master_token - vault_unseal_key
deploy: deploy:
mode: replicated mode: replicated
replicas: 3 replicas: 3
placement: 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 max_replicas_per_node: 1
constraints: constraints:
- node.labels.type == service - node.labels.type == service
@ -61,74 +64,6 @@ services:
condition: any condition: any
delay: 5s 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 "<Unseal-Key>" | 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 <Unseal-Key>
# vault login <Root-Token>
# vault secrets enable transit
# vault write -f transit/keys/autounseal
# vault policy write autounseal - <<EOF
# path "transit/encrypt/autounseal" { capabilities = ["update"] }
# path "transit/decrypt/autounseal" { capabilities = ["update"] }
# EOF
# vault token create -policy=autounseal -period=768h -orphan
# -> docker secret rm transit_master_token
# -> echo "<policy-token>" | 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 <node-hostname>
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: configs:
# ========================================================================= # =========================================================================
# CONFIG TEMPLATE PHASE 1: Bootstrap / Initial Discovery (No Verification) # CONFIG TEMPLATE PHASE 1: Bootstrap / Initial Discovery (No Verification)
@ -142,24 +77,13 @@ configs:
vault_template_v2: vault_template_v2:
file: ./vault-template-v2.json file: ./vault-template-v2.json
# =========================================================================
# TRANSIT VAULT CONFIG: Persistent file storage, TLS disabled (internal only)
# =========================================================================
vault_transit_config:
file: ./vault-template-transit.json
volumes: volumes:
vault-data-vl: vault-data-vl:
vault-logs-vl: vault-logs-vl:
vault-transit-data-vl:
secrets: secrets:
# Must be provisioned via 'docker secret create transit_master_token -' before deployment # Managed by vault-bootstrap.sh: placeholder on first deploy, replaced with real unseal key after init.
transit_master_token: vault_unseal_key:
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:
external: true external: true
networks: networks:

View File

@ -1,49 +1,28 @@
#!/bin/bash #!/bin/bash
# vault-bootstrap.sh — Full Vault HA cluster bootstrap # vault-bootstrap.sh — Vault HA cluster bootstrap (Shamir seal, Docker secret)
# iklim-app-03 (transit node + manager) uzerinde calistirilir. SSH gerektirmez. # 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 set -euo pipefail
# ─── Configuration ─────────────────────────────────────────────────── # ─── Configuration ───────────────────────────────────────────────────
STACK_NAME="iklimco" STACK_NAME="iklimco"
STACK_FILE="$(cd "$(dirname "$0")" && pwd)/docker-stack-vault.yml" STACK_FILE="$(cd "$(dirname "$0")" && pwd)/docker-stack-vault.yml"
TRANSIT_NODE="iklim-app-03"
OUT_DIR="/tmp/vault-bootstrap" OUT_DIR="/tmp/vault-bootstrap"
SKIP_DEPLOY="${SKIP_DEPLOY:-false}"
# ───────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────
mkdir -p "$OUT_DIR" mkdir -p "$OUT_DIR"
TRANSIT_INIT_FILE="$OUT_DIR/transit-init.txt"
MAIN_INIT_FILE="$OUT_DIR/main-vault-init.txt" MAIN_INIT_FILE="$OUT_DIR/main-vault-init.txt"
AUTOUNSEAL_TOKEN_FILE="$OUT_DIR/autounseal-token.txt"
# ─── Logging ───────────────────────────────────────────────────────── # ─── Logging ─────────────────────────────────────────────────────────
step() { echo; echo "════════════════════════════════════════════════"; echo " [$(date '+%H:%M:%S')] $*"; echo "════════════════════════════════════════════════"; } step() { echo; echo "════════════════════════════════════════════════"; echo " [$(date '+%H:%M:%S')] $*"; echo "════════════════════════════════════════════════"; }
ok() { echo " [OK] $*"; } ok() { echo " [OK] $*"; }
info() { echo " --> $*"; } info() { echo " --> $*"; }
fail() { echo; echo " [HATA] $*" >&2; exit 1; } fail() { echo; echo " [HATA] $*" >&2; exit 1; }
trap 'echo; echo " [HATA] Script satir $LINENO'"'"'de beklenmedik sekilde sonlandi" >&2' ERR trap 'echo; echo " [HATA] Script satir $LINENO'"'"'de beklenmedik sekilde sonlandi" >&2' ERR
# ───────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────
# ─── Helpers ───────────────────────────────────────────────────────── # ─── 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() { wait_service_running() {
local svc="$1" expected="$2" timeout="${3:-180}" elapsed=0 local svc="$1" expected="$2" timeout="${3:-180}" elapsed=0
info "Bekleniyor: $svc ($expected running task)..." info "Bekleniyor: $svc ($expected running task)..."
@ -61,6 +40,22 @@ wait_service_running() {
done done
fail "$svc $timeout saniye icinde hazir olmadi" 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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ━━━ 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" [ -f "$STACK_FILE" ] || fail "Stack dosyasi bulunamadi: $STACK_FILE"
ok "On kosullar tamam" ok "On kosullar tamam"
# ━━━ ADIM 1 — Placeholder secrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ━━━ ADIM 1 — Placeholder secret ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
step "ADIM 1 — Placeholder secrets olusturuluyor" step "ADIM 1 — vault_unseal_key kontrol ediliyor"
if secret_exists vault_transit_unseal_key; then if docker secret ls --format '{{.Name}}' | grep -q '^vault_unseal_key'; then
info "vault_transit_unseal_key zaten var, atlaniyor" info "vault_unseal_key mevcut, atlaniyor"
else else
echo "bootstrap" | docker secret create vault_transit_unseal_key - >/dev/null echo "bootstrap" | docker secret create vault_unseal_key - >/dev/null
ok "vault_transit_unseal_key olusturuldu" ok "vault_unseal_key (placeholder) 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 fi
# ━━━ ADIM 2 — Node label ve stack deploy ━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ━━━ ADIM 2 — Stack deploy ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
step "ADIM 2 — Transit node etiketleniyor ve stack deploy ediliyor" step "ADIM 2 — Stack deploy"
docker node update --label-add vault_transit=true "$TRANSIT_NODE" >/dev/null if [ "$SKIP_DEPLOY" = "true" ]; then
ok "Node etiketi eklendi: $TRANSIT_NODE" 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" # ━━━ ADIM 3 — Vault cluster bekleniyor ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ok "Stack deploy edildi (ana vault node'lari transit hazir olana kadar crash loop'ta — beklenen)" step "ADIM 3 — Vault cluster bekleniyor"
wait_service_running "${STACK_NAME}_vault" 3 300
# ━━━ 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 sleep 10
if transit_vault "vault status 2>/dev/null" | grep -q "Initialized.*true"; then # ━━━ ADIM 4 — Vault durum kontrolu ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
info "Transit zaten initialize edilmis" step "ADIM 4 — Vault durum kontrolu"
[ -f "$TRANSIT_INIT_FILE" ] && grep -q "Initial Root Token" "$TRANSIT_INIT_FILE" \ VAULT_STATUS_OUT=$(run_vault "vault status 2>/dev/null" || true)
|| fail "Transit initialize edilmis ama init dosyasi eksik — Unseal Key ve Root Token'i manuel olarak $TRANSIT_INIT_FILE dosyasina kaydedin" VAULT_INITIALIZED=$(echo "$VAULT_STATUS_OUT" | awk '/^Initialized/{print $2}')
else VAULT_SEALED=$(echo "$VAULT_STATUS_OUT" | awk '/^Sealed/{print $2}')
info "Transit vault initialize ediliyor..." info "Initialized: ${VAULT_INITIALIZED:-unknown}, Sealed: ${VAULT_SEALED:-unknown}"
rm -f "$TRANSIT_INIT_FILE"
transit_vault "vault operator init -key-shares=1 -key-threshold=1" | tee "$TRANSIT_INIT_FILE" if [ "$VAULT_INITIALIZED" = "true" ] && [ "$VAULT_SEALED" = "false" ]; then
ok "Transit init tamamlandi — cikti: $TRANSIT_INIT_FILE" ok "Vault zaten initialize edilmis ve unsealed"
echo
echo "════════════════════════════════════════════════"
echo " BOOTSTRAP TAMAMLANDI (Vault saglıklı)"
echo "════════════════════════════════════════════════"
exit 0
fi fi
# ━━━ ADIM 4 — Transit unseal ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ━━━ ADIM 5 — Vault initialize (gerekirse) ━━━━━━━━━━━━━━━━━━━━━━━━━
step "ADIM 4 — Transit vault unseal ediliyor" step "ADIM 5 — Vault initialize / unseal key hazirlaniyor"
if transit_is_sealed; then if [ "$VAULT_INITIALIZED" = "true" ]; then
UNSEAL_KEY=$(awk '/Unseal Key 1:/{print $NF}' "$TRANSIT_INIT_FILE") # Vault is sealed but initialized. This happens when the vault_unseal_key Docker secret
[ -n "$UNSEAL_KEY" ] || fail "Unseal key '$TRANSIT_INIT_FILE' dosyasinda bulunamadi" # contains the wrong value (e.g., placeholder was never replaced). Provide the init file
transit_vault "vault operator unseal $UNSEAL_KEY" >/dev/null # so the real key can be extracted and pushed to the secret.
ok "Transit unsealed" 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: <gercek-key>"
ok "Init dosyasi mevcut"
else 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 fi
# ━━━ ADIM 5 — Transit engine kurulumu ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ━━━ ADIM 6 — vault_unseal_key Docker secret guncelle ━━━━━━━━━━━━━━
step "ADIM 5 — Transit secrets engine kuruluyor" # Two-step update (delete + recreate with the same name) keeps the secret name
ROOT_TOKEN=$(awk '/Initial Root Token:/{print $NF}' "$TRANSIT_INIT_FILE") # consistent with the stack file so future 'docker stack deploy' runs do not
[ -n "$ROOT_TOKEN" ] || fail "Root token '$TRANSIT_INIT_FILE' dosyasinda bulunamadi" # 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 info "Eski secret servis uzerinden kaldiriliyor (rolling restart 1/2)..."
docker exec "$(transit_id)" sh -c 'cat > /tmp/autounseal-policy.hcl << '"'"'EOF'"'"' docker service update --secret-rm vault_unseal_key "${STACK_NAME}_vault" >/dev/null
path "transit/encrypt/autounseal" { capabilities = ["update"] } sleep 5
path "transit/decrypt/autounseal" { capabilities = ["update"] } docker secret rm vault_unseal_key || true
EOF'
ok "Policy dosyasi container icine yazildi"
transit_vault "vault login $ROOT_TOKEN" >/dev/null info "Gercek unseal key ile secret yeniden olusturuluyor (rolling restart 2/2)..."
ok "Transit root login OK" 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 # ━━━ ADIM 7 — Unseal dogrula ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
info "Transit engine zaten etkin, atlaniyor" step "ADIM 7 — Vault unseal dogrulaniyor"
else info "Rolling restart tamamlanmasi ve unseal bekleniyor (30s)..."
transit_vault "vault secrets enable transit" >/dev/null sleep 30
ok "Transit engine etkinlestirildi"
fi
if transit_vault "vault read transit/keys/autounseal 2>/dev/null" | grep -q "autounseal"; then UNSEALED=0
info "Autounseal key zaten var, atlaniyor" for i in $(seq 1 12); do
else STATUS=$(run_vault "vault status 2>/dev/null" | awk '/^Sealed/{print $2}' || echo "true")
transit_vault "vault write -f transit/keys/autounseal" >/dev/null if [ "$STATUS" = "false" ]; then
ok "Autounseal key olusturuldu" ok "Vault cluster unsealed"
fi 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 [ "$UNSEALED" -eq 1 ] || fail "Vault cluster unseal olmadi — 'docker service logs ${STACK_NAME}_vault' ile loglari kontrol edin"
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 "════════════════════════════════════════════════" echo "════════════════════════════════════════════════"
echo " BOOTSTRAP TAMAMLANDI" echo " BOOTSTRAP TAMAMLANDI"
echo " Transit init : $TRANSIT_INIT_FILE" echo " Init cikti: $MAIN_INIT_FILE"
echo " Ana vault init: $MAIN_INIT_FILE" echo " ONEMLI: Bu dosyayi guvenli yere yedekle ve"
echo " ONEMLI: Bu dosyalari guvenli yere yedekle!" echo " produksiyon ortamindan sil!"
echo "════════════════════════════════════════════════" echo "════════════════════════════════════════════════"

View File

@ -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
}

View File

@ -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": { "listener": {
"tcp": { "tcp": {
"address": "0.0.0.0:8200", "address": "0.0.0.0:8200",

View File

@ -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": { "listener": {
"tcp": { "tcp": {
"address": "0.0.0.0:8200", "address": "0.0.0.0:8200",