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:
|
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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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"
|
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)"
|
ok "Stack deploy edildi"
|
||||||
|
fi
|
||||||
|
|
||||||
# ━━━ ADIM 3 — Transit vault initialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
# ━━━ ADIM 3 — Vault cluster bekleniyor ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
step "ADIM 3 — Transit vault bekleniyor ve initialize ediliyor"
|
step "ADIM 3 — Vault cluster bekleniyor"
|
||||||
wait_service_running "${STACK_NAME}_vault-transit" 1 120
|
wait_service_running "${STACK_NAME}_vault" 3 300
|
||||||
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"
|
|
||||||
|
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
|
fi
|
||||||
|
[ "$i" -eq 12 ] && break
|
||||||
|
echo " ${i}/12 — Sealed: $STATUS, retrying in 5s..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
if transit_vault "vault read transit/keys/autounseal 2>/dev/null" | grep -q "autounseal"; then
|
[ "$UNSEALED" -eq 1 ] || fail "Vault cluster unseal olmadi — 'docker service logs ${STACK_NAME}_vault' ile loglari kontrol edin"
|
||||||
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 "════════════════════════════════════════════════"
|
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 "════════════════════════════════════════════════"
|
||||||
|
|||||||
@ -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": {
|
"listener": {
|
||||||
"tcp": {
|
"tcp": {
|
||||||
"address": "0.0.0.0:8200",
|
"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": {
|
"listener": {
|
||||||
"tcp": {
|
"tcp": {
|
||||||
"address": "0.0.0.0:8200",
|
"address": "0.0.0.0:8200",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user