Resources and access
terraform / check (push) Waiting to run

This commit is contained in:
stolzor committed 2026-10-11 21:04:27 +03:00
1 parent 64be99308b
commit 5cba142003
38 files changed
+565 -353

No files matched your search

+7 -4
View File
@@ -2,18 +2,21 @@
#MISE description="Запустить пример Go-сервиса: mise run example:up [сервис] [static|dynamic]"
set -euo pipefail
roles=$(terraform -chdir=vault output -json approle_role_ids)
service=${1:-$(jq -r 'keys[0]' <<<"$roles")}
config=$(echo 'jsonencode(yamldecode(file("config.yaml")))' | terraform console | jq -r .)
service=${1:-$(jq -r '[.services // {} | to_entries[] | select((.value.buckets // {} | length) > 0 and (.value.databases // [] | length) > 0) | .key][0] // empty' <<<"$config")}
[[ -n "$service" ]] || { echo "в config.yaml нет сервиса с бакетом и базой" >&2; exit 1; }
creds=${2:-static}
database=$(jq -r --arg s "$service" '.services[$s].databases[0] // empty' <<<"$config")
[[ -n "$database" ]] || { echo "у сервиса $service нет базы, пример рассчитан на сервис с базой и бакетом" >&2; exit 1; }
compose=(docker compose -f examples/go-service/compose.yml)
url=http://127.0.0.1:${EXAMPLE_PORT:-8090}/
role_id=$(jq -er --arg s "$service" '.[$s]' <<<"$roles") ||
role_id=$(terraform -chdir=vault output -json approle_role_ids | jq -er --arg s "$service" '.[$s]') ||
{ echo "нет роли AppRole для сервиса $service" >&2; exit 1; }
secret_id=$(curl -sS -X POST -H "X-Vault-Token: $VAULT_TOKEN" -d '{"ttl": "5m", "num_uses": 1}' \
"$VAULT_ADDR/v1/auth/approle/role/$service/secret-id" | jq -er .data.secret_id)
export SERVICE_NAME=$service VAULT_ROLE_ID=$role_id VAULT_SECRET_ID=$secret_id POSTGRES_CREDS=$creds
export SERVICE_NAME=$service DATABASE_NAME=$database VAULT_ROLE_ID=$role_id VAULT_SECRET_ID=$secret_id POSTGRES_CREDS=$creds
INFRA_NETWORK=$(docker compose config --format json | jq -er .networks.infra.name)
export INFRA_NETWORK
"${compose[@]}" up -d --build --force-recreate --quiet-pull 2>&1 | tail -3
+88 -55
View File
@@ -27,20 +27,40 @@ psql_as() {
psql -h postgres -U "$1" -d "$3" -Atc "select session_user || ' работает как ' || current_user" 2>&1 | tail -1 || true
}
mc_cfg=/tmp/app-check-$$
mc_app() { docker compose exec -T object-storage mc --config-dir "$mc_cfg" "$@"; }
token=""
revoke_token() {
[[ -z "$token" ]] || vault_api POST auth/token/revoke-self "$token" >/dev/null 2>&1 || true
token=""
}
trap revoke_token EXIT
cleanup() {
revoke_token
docker compose exec -T object-storage rm -rf "$mc_cfg" >/dev/null 2>&1 || true
}
trap cleanup EXIT
admin_token=$VAULT_TOKEN
CONFIG=$(echo 'jsonencode(yamldecode(file("config.yaml")))' | terraform console | jq -r .)
ROLES=$(terraform -chdir=vault output -json approle_role_ids)
mapfile -t ALL < <(jq -r 'keys[]' <<<"$ROLES")
mapfile -t ALL < <(jq -r '.services // {} | keys[]' <<<"$CONFIG")
mapfile -t ALL_BUCKETS < <(jq -r '.buckets // {} | keys[]' <<<"$CONFIG")
mapfile -t ALL_DBS < <(jq -r '.databases // {} | keys[]' <<<"$CONFIG")
if [[ $# -gt 0 ]]; then SERVICES=("$@"); else SERVICES=("${ALL[@]}"); fi
contains() {
local needle=$1 item
shift
for item in "$@"; do [[ "$item" == "$needle" ]] && return 0; done
return 1
}
for svc in "${SERVICES[@]}"; do
printf '\n################ %s\n' "$svc"
mapfile -t buckets < <(jq -r --arg s "$svc" '.services[$s].buckets // {} | keys[]' <<<"$CONFIG")
mapfile -t ro_buckets < <(jq -r --arg s "$svc" '.services[$s].buckets // {} | to_entries[] | select(.value == "ro") | .key' <<<"$CONFIG")
mapfile -t dbs < <(jq -r --arg s "$svc" '.services[$s].databases // [] | .[]' <<<"$CONFIG")
role_id=$(jq -r --arg s "$svc" '.[$s] // empty' <<<"$ROLES")
[[ -n "$role_id" ]] || fail "нет роли AppRole для $svc"
@@ -53,83 +73,96 @@ for svc in "${SERVICES[@]}"; do
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"
step "2. S3: $KV/$svc/s3"
r=$(vault_api GET "$KV/data/$svc/s3" "$token")
if [[ $(code "$r") == 200 ]]; then
if [[ ${#buckets[@]} -eq 0 ]]; then
[[ $(code "$r") == 404 ]] || fail "у сервиса нет бакетов, но секрет есть"
ok "бакетов нет, секрета нет"
else
[[ $(code "$r") == 200 ]] || fail "секрет не читается: HTTP $(code "$r")"
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 доступен"
mc_app alias set app http://localhost:9000 "$(jq -r .access_key <<<"$s3")" "$(jq -r .secret_key <<<"$s3")" >/dev/null
for b in "${buckets[@]}"; do
mc_app ls "app/$b" >/dev/null 2>&1 || fail "свой бакет $b недоступен"
if contains "$b" "${ro_buckets[@]}"; then
out=$(echo check | mc_app pipe "app/$b/.app-check" 2>&1 || true)
[[ "$out" == *"Access Denied"* || "$out" == *"Insufficient permissions"* ]] ||
fail "запись в бакет $b с доступом ro прошла: $out"
ok "бакет $b: чтение есть, запись запрещена"
else
ok "бакет $b доступен"
fi
done
for b in "${ALL_BUCKETS[@]}"; do
contains "$b" "${buckets[@]}" && continue
out=$(mc_app ls "app/$b" 2>&1 || true)
[[ "$out" == *"Access Denied"* ]] || fail "чужой бакет $b доступен: $out"
ok "чужой бакет $b: Access Denied"
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
step "3. Postgres"
dyn_users=()
if [[ ${#dbs[@]} -eq 0 ]]; then
ok "баз нет"
fi
for db in "${dbs[@]}"; do
r=$(vault_api GET "$KV/data/$svc/postgres/$db" "$token")
[[ $(code "$r") == 200 ]] || fail "секрет $KV/$svc/postgres/$db не читается: HTTP $(code "$r")"
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
out=$(psql_as "$(jq -r .username <<<"$pg")" "$(jq -r .password <<<"$pg")" "$db")
[[ "$out" == *"работает как ${db}_owner" ]] || fail "постоянная роль не подключилась к $db: $out"
ok "$db, постоянная роль: $out"
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")"
r=$(vault_api GET "$DB/creds/$svc-$db" "$token")
[[ $(code "$r") == 200 ]] || fail "Vault не выдал временную роль для $db: $(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
dyn_users+=("$dyn_user")
out=$(psql_as "$dyn_user" "$dyn_pass" "$db")
[[ "$out" == *"работает как ${db}_owner" ]] || fail "временная роль не подключилась к $db: $out"
ok "$db, временная роль на $(body "$r" | jq -r .lease_duration)s: $out"
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"
for other in "${ALL_DBS[@]}"; do
contains "$other" "${dbs[@]}" && continue
out=$(psql_as "$dyn_user" "$dyn_pass" "$other")
[[ "$out" == *"CONNECT privilege"* ]] || fail "роль попала в чужую базу $other: $out"
ok "чужая база $other: нет права CONNECT"
done
done
step "4. Чужие секреты"
for db in "${ALL_DBS[@]}"; do
contains "$db" "${dbs[@]}" && continue
[[ $(code "$(vault_api GET "$DB/creds/$svc-$db" "$token")") == 403 ]] || fail "сервис получил роль для чужой базы $db"
ok "$DB/creds/$svc-$db: 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
paths=("$KV/data/$other/s3")
while read -r db; do
[[ -n "$db" ]] && paths+=("$KV/data/$other/postgres/$db" "$DB/creds/$other-$db")
done < <(jq -r --arg s "$other" '.services[$s].databases // [] | .[]' <<<"$CONFIG")
for path in "${paths[@]}"; do
[[ $(code "$(vault_api GET "$path" "$token")") == 403 ]] || fail "сервис прочитал $path"
ok "$path: 403"
done
done
step "6. Отзыв токена"
step "5. Отзыв токена"
old_token=$token
revoke_token
[[ $(code "$(vault_api GET "$KV/data/$svc/s3" "$old_token")") == 403 ]] || fail "токен всё ещё работает"
ok "токен больше не принимается"
if [[ -n "${dyn_user:-}" ]]; then
if [[ ${#dyn_users[@]} -gt 0 ]]; 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=""
for dyn_user in "${dyn_users[@]}"; do
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"
done
ok "временные роли удалены вместе с токеном"
fi
done