1 parent
64be99308b
commit
5cba142003
38 files changed
+565
-353
No files matched your search
@@ -60,40 +60,59 @@ docker compose exec vault bao status # Sealed должно быть f
|
||||
|
||||
Секреты из `.env` превращаются в переменные, которые ждут инструменты (`TF_VAR_*` для Terraform, `AWS_*` для backend), в секции `[env]` файла `mise.toml`.
|
||||
|
||||
## Как добавить сервис
|
||||
## Бакеты, базы и сервисы
|
||||
|
||||
1. Описать его в `config.yaml`:
|
||||
В `config.yaml` отдельно описано, что существует, и отдельно — кто имеет к этому доступ:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
orders:
|
||||
postgres:
|
||||
username: orders
|
||||
name: orders
|
||||
pgvector: true # необязательно, по умолчанию false
|
||||
buckets:
|
||||
orders-files:
|
||||
versioning: true # по умолчанию true
|
||||
noncurrent_days: 30 # сколько хранить старые версии
|
||||
```
|
||||
```yaml
|
||||
buckets:
|
||||
orders-files:
|
||||
versioning: true
|
||||
noncurrent_days: 30
|
||||
reports:
|
||||
versioning: false
|
||||
expire_days: 90
|
||||
|
||||
Блоки `postgres` и `buckets` необязательны: сервису можно дать только базу или только бакеты.
|
||||
databases:
|
||||
orders:
|
||||
pgvector: true
|
||||
analytics: {}
|
||||
|
||||
2. Применить по очереди, читая `plan` перед каждым `apply`:
|
||||
services:
|
||||
orders:
|
||||
buckets:
|
||||
orders-files: rw
|
||||
reports: ro
|
||||
databases: [orders, analytics]
|
||||
```
|
||||
|
||||
```bash
|
||||
mise run tf vault apply # политика и роль AppRole
|
||||
mise run tf storage apply # бакеты и ключи S3
|
||||
mise run tf database apply # база, роль, пароли
|
||||
```
|
||||
| Раздел | Поле | Значение |
|
||||
|---|---|---|
|
||||
| `buckets` | `versioning` | хранить старые версии объектов, по умолчанию `true` |
|
||||
| | `noncurrent_days` | сколько дней хранить старые версии, по умолчанию 30 |
|
||||
| | `expire_days` | удалять объекты через столько дней; по умолчанию не удалять |
|
||||
| `databases` | `pgvector` | установить расширение `vector`, по умолчанию `false` |
|
||||
| `services` | `buckets` | бакет и доступ к нему: `rw` или `ro` |
|
||||
| | `databases` | базы, в которых сервис работает |
|
||||
|
||||
3. Проверить: `mise run vault:app-check orders`.
|
||||
Бакет или база без сервиса — это просто запись в `buckets` или `databases`. Один бакет и одну базу можно дать нескольким сервисам. Сервис — это учётная запись: для него создаются ключи S3, роли в Postgres, политика и роль AppRole в Vault.
|
||||
|
||||
Удаление сервиса — те же шаги в обратном порядке (`database`, `storage`, `vault`) после удаления блока из `config.yaml`. Бакет должен быть пустым. Переименование бакета или базы Terraform воспринимает как удаление старого и создание нового.
|
||||
После правки применить по очереди, читая `plan` перед каждым `apply`:
|
||||
|
||||
Имена бакетов должны быть уникальны между сервисами; удобно начинать их с имени сервиса.
|
||||
```bash
|
||||
mise run tf vault apply # политики и роли AppRole
|
||||
mise run tf storage apply # бакеты и ключи S3
|
||||
mise run tf database apply # базы, роли, пароли
|
||||
mise run vault:app-check # проверить доступы
|
||||
```
|
||||
|
||||
Опечатка в имени необязательного поля (например, `noncurent_days`) ошибки не вызовет: лишнее поле молча отбрасывается, и берётся значение по умолчанию. После правки `config.yaml` смотрите в `plan`, что изменилось именно то, что вы хотели.
|
||||
При удалении порядок обратный: `database`, `storage`, `vault`. Что стоит знать:
|
||||
|
||||
- Удалить можно только пустой бакет.
|
||||
- Переименование бакета или базы Terraform воспринимает как удаление старого и создание нового.
|
||||
- Ссылку сервиса на несуществующий бакет или базу и недопустимое значение доступа ловит проверка на `plan`.
|
||||
- Опечатка в имени необязательного поля (например, `noncurent_days`) ошибки не вызовет: лишнее поле молча отбрасывается, и берётся значение по умолчанию. Смотрите в `plan`, что изменилось именно то, что вы хотели.
|
||||
- Пока живы временные роли, выданные Vault, отобрать у сервиса базу не получится: Postgres ответит `dependent privileges exist`. Нужно дождаться истечения их срока (до часа) или отозвать токены сервиса.
|
||||
|
||||
## Что получает сервис
|
||||
|
||||
@@ -102,8 +121,10 @@ docker compose exec vault bao status # Sealed должно быть f
|
||||
| Путь в Vault | Содержимое |
|
||||
|---|---|
|
||||
| `secrets/<сервис>/s3` | `endpoint`, `buckets`, `access_key`, `secret_key` |
|
||||
| `secrets/<сервис>/postgres` | постоянные `host`, `port`, `database`, `username`, `password`, `sslmode` |
|
||||
| `database/creds/<сервис>` | временные `username` и `password`: Vault создаёт роль в Postgres на час и сам её удаляет |
|
||||
| `secrets/<сервис>/postgres/<база>` | постоянные `host`, `port`, `database`, `username`, `password`, `sslmode` |
|
||||
| `database/creds/<сервис>-<база>` | временные `username` и `password`: Vault создаёт роль в Postgres на час и сам её удаляет |
|
||||
|
||||
В Postgres у сервиса отдельная роль на каждую базу, `<сервис>-<база>`. Владеет базой роль `<база>_owner` без права входа, а роли сервисов работают от её имени. Поэтому таблицы, созданные одним сервисом, доступны другому сервису той же базы.
|
||||
|
||||
Какой пароль базы выбрать: постоянный проще и подходит по умолчанию; временный не нужно ротировать и он не лежит в KV, но сервис обязан продлевать токен Vault, иначе через час потеряет доступ к базе.
|
||||
|
||||
@@ -123,7 +144,7 @@ curl -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
|
||||
В `examples/go-service/` лежит небольшой сервис, который показывает всю цепочку: при запуске он знает только адрес Vault, `role_id` и `secret_id`, а ключи S3 и пароль базы получает из Vault.
|
||||
|
||||
```bash
|
||||
mise run example:up # первый сервис из config.yaml, постоянный пароль базы
|
||||
mise run example:up # первый сервис с бакетом и базой, постоянный пароль
|
||||
mise run example:up <сервис> dynamic # временная роль в Postgres от Vault
|
||||
curl http://127.0.0.1:8090/ # состояние подключений
|
||||
mise run example:down
|
||||
@@ -132,7 +153,7 @@ mise run example:down
|
||||
Задача `example:up` действует как администратор: берёт `role_id`, выпускает одноразовый `secret_id`, собирает образ и запускает контейнер в сети основного Compose. Дальше работает только код из `main.go`:
|
||||
|
||||
1. вход через AppRole клиентом `github.com/hashicorp/vault/api`;
|
||||
2. чтение `secrets/<сервис>/s3` и `secrets/<сервис>/postgres`;
|
||||
2. чтение `secrets/<сервис>/s3` и `secrets/<сервис>/postgres/<база>`;
|
||||
3. подключение к Postgres (`pgx`) и S3 (`minio-go`);
|
||||
4. HTTP-обработчик, который показывает, под кем сервис работает в базе и сколько объектов видит в своих бакетах.
|
||||
|
||||
@@ -158,8 +179,10 @@ infra/
|
||||
├── data/ # данные сервисов, не в git
|
||||
├── modules/
|
||||
│ ├── config/ # типы, проверки и значения по умолчанию для config.yaml
|
||||
│ ├── app-storage/ # бакеты, lifecycle, пользователь и ключи одного сервиса
|
||||
│ └── app-database/ # база, роль и права одного сервиса
|
||||
│ ├── bucket/ # бакет, versioning и lifecycle
|
||||
│ ├── bucket-access/ # пользователь, политика и ключи S3 одного сервиса
|
||||
│ ├── database/ # база, её роль-владелец и права
|
||||
│ └── database-access/ # роль сервиса в одной базе
|
||||
├── vault/ # Terraform: mount, политики, AppRole
|
||||
├── storage/ # Terraform: Silo и запись ключей в Vault
|
||||
└── database/ # Terraform: Postgres, запись паролей и временные роли
|
||||
@@ -180,7 +203,7 @@ infra/
|
||||
|
||||
Шаги проверены на чистой копии репозитория с пустым каталогом данных.
|
||||
|
||||
0. Скопировать репозиторий без `.env`, `data/`, `vault_keys.txt` и каталогов `.terraform/` (в git их и так нет). В `config.yaml` заменить под свой проект: логины администраторов, порты, если стандартные заняты, имена бакетов для state и бэкапов, раздел `services`. Больше ничего править не нужно.
|
||||
0. Скопировать репозиторий без `.env`, `data/`, `vault_keys.txt` и каталогов `.terraform/` (в git их и так нет). В `config.yaml` заменить под свой проект: логины администраторов, порты, если стандартные заняты, имя бакета для state, разделы `buckets`, `databases` и `services`. Бакет из `infra.silo.backup_bucket` должен быть описан в `buckets`. Больше ничего править не нужно.
|
||||
1. Скопировать `.env.example` в `.env` и заполнить три пароля; `VAULT_TOKEN` оставить пустым, его запишет шаг 6. Спецсимволы в паролях ломают адреса подключения, поэтому удобно генерировать их командой `openssl rand -hex 32`. Затем `mise trust` и `mise run up`. Поднимать именно этой задачей: она выдаёт Vault и pgAdmin права на их каталоги данных, без чего на чистой машине они не запускаются.
|
||||
2. Инициализировать Vault, сохранить unseal-ключ и root-токен, распечатать:
|
||||
|
||||
@@ -222,13 +245,16 @@ infra/
|
||||
|
||||
В коде нет комментариев, поэтому причины решений, которые не видны из самого кода, собраны здесь.
|
||||
|
||||
- **`depends_on` между грантами в `modules/app-database`.** Гранты `public` и `owner` меняют права одной и той же базы; без явного порядка Terraform выполнял бы их параллельно, и Postgres мог бы вернуть `tuple concurrently updated`.
|
||||
- **`depends_on` между грантами в `modules/database`.** Гранты `public` и `owner` меняют права одной и той же базы; без явного порядка Terraform выполнял бы их параллельно, и Postgres мог бы вернуть `tuple concurrently updated`.
|
||||
- **Грант `owner`.** После отзыва прав у `public` владелец базы не может подключиться к ней без явного `CONNECT`.
|
||||
- **`assume_role` в `modules/database-access`.** Роль сервиса при входе переключается на владельца базы, поэтому всё, что она создаёт, принадлежит владельцу, а не ей самой. Иначе второй сервис той же базы не смог бы изменить чужие таблицы.
|
||||
- **Отдельная роль на пару сервис и база.** `assume_role` задаётся для роли целиком, а владельцы у баз разные.
|
||||
- **`depends_on` в output `owner` модуля `database`.** Роль сервиса не создаётся, пока владельцу не выданы права на базу.
|
||||
- **`ignore_changes = [roles]` у роли `vault` в `database/main.tf`.** Членством управляет `postgresql_grant_role`; без этой строки два ресурса по очереди переписывают список ролей.
|
||||
- **`with_admin_option` там же.** Начиная с Postgres 16 роль с `CREATEROLE` добавляет участников только в те роли, где у неё есть `ADMIN OPTION`.
|
||||
- **`ALTER ROLE ... SET role` в `creation_statements`.** Временная роль работает от имени роли сервиса, поэтому созданные ею объекты принадлежат сервису, а саму её можно удалить без ошибок.
|
||||
- **`ALTER ROLE ... SET role` в `creation_statements`.** Тот же приём для временных ролей Vault: созданные ими объекты принадлежат владельцу базы, а саму роль можно удалить без ошибок.
|
||||
- **`delete_all_versions` у секретов.** Без него удалённый сервис оставлял бы в Vault восстановимые версии своих ключей.
|
||||
- **`prevent_destroy` у бакета со state.** В нём лежит state той самой конфигурации, которая им управляет.
|
||||
- **`prevent_destroy` у бакета со state.** В нём лежит state той самой конфигурации, которая им управляет. Из-за этого он описан отдельным ресурсом, а не в `buckets`: `prevent_destroy` нельзя включить по условию.
|
||||
- **Политика `terraform` в `vault/main.tf`.** Каждый путь взят из записи реальных запросов провайдера (`TF_LOG=DEBUG`) при добавлении и удалении сервиса. Создание и удаление mount, метода входа и самой политики в неё не входят намеренно.
|
||||
- **`host` и `internal_host` в `config.yaml`.** Первый — адрес с хоста, им пользуется Terraform; второй — адрес внутри сети Compose, он попадает в секреты сервисов и в настройки Vault.
|
||||
- **`include` в `docker-compose.yml`.** Только так Compose читает второй env-файл для подстановки переменных; сам он загружает один `.env`.
|
||||
|
||||
Reference in new issue
Block a user