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:
parent
bf81b6ebee
commit
508363fc75
@ -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
|
||||
|
||||
- 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 \
|
||||
--with-registry-auth \
|
||||
-c deploy/docker-stack-service.yml \
|
||||
-c deploy/docker-stack-service.prod.yml \
|
||||
-c docker-stack-vault.yml \
|
||||
iklimco
|
||||
|
||||
- name: Verify Deployment
|
||||
- name: Run Bootstrap
|
||||
env:
|
||||
SKIP_DEPLOY: "true"
|
||||
run: bash vault-bootstrap.sh
|
||||
|
||||
- name: Verify Vault Cluster Health
|
||||
run: |
|
||||
sleep 15
|
||||
docker service ps iklimco_forecast-service \
|
||||
--filter "desired-state=running" \
|
||||
--format "table {{.Name}}\t{{.CurrentState}}\t{{.Image}}" | head -5
|
||||
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
|
||||
|
||||
@ -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 "<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:
|
||||
# =========================================================================
|
||||
# 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:
|
||||
|
||||
@ -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: <gercek-key>"
|
||||
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 "════════════════════════════════════════════════"
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user