feat: initialize vault transit auto-unseal documentation and configs

- Added comprehensive step-by-step guide in README.md for Vault Transit auto-unseal setup.
- Included Docker Swarm stack definition (docker-stack-vault.yml).
- Added Vault configuration templates and bootstrap scripts.
- Configured Gitea workflows for the VaultTest environment.
This commit is contained in:
Murat ÖZDEMİR 2026-05-27 01:48:30 +03:00
parent e978e52dcc
commit bf81b6ebee
7 changed files with 787 additions and 0 deletions

View File

@ -0,0 +1,107 @@
name: Deploy Vault to Production Environment
on:
push:
branches:
- prod-env
concurrency:
group: prod-deploy
cancel-in-progress: false
jobs:
deploy:
runs-on: prod-runner
steps:
- name: Checkout Branch
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
- name: Download Deploy Inputs
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 \
iklimco
- name: Verify Deployment
run: |
sleep 15
docker service ps iklimco_forecast-service \
--filter "desired-state=running" \
--format "table {{.Name}}\t{{.CurrentState}}\t{{.Image}}" | head -5

199
README.md
View File

@ -0,0 +1,199 @@
# ADIM 1 — Placeholder secrets oluştur (manager node)
```bash
# opsiyonel history reset
history -w && > ~/.bash_history && history -c
echo "bootstrap" | docker secret create vault_transit_unseal_key -
echo "bootstrap" | docker secret create transit_master_token -
```
# ADIM 2 — Stack deploy et
```bash
docker node update --label-add vault_transit=true iklim-app-03
docker stack deploy --with-registry-auth -c docker-stack-vault.yml iklimco
```
Ana vault node'ları transit henüz hazır olmadığı için crash loop'a girer — beklenen durum.
# ADIM 3 — Transit vault'u initialize et
```bash
# Transit'in hangi node'da çalıştığını bul:
docker service ps iklimco_vault-transit
# O node'a SSH'la, sonra:
docker exec -it $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault operator init -key-shares=1 -key-threshold=1'
# Unseal Key 1 ve Initial Root Token'ı kaydet.
# Unseal Key 1: ........
#
# Initial Root Token: hvs.xxxxxxxxxx
#
# Vault initialized with 1 key shares and a key threshold of 1. Please securely
# distribute the key shares printed above. When the Vault is re-sealed,
# restarted, or stopped, you must supply at least 1 of these keys to unseal it
# before it can start servicing requests.
#
# Vault does not store the generated root key. Without at least 1 keys to
# reconstruct the root key, Vault will remain permanently sealed!
#
# It is possible to generate new unseal keys, provided you have a quorum of
# existing unseal keys shares. See "vault operator rekey" for more information.
```
Unseal Key 1: cS0HPNVl8/9r42SXxeq9Y4uokJP886UAeRQ/sBsBFnQ=
Initial Root Token: hvs.AReLHEa44pztSLBUqW2djdEv
# ADIM 4 — Transit'i manuel unseal et (sadece bu seferlik)
```bash
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal UNSEAL_KEY_1'
```
| Key | Value |
| ------------ | ------------------------------------ |
| Seal Type | shamir |
| Initialized | true |
| Sealed | false |
| Total Shares | 1 |
| Threshold | 1 |
| Version | 2.0.1 |
| Build Date | 2026-05-19T17:20:48Z |
| Storage Type | file |
| Cluster Name | vault-cluster-5bd8a332 |
| Cluster ID | b03a2f93-53b0-d32b-9762-c36a9d45df90 |
| HA Enabled | false |
# ADIM 5 — Transit engine kur
```bash
# Policy dosyasını host'ta oluştur, container'a kopyala:
cat > /tmp/autounseal-policy.hcl << 'EOF'
path "transit/encrypt/autounseal" { capabilities = ["update"] }
path "transit/decrypt/autounseal" { capabilities = ["update"] }
EOF
docker cp /tmp/autounseal-policy.hcl \
$(docker ps -q -f name=iklimco_vault-transit):/tmp/
# Successfully copied 128B (transferred 2.05kB) to 61a136a1c04e:/tmp/
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault login ROOT_TOKEN'
```
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
(token -> root token)
| Key | Value |
| ----------------- | ---------------------------- |
| token | hvs.AReLHEa44pztSLBUqW2djdEv |
| token_accessor | 6w5ZKxbSSP3S5kz4D6luAmjv |
| token_duration | ∞ |
| token_renewable | false |
| token_policies | ["root"] |
| identity_policies | [] |
| policies | ["root"] |
```bash
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault secrets enable transit'
# Success! Enabled the transit secrets engine at: transit/
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault write -f transit/keys/autounseal'
```
| Key | Value |
| ---------------------- | ----------------- |
| allow_plaintext_backup | false |
| auto_rotate_period | 0s |
| deletion_allowed | false |
| derived | false |
| exportable | false |
| imported_key | false |
| keys | map[1:1779831017] |
| latest_version | 1 |
| min_available_version | 0 |
| min_decryption_version | 1 |
| min_encryption_version | 0 |
| name | autounseal |
| supports_decryption | true |
| supports_derivation | true |
| supports_encryption | true |
| supports_signing | false |
| type | aes256-gcm96 |
```bash
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault policy write autounseal /tmp/autounseal-policy.hcl'
# Success! Uploaded policy: autounseal
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault token create -policy=autounseal -period=768h -orphan'
```
(token -> auto unseal token)
| Key | Value |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| token | hvs.CAESIFqiceeloWSqHszPL8OY9PCFKpNQsh6NXoBxw_Us0w7gGh4KHGh2cy5XWTBXekE1VUNQcGhmNlE4U1F1RVhWOFo |
| token_accessor | mRgwI0az8UZguETf5iqJWXhb |
| token_duration | 768h |
| token_renewable | true |
| token_policies | ["autounseal" "default"] |
| identity_policies | [] |
| policies | ["autounseal" "default"] |
# ADIM 6 — Secrets'ı gerçek değerlerle güncelle (manager node'a dön)
```bash
# 6a. Transit unseal key — sırayla: servis'ten çıkar, sil, gerçek değerle oluştur, ekle
docker service update --secret-rm vault_transit_unseal_key iklimco_vault-transit
# iklimco_vault-transit
# overall progress: 1 out of 1 tasks
# 1/1: running [==================================================>]
# verify: Service iklimco_vault-transit converged
docker secret rm vault_transit_unseal_key
# vault_transit_unseal_key
echo "UNSEAL_KEY_1" | docker secret create vault_transit_unseal_key -
docker service update --secret-add vault_transit_unseal_key iklimco_vault-transit
# iklimco_vault-transit
# overall progress: 1 out of 1 tasks
# 1/1: running [==================================================>]
# verify: Service iklimco_vault-transit converged
# 6b. Transit'in unsealed olduğunu doğrula (iklim-app-03'te)
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault status'
# Sealed: false olmalı. Eğer hâlâ sealed ise manuel unseal et:
docker exec $(docker ps -q -f name=iklimco_vault-transit) \
sh -c 'VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal UNSEAL_KEY_1'
# 6c. Autounseal token — ATOMIC SWAP (vault hiç token'sız restart olmaz)
# DIKKAT: --secret-rm ve --secret-add AYNI komutta verilmeli
echo "hvs.AUTOUNSEAL_TOKEN" | docker secret create transit_master_token_v2 -
docker service update \
--secret-rm transit_master_token \
--secret-add source=transit_master_token_v2,target=transit_master_token \
iklimco_vault
```
# ADIM 7 — Ana vault cluster'ı initialize et
```bash
# Transit açıldıktan ve vault node'ları stable olduktan sonra (~1-2 dk):
docker service ps iklimco_vault # vault.1'in hangi node'da olduğunu bul
# O node'a SSH'la, sonra:
docker exec $(docker ps -q -f name=iklimco_vault.1) vault operator init
# Recovery Keys ve Root Token'ı kaydet. Bitti.
```

