137 lines
5.8 KiB
Bash
Executable File
137 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#MISE description="Проверка глазами сервиса: вход по AppRole, секреты из Vault, доступ к S3 и Postgres"
|
|
set -euo pipefail
|
|
|
|
KV=$VAULT_MOUNT
|
|
DB=$VAULT_DATABASE_MOUNT
|
|
|
|
step() { printf '\n== %s\n' "$*"; }
|
|
ok() { printf ' ok: %s\n' "$*"; }
|
|
fail() {
|
|
printf ' ОШИБКА: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
vault_api() {
|
|
local method=$1 path=$2 token=${3:-} body=${4:-}
|
|
curl -sS -X "$method" -w '\n%{http_code}' \
|
|
${token:+-H "X-Vault-Token: $token"} \
|
|
${body:+-d "$body"} \
|
|
"$VAULT_ADDR/v1/$path"
|
|
}
|
|
code() { tail -n1 <<<"$1"; }
|
|
body() { sed '$d' <<<"$1"; }
|
|
|
|
psql_as() {
|
|
docker compose exec -T -e PGPASSWORD="$2" postgres \
|
|
psql -h postgres -U "$1" -d "$3" -Atc "select session_user || ' работает как ' || current_user" 2>&1 | tail -1 || true
|
|
}
|
|
|
|
token=""
|
|
revoke_token() {
|
|
[[ -z "$token" ]] || vault_api POST auth/token/revoke-self "$token" >/dev/null 2>&1 || true
|
|
token=""
|
|
}
|
|
trap revoke_token EXIT
|
|
|
|
admin_token=$VAULT_TOKEN
|
|
ROLES=$(terraform -chdir=vault output -json approle_role_ids)
|
|
mapfile -t ALL < <(jq -r 'keys[]' <<<"$ROLES")
|
|
if [[ $# -gt 0 ]]; then SERVICES=("$@"); else SERVICES=("${ALL[@]}"); fi
|
|
|
|
for svc in "${SERVICES[@]}"; do
|
|
printf '\n################ %s\n' "$svc"
|
|
|
|
role_id=$(jq -r --arg s "$svc" '.[$s] // empty' <<<"$ROLES")
|
|
[[ -n "$role_id" ]] || fail "нет роли AppRole для $svc"
|
|
secret_id=$(vault_api POST "auth/approle/role/$svc/secret-id" "$admin_token" '{"ttl": "5m", "num_uses": 1}' |
|
|
sed '$d' | jq -r .data.secret_id)
|
|
|
|
step "1. Вход через AppRole"
|
|
r=$(vault_api POST auth/approle/login "" "$(jq -n --arg r "$role_id" --arg s "$secret_id" '{role_id: $r, secret_id: $s}')")
|
|
[[ $(code "$r") == 200 ]] || fail "вход не удался: $(body "$r")"
|
|
token=$(body "$r" | jq -r .auth.client_token)
|
|
ok "токен получен, политики: $(body "$r" | jq -r '.auth.policies | join(", ")'), ttl: $(body "$r" | jq -r .auth.lease_duration)s"
|
|
|
|
step "2. Ключи S3 из $KV/$svc/s3"
|
|
r=$(vault_api GET "$KV/data/$svc/s3" "$token")
|
|
if [[ $(code "$r") == 200 ]]; then
|
|
s3=$(body "$r" | jq .data.data)
|
|
ok "поля: $(jq -r 'keys | join(", ")' <<<"$s3")"
|
|
cfg=/tmp/app-check-$$
|
|
docker compose exec -T object-storage mc --config-dir "$cfg" alias set app http://localhost:9000 \
|
|
"$(jq -r .access_key <<<"$s3")" "$(jq -r .secret_key <<<"$s3")" >/dev/null
|
|
IFS=, read -ra own <<<"$(jq -r .buckets <<<"$s3")"
|
|
for b in "${own[@]}"; do
|
|
docker compose exec -T object-storage mc --config-dir "$cfg" ls "app/$b" >/dev/null 2>&1 || fail "свой бакет $b недоступен"
|
|
ok "бакет $b доступен"
|
|
done
|
|
docker compose exec -T object-storage rm -rf "$cfg"
|
|
else
|
|
ok "секрета нет (у сервиса нет бакетов), HTTP $(code "$r")"
|
|
fi
|
|
|
|
step "3. Постоянные учётные данные Postgres из $KV/$svc/postgres"
|
|
r=$(vault_api GET "$KV/data/$svc/postgres" "$token")
|
|
own_db=""
|
|
if [[ $(code "$r") == 200 ]]; then
|
|
pg=$(body "$r" | jq .data.data)
|
|
own_db=$(jq -r .database <<<"$pg")
|
|
ok "поля: $(jq -r 'keys | join(", ")' <<<"$pg")"
|
|
out=$(psql_as "$(jq -r .username <<<"$pg")" "$(jq -r .password <<<"$pg")" "$own_db")
|
|
[[ "$out" == *"работает как"* ]] || fail "не подключился к $own_db: $out"
|
|
ok "$own_db: $out"
|
|
else
|
|
ok "секрета нет (у сервиса нет базы), HTTP $(code "$r")"
|
|
fi
|
|
|
|
step "4. Временные учётные данные Postgres из $DB/creds/$svc"
|
|
if [[ -n "$own_db" ]]; then
|
|
r=$(vault_api GET "$DB/creds/$svc" "$token")
|
|
[[ $(code "$r") == 200 ]] || fail "Vault не выдал временную роль: $(body "$r")"
|
|
dyn_user=$(body "$r" | jq -r .data.username)
|
|
dyn_pass=$(body "$r" | jq -r .data.password)
|
|
ok "выдана роль $dyn_user на $(body "$r" | jq -r .lease_duration)s"
|
|
out=$(psql_as "$dyn_user" "$dyn_pass" "$own_db")
|
|
[[ "$out" == *"работает как"* ]] || fail "временная роль не подключилась к $own_db: $out"
|
|
ok "$own_db: $out"
|
|
for other in $(docker compose exec -T postgres psql -U "$POSTGRES_USER" -d postgres -Atc \
|
|
"select datname from pg_database where datdba <> 10 and datname <> '$own_db'"); do
|
|
out=$(psql_as "$dyn_user" "$dyn_pass" "$other")
|
|
[[ "$out" == *"CONNECT privilege"* ]] || fail "временная роль попала в чужую базу $other: $out"
|
|
ok "$other: отказ, нет права CONNECT"
|
|
done
|
|
else
|
|
ok "пропущено, у сервиса нет базы"
|
|
fi
|
|
|
|
step "5. Чужие секреты"
|
|
for path in "$KV/data/infra/postgres" "$KV/data/infra/silo"; do
|
|
[[ $(code "$(vault_api GET "$path" "$token")") == 403 ]] || fail "сервис прочитал $path"
|
|
ok "$path: 403"
|
|
done
|
|
for other in "${ALL[@]}"; do
|
|
[[ "$other" == "$svc" ]] && continue
|
|
for path in "$KV/data/$other/s3" "$KV/data/$other/postgres" "$DB/creds/$other"; do
|
|
[[ $(code "$(vault_api GET "$path" "$token")") == 403 ]] || fail "сервис прочитал $path"
|
|
ok "$path: 403"
|
|
done
|
|
done
|
|
|
|
step "6. Отзыв токена"
|
|
old_token=$token
|
|
revoke_token
|
|
[[ $(code "$(vault_api GET "$KV/data/$svc/s3" "$old_token")") == 403 ]] || fail "токен всё ещё работает"
|
|
ok "токен больше не принимается"
|
|
if [[ -n "${dyn_user:-}" ]]; then
|
|
sleep 2
|
|
left=$(docker compose exec -T postgres psql -U "$POSTGRES_USER" -d postgres -Atc \
|
|
"select count(*) from pg_roles where rolname = '$dyn_user'")
|
|
[[ "$left" == 0 ]] || fail "временная роль $dyn_user осталась в Postgres"
|
|
ok "временная роль $dyn_user удалена вместе с токеном"
|
|
dyn_user=""
|
|
fi
|
|
done
|
|
|
|
printf '\nВсе проверки пройдены.\n'
|