#!/usr/bin/env bash #MISE description="Токен Terraform для Vault, проверка политик, отзыв root-токена (нужен ROOT_TOKEN)" set -euo pipefail MOUNT=$VAULT_MOUNT ENV_FILE=.env KEYS_FILE=vault_keys.txt REVOKE_ROOT=false [[ "${1:-}" == "--revoke-root" ]] && REVOKE_ROOT=true bao_as() { local token=$1 shift docker compose exec -T -e BAO_TOKEN="$token" vault bao "$@" } step() { printf '\n== %s\n' "$*"; } ok() { printf ' ok: %s\n' "$*"; } fail() { printf ' ОШИБКА: %s\n' "$*" >&2 exit 1 } ROOT_TOKEN=${ROOT_TOKEN:-$(sed -n 's/^Initial Root Token: *//p' "$KEYS_FILE" 2>/dev/null || true)} [[ -n "$ROOT_TOKEN" ]] || fail "нет root-токена: задайте ROOT_TOKEN или положите $KEYS_FILE" bao_as "$ROOT_TOKEN" token lookup >/dev/null 2>&1 || fail "root-токен не принят (отозван или Vault запечатан)" step "Шаг 1. Токен для Terraform" TF_TOKEN=${VAULT_TOKEN:-} if [[ -n "$TF_TOKEN" ]] && bao_as "$TF_TOKEN" token lookup -format=json 2>/dev/null | jq -e '.data.policies | index("terraform")' >/dev/null; then ok "в $ENV_FILE уже есть рабочий токен, новый не создаю" else TF_TOKEN=$(bao_as "$ROOT_TOKEN" token create -orphan -policy=terraform -period=720h \ -display-name=terraform -format=json | jq -r .auth.client_token) [[ -n "$TF_TOKEN" && "$TF_TOKEN" != null ]] || fail "токен не создан" sed -i '/^VAULT_TOKEN=/d' "$ENV_FILE" printf 'VAULT_TOKEN=%s\n' "$TF_TOKEN" >>"$ENV_FILE" ok "токен создан и записан в $ENV_FILE как VAULT_TOKEN" fi bao_as "$TF_TOKEN" token lookup -format=json | jq -r '.data | " политики: \(.policies | join(", ")); orphan: \(.orphan); period: \(.period)s"' step "Шаг 2. terraform plan в vault/ с новым токеном" set +e TF_VAR_vault_token=$TF_TOKEN terraform -chdir=vault plan -input=false -no-color -detailed-exitcode >/tmp/vault-plan.$$ 2>&1 PLAN_EXIT=$? set -e case $PLAN_EXIT in 0) ok "No changes: политики terraform хватает" ;; 2) grep -E '^ # |^Plan:' /tmp/vault-plan.$$ || true rm -f /tmp/vault-plan.$$ fail "plan хочет что-то изменить, прочитайте его: terraform -chdir=vault plan" ;; *) grep -vE 'Refreshing state' /tmp/vault-plan.$$ | tail -30 rm -f /tmp/vault-plan.$$ fail "plan упал, скорее всего в политике terraform не хватает пути из ошибки выше" ;; esac rm -f /tmp/vault-plan.$$ step "Шаг 3. Границы сервисов через AppRole" ROLES_JSON=$(terraform -chdir=vault output -json approle_role_ids) mapfile -t SERVICES < <(jq -r 'keys[]' <<<"$ROLES_JSON") cleanup() { for svc in "${SERVICES[@]}"; do bao_as "$ROOT_TOKEN" kv metadata delete -mount="$MOUNT" "$svc/bootstrap-check" >/dev/null 2>&1 || true done } trap cleanup EXIT for svc in "${SERVICES[@]}"; do bao_as "$ROOT_TOKEN" kv put -mount="$MOUNT" "$svc/bootstrap-check" ok=1 >/dev/null done for svc in "${SERVICES[@]}"; do role_id=$(jq -r --arg s "$svc" '.[$s]' <<<"$ROLES_JSON") secret_id=$(bao_as "$ROOT_TOKEN" write -f -field=secret_id "auth/approle/role/$svc/secret-id") svc_token=$(bao_as "$ROOT_TOKEN" write -field=token auth/approle/login role_id="$role_id" secret_id="$secret_id") for target in "${SERVICES[@]}"; do set +e out=$(bao_as "$svc_token" kv get -mount="$MOUNT" "$target/bootstrap-check" 2>&1) rc=$? set -e if [[ "$target" == "$svc" ]]; then [[ $rc -eq 0 ]] || fail "$svc не читает свой путь $MOUNT/$target: $out" ok "$svc читает $MOUNT/$target/*" else [[ $rc -ne 0 && "$out" == *"permission denied"* ]] || fail "$svc прочитал чужой путь $MOUNT/$target" ok "$svc получает permission denied на $MOUNT/$target/*" fi done bao_as "$svc_token" token revoke -self >/dev/null done step "Шаг 4. Root-токен" if $REVOKE_ROOT; then cleanup trap - EXIT bao_as "$ROOT_TOKEN" token revoke -self >/dev/null ok "root-токен отозван; новый выпускается через bao operator generate-root с unseal-ключом" else ok "не отозван (запустите с --revoke-root, когда будете готовы)" fi