167
docker-stack-vault.yml Normal file
View File

@ -0,0 +1,167 @@
version: '3.8'
services:
# -------------------------------------------------------------------------
# 1. PRIMARY 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
vault:
image: hashicorp/vault:2.0.1
cap_add:
- IPC_LOCK
# Overriding the default entrypoint to manipulate configuration strictly in RAM memory
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
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"
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.
VAULT_SKIP_VERIFY: "true"
volumes:
# Persistent volume for primary Raft storage
- vault-data-vl:/vault/file
# Persistent volume for audit and operational logs
- vault-logs-vl:/vault/logs
# Read-only mount of the host directory where the real wildcard certificates are managed
- /opt/iklimco/ssl:/vault/certs:ro
configs:
# PHASE 1: Deploy with 'vault_template_v1' (TLS validation skipped for easy initialization)
# PHASE 2: Update this source to 'vault_template_v2' to enforce strict wildcard SSL verification
- source: vault_template_v2
target: /vault/config/vault.json
mode: 0444
secrets:
- transit_master_token
deploy:
mode: replicated
replicas: 3
placement:
# High Availability rule: Ensures Vault nodes are strictly scheduled across different physical Swarm workers
max_replicas_per_node: 1
constraints:
- node.labels.type == service
restart_policy:
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)
# =========================================================================
vault_template_v1:
file: ./vault-template-v1.json
# =========================================================================
# CONFIG TEMPLATE PHASE 2: Production Safe (Strict Wildcard SSL Enforcement)
# =========================================================================
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:
external: true
networks:
iklimco-net:
external: true

