From 0bb32ccabdead61d16c55b2fd8e02b958fe9f5eb Mon Sep 17 00:00:00 2001 From: Stephan D Date: Fri, 7 Nov 2025 11:15:57 +0100 Subject: [PATCH] vault app_role secrets pass --- .woodpecker/db.yml | 19 +++++++++++-------- ci/prod/compose/db.yml | 20 +++++++++++++++++--- ci/prod/scripts/deploy-db.sh | 31 +++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/.woodpecker/db.yml b/.woodpecker/db.yml index c02526e..cd291fb 100644 --- a/.woodpecker/db.yml +++ b/.woodpecker/db.yml @@ -6,6 +6,7 @@ steps: - name: version image: alpine:latest commands: + - set -euo pipefail - apk add --no-cache git - GIT_REV="$(git rev-parse --short HEAD)" - BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)" @@ -16,17 +17,22 @@ steps: image: alpine:latest depends_on: [ version ] environment: + # Vault access for CI (AppRole for CI itself, NOT the app AppRole) VAULT_ADDR: https://vault.sendico.io - VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE } - VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID } + VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE } # CI's AppRole role_id + VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID } # CI's AppRole secret_id commands: + - set -euo pipefail - apk add --no-cache curl bash coreutils sed python3 openssh-keygen - mkdir -p secrets - # fetch SSH private key for deploy (base64-encoded) and decode + # Fetch SSH private key for deploy (base64-encoded) and decode - ./ci/vlt kv_to_file kv ops/deploy/ssh_key private_b64 secrets/SSH_KEY.b64 600 - base64 -d secrets/SSH_KEY.b64 > secrets/SSH_KEY - chmod 600 secrets/SSH_KEY - ssh-keygen -y -f secrets/SSH_KEY >/dev/null + # Fetch AppRole creds for Vault Agent (adjust the KV path if different) + - ./ci/vlt kv_to_file kv ops/vault/approle/sendico-db role_id secrets/ROLE_ID 600 + - ./ci/vlt kv_to_file kv ops/vault/approle/sendico-db secret_id secrets/SECRET_ID 600 - name: deploy image: alpine:latest @@ -36,14 +42,11 @@ steps: - apk add --no-cache bash openssh-client rsync coreutils - mkdir -p /root/.ssh - install -m 600 secrets/SSH_KEY /root/.ssh/id_rsa + # Normalize CRLF if any, then export runtime env (Compose variables) - sed -i 's/\r$//' ./ci/prod/.env.runtime - set -a - . ./ci/prod/.env.runtime - . ./.env.version - set +a + # Run external deploy script (quiet by default; set DEBUG_DEPLOY=1 to debug) - bash ci/prod/scripts/deploy-db.sh - - - - - diff --git a/ci/prod/compose/db.yml b/ci/prod/compose/db.yml index 581f82d..172f321 100644 --- a/ci/prod/compose/db.yml +++ b/ci/prod/compose/db.yml @@ -8,12 +8,14 @@ volumes: mongo1_data: {} mongo2_data: {} mongo3_data: {} + # In-memory store for secrets/material rendered by Vault Agent (no host persistence) vault_secrets: driver: local driver_opts: type: tmpfs device: tmpfs o: size=32m,uid=999,gid=999,mode=0750 + # In-memory config for PBM (renders from templates, no host persistence) pbm_cfg: driver: local driver_opts: @@ -28,16 +30,24 @@ services: container_name: vault-agent-sendico restart: unless-stopped cap_add: ["IPC_LOCK"] + # Only static env here. AppRole creds are injected via CI at runtime. environment: VAULT_ADDR: ${VAULT_ADDR} + VAULT_ROLE_ID: ${VAULT_ROLE_ID} # provided only during `docker compose up` + VAULT_SECRET_ID: ${VAULT_SECRET_ID} # provided only during `docker compose up` volumes: - ./vault/agent.hcl:/etc/vault/agent.hcl:ro - ./vault/templates:/etc/vault/templates:ro - - /opt/sendico/vault/sendico-db/role_id:/vault/role_id:ro - - /opt/sendico/vault/sendico-db/secret_id:/vault/secret_id:ro - vault_secrets:/vault/secrets:rw - pbm_cfg:/etc/backup:rw - command: sh -lc 'vault agent -config=/etc/vault/agent.hcl' + # Write AppRole creds to tmpfs, drop them from env, then exec agent + command: > + sh -lc 'set -euo pipefail; umask 077; + : "${VAULT_ROLE_ID:?}"; : "${VAULT_SECRET_ID:?}"; + printf "%s" "$VAULT_ROLE_ID" > /vault/secrets/role_id; + printf "%s" "$VAULT_SECRET_ID" > /vault/secrets/secret_id; + unset VAULT_ROLE_ID VAULT_SECRET_ID; + exec vault agent -config=/etc/vault/agent.hcl' healthcheck: test: ["CMD-SHELL","test -s /vault/secrets/MONGO_INITDB_ROOT_USERNAME -a -s /vault/secrets/MONGO_INITDB_ROOT_PASSWORD -a -s /vault/secrets/mongo.kf -a -s /etc/backup/pbm.env -a -s /etc/backup/.u -a -s /etc/backup/.p"] interval: 5s @@ -120,6 +130,7 @@ services: - vault_secrets:/vault/secrets:ro entrypoint: | bash -c ' + set -euo pipefail u=$(cat /vault/secrets/MONGO_INITDB_ROOT_USERNAME) p=$(cat /vault/secrets/MONGO_INITDB_ROOT_PASSWORD) until mongosh --quiet --host sendico_db1 --port ${MONGO_PORT} --eval "db.adminCommand({ ping: 1 })"; do @@ -152,6 +163,7 @@ services: - pbm_cfg:/etc/backup:ro command: | sh -lc ' + set -euo pipefail . /etc/backup/pbm.env U=$$(cat /etc/backup/.u) ; P=$$(cat /etc/backup/.p) export AWS_EC2_METADATA_DISABLED=true @@ -171,6 +183,7 @@ services: - pbm_cfg:/etc/backup:ro command: | sh -lc ' + set -euo pipefail . /etc/backup/pbm.env U=$$(cat /etc/backup/.u) ; P=$$(cat /etc/backup/.p) export AWS_EC2_METADATA_DISABLED=true @@ -190,6 +203,7 @@ services: - pbm_cfg:/etc/backup:ro command: | sh -lc ' + set -euo pipefail . /etc/backup/pbm.env U=$$(cat /etc/backup/.u) ; P=$$(cat /etc/backup/.p) export AWS_EC2_METADATA_DISABLED=true diff --git a/ci/prod/scripts/deploy-db.sh b/ci/prod/scripts/deploy-db.sh index 5016a55..c100f75 100755 --- a/ci/prod/scripts/deploy-db.sh +++ b/ci/prod/scripts/deploy-db.sh @@ -4,7 +4,7 @@ set -euo pipefail [[ "${DEBUG_DEPLOY:-0}" = "1" ]] && set -x trap 'echo "[deploy-db] error at line $LINENO" >&2' ERR -# Required environment variables (must be exported by the CI step) +# Required env (exported by CI step from .env.runtime) : "${REMOTE_BASE:?missing REMOTE_BASE}" : "${DB_DIR:?missing DB_DIR}" : "${SSH_USER:?missing SSH_USER}" @@ -29,20 +29,35 @@ fi RSYNC_FLAGS=(-az --delete) [[ "${DEBUG_DEPLOY:-0}" = "1" ]] && RSYNC_FLAGS=(-avz --delete) -# Create remote directories and sync files +# AppRole creds for Vault Agent, fetched in the previous pipeline step +VAULT_ROLE_ID="$(cat secrets/ROLE_ID)" +VAULT_SECRET_ID="$(cat secrets/SECRET_ID)" + +# Create remote directory structure ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p ${REMOTE_DIR}/{compose,env}" + +# Sync compose bundle and runtime env to the remote host rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/compose/ "$REMOTE_TARGET:${REMOTE_DIR}/compose/" rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/.env.runtime "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.runtime" -# Deploy on remote host (use 'bash -s' so heredoc is executed remotely) -ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" REMOTE_DIR="$REMOTE_DIR" bash -s <<'EOSSH' +# Deploy on remote: export AppRole creds ONLY to the compose commands. +# The vault-agent container will read them, write to tmpfs, unset, and exec. +ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \ + REMOTE_DIR="$REMOTE_DIR" \ + VAULT_ROLE_ID="$VAULT_ROLE_ID" \ + VAULT_SECRET_ID="$VAULT_SECRET_ID" \ + bash -s <<'EOSSH' set -euo pipefail cd "${REMOTE_DIR}/compose" set -a; . ../env/.env.runtime; set +a -# Pull quietly if supported; otherwise fall back to normal -docker compose -f db.yml pull --quiet 2>/dev/null || docker compose -f db.yml pull -docker compose -f db.yml up -d --remove-orphans -docker compose ps + +# Pull and start with ephemeral AppRole env present only for these invocations +VAULT_ROLE_ID="${VAULT_ROLE_ID}" VAULT_SECRET_ID="${VAULT_SECRET_ID}" docker compose -f db.yml pull --quiet 2>/dev/null || \ +VAULT_ROLE_ID="${VAULT_ROLE_ID}" VAULT_SECRET_ID="${VAULT_SECRET_ID}" docker compose -f db.yml pull + +VAULT_ROLE_ID="${VAULT_ROLE_ID}" VAULT_SECRET_ID="${VAULT_SECRET_ID}" docker compose -f db.yml up -d --remove-orphans + +docker compose -f db.yml ps date -Is > .last_deploy logger -t deploy-db "db deployed at $(date -Is) in ${REMOTE_DIR}" EOSSH