diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml new file mode 100644 index 0000000..2edac30 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml @@ -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:"; exit 1 ;; + esac + case "$PROD_IMAGE_TAG" in + *-rc*) echo "❌ PROD_IMAGE_TAG must not be an rc tag"; exit 1 ;; + esac + echo "source_image_digest=$SOURCE_IMAGE_DIGEST" >> $GITHUB_OUTPUT + echo "prod_image=registry.tarla.io/iklimco/forecast-service:$PROD_IMAGE_TAG" >> $GITHUB_OUTPUT + echo "🏷️ Promote: $SOURCE_IMAGE_DIGEST -> registry.tarla.io/iklimco/forecast-service:$PROD_IMAGE_TAG" + + - name: Promote Harbor Image + run: | + echo "${{ secrets.HARBOR_CI_TOKEN }}" | \ + docker login registry.tarla.io -u robot-ci-push-iklimco --password-stdin + docker pull "${{ steps.manifest.outputs.source_image_digest }}" + docker tag "${{ steps.manifest.outputs.source_image_digest }}" "${{ steps.manifest.outputs.prod_image }}" + docker push "${{ steps.manifest.outputs.prod_image }}" + echo "βœ… Promoted: ${{ steps.manifest.outputs.prod_image }}" + + - name: Docker Login for Swarm Pull + run: | + echo "${{ secrets.HARBOR_PULL_TOKEN }}" | \ + docker login registry.tarla.io -u robot-swarm-pull-iklimco --password-stdin + + - name: Update Swarm Service + run: | + set -a; . ./.env; . ./.env.secrets.forecast; set +a + SERVICE_IMAGE="${{ steps.manifest.outputs.prod_image }}" \ + docker stack deploy \ + --with-registry-auth \ + -c deploy/docker-stack-service.yml \ + -c deploy/docker-stack-service.prod.yml \ + iklimco + + - name: Verify Deployment + run: | + sleep 15 + docker service ps iklimco_forecast-service \ + --filter "desired-state=running" \ + --format "table {{.Name}}\t{{.CurrentState}}\t{{.Image}}" | head -5 diff --git a/README.md b/README.md index e69de29..7c0ee85 100644 --- a/README.md +++ b/README.md @@ -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. +``` diff --git a/docker-stack-vault.yml b/docker-stack-vault.yml new file mode 100644 index 0000000..e9bc5da --- /dev/null +++ b/docker-stack-vault.yml @@ -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 "" | docker secret create vault_transit_unseal_key - + # docker service update --secret-add vault_transit_unseal_key iklimco_vault-transit + # Step 5: (exec into container) unseal, enable transit engine, create autounseal key, + # create a dedicated policy token, then update the transit_master_token secret: + # vault operator unseal + # vault login + # vault secrets enable transit + # vault write -f transit/keys/autounseal + # vault policy write autounseal - < docker secret rm transit_master_token + # -> echo "" | docker secret create transit_master_token - + # NORMAL OPERATION: auto-unseals on restart using vault_transit_unseal_key + # NODE PINNING (recommended): pin to one node to prevent volume data loss on rescheduling + # docker node update --label-add vault_transit=true + vault-transit: + image: hashicorp/vault:2.0.1 + cap_add: + - IPC_LOCK + entrypoint: ["sh", "-c"] + # 1. Starts vault in background with persistent file storage config + # 2. Registers SIGTERM/SIGINT trap so Docker stop triggers graceful vault shutdown + # 3. Polls vault status; exit 1 = not ready yet, exit 0/2 = vault is responding + # 4. Auto-unseals using vault_transit_unseal_key secret (no-op when uninitialized) + # 5. Waits for vault to exit and propagates its exit code to Docker + command: > + "vault server -config=/vault/config/transit.json & + VAULT_PID=$$!; + trap 'kill -TERM $$VAULT_PID; wait $$VAULT_PID' TERM INT; + export VAULT_ADDR='http://127.0.0.1:8200'; + for i in 1 2 3 4 5 6 7 8 9 10; do vault status > /dev/null 2>&1; [ $$? -ne 1 ] && break; sleep 2; done; + vault operator unseal $$(cat /run/secrets/vault_transit_unseal_key) > /dev/null 2>&1 || true; + wait $$VAULT_PID" + networks: + - iklimco-net + secrets: + - vault_transit_unseal_key + volumes: + - vault-transit-data-vl:/vault/file + configs: + - source: vault_transit_config + target: /vault/config/transit.json + mode: 0444 + deploy: + mode: replicated + replicas: 1 + placement: + constraints: + - node.labels.type == service + - node.labels.vault_transit == true + restart_policy: + condition: any + delay: 5s + +configs: + # ========================================================================= + # CONFIG TEMPLATE PHASE 1: Bootstrap / Initial Discovery (No Verification) + # ========================================================================= + 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 diff --git a/vault-bootstrap.sh b/vault-bootstrap.sh new file mode 100755 index 0000000..be84008 --- /dev/null +++ b/vault-bootstrap.sh @@ -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 "════════════════════════════════════════════════" diff --git a/vault-template-transit.json b/vault-template-transit.json new file mode 100644 index 0000000..55c4754 --- /dev/null +++ b/vault-template-transit.json @@ -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 +} diff --git a/vault-template-v1.json b/vault-template-v1.json new file mode 100644 index 0000000..b278f44 --- /dev/null +++ b/vault-template-v1.json @@ -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 +} diff --git a/vault-template-v2.json b/vault-template-v2.json new file mode 100644 index 0000000..2829297 --- /dev/null +++ b/vault-template-v2.json @@ -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 +}