222
vault-bootstrap.sh Executable file
View File

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

View File

@ -0,0 +1,15 @@
{
"storage": {
"file": {
"path": "/vault/file"
}
},
"listener": {
"tcp": {
"address": "0.0.0.0:8200",
"tls_disable": 1
}
},
"disable_mlock": true,
"ui": false
}

36
vault-template-v1.json Normal file
View File

@ -0,0 +1,36 @@
{
"storage": {
"raft": {
"path": "/vault/file",
"node_id": "HOSTNAME_PLACEHOLDER",
"retry_join": [
{
"leader_api_addr": "https://vault.iklim.co:8200",
"tls_skip_verify": true
}
]
}
},
"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",
"cluster_address": "0.0.0.0:8201",
"tls_disable": 0,
"tls_cert_file": "/vault/certs/STAR.iklim.co.full.crt",
"tls_key_file": "/vault/certs/STAR.iklim.co_key.pem"
}
},
"api_addr": "https://HOSTNAME_PLACEHOLDER:8200",
"cluster_addr": "https://HOSTNAME_PLACEHOLDER:8201",
"disable_mlock": true,
"ui": true
}

41
vault-template-v2.json Normal file
View File

@ -0,0 +1,41 @@
{
"storage": {
"raft": {
"path": "/vault/file",
"node_id": "HOSTNAME_PLACEHOLDER",
"retry_join": [
{
"leader_api_addr": "https://vault.iklim.co:8200",
"tls_skip_verify": false,
"leader_ca_cert_file": "/vault/certs/STAR.iklim.co.full.crt",
"leader_client_cert_file": "/vault/certs/STAR.iklim.co.full.crt",
"leader_client_key_file": "/vault/certs/STAR.iklim.co_key.pem",
"leader_tls_servername": "vault.iklim.co"
}
]
}
},
"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",
"cluster_address": "0.0.0.0:8201",
"tls_disable": 0,
"tls_cert_file": "/vault/certs/STAR.iklim.co.full.crt",
"tls_key_file": "/vault/certs/STAR.iklim.co_key.pem",
"tls_client_ca_file": "/vault/certs/STAR.iklim.co.full.crt"
}
},
"api_addr": "https://HOSTNAME_PLACEHOLDER:8200",
"cluster_addr": "https://HOSTNAME_PLACEHOLDER:8201",
"disable_mlock": true,
"ui": true
}