# Замена postgres superuser в food-market-server Sprint 13, задача 1. Дата: 2026-06-07. ## Контекст `food-market-server` — legacy backend (back.food-market.kz, port 8084 на prod-vm `192.168.1.190`, systemd `food-market-server.service`). Хранилище — `food-market-server-postgres` (Docker, port 5436). До этой задачи в `appsettings.Production.json` была строка с **superuser'ом**: ``` Host=localhost;Port=5436;Database=food_market_server;Username=postgres;Password=1q2w3e4r ``` Это плохо по двум причинам: - Слабый пароль (`1q2w3e4r`), известен любому, кто прочитает конфиг. - `postgres` — суперюзер: CREATE DATABASE, CREATE ROLE, REPLICATION, BYPASS RLS, может уничтожить всё что угодно в кластере (включая другие БД, если они там появятся). ## Решение Создан dedicated app-role `food_market_server_app`: - LOGIN + сильный пароль (48 hex chars). - NOSUPERUSER, NOCREATEDB, NOCREATEROLE, NOREPLICATION, NOBYPASSRLS. - Гранты: SELECT/INSERT/UPDATE/DELETE на все существующие таблицы + USAGE/SELECT/UPDATE на sequences + USAGE/CREATE на schema public (CREATE нужен для EF миграций, которые app запускает на старте через `db.Database.Migrate()`). - DEFAULT PRIVILEGES `FOR ROLE postgres IN SCHEMA public` — все будущие таблицы, что создаст superuser (например, если миграцию применить вручную через `postgres`), автоматически получат CRUD для app-роли. ## Что сделано (атомарно) ``` 1. Бэкап конфига: /opt/food-market-server/appsettings.Production.json → appsettings.Production.json.bak.20260607-fms-rolemigration 2. Создание роли в БД: CREATE ROLE food_market_server_app LOGIN PASSWORD '...' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS; GRANT CONNECT ON DATABASE food_market_server TO food_market_server_app; GRANT USAGE, CREATE ON SCHEMA public TO food_market_server_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO food_market_server_app; GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO food_market_server_app; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO food_market_server_app; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO food_market_server_app; 3. Обновление appsettings.Production.json: Username: postgres → food_market_server_app Password: 1q2w3e4r → <48-hex> 4. systemctl restart food-market-server → active. 5. curl http://localhost:8084/ → 200 (SPA fallback). 6. curl https://back.food-market.kz/ → 200. 7. Логи без EF errors после старта. ``` ## Проверка работоспособности ```bash ssh nns@192.168.1.190 'sudo systemctl status food-market-server | head -5' curl -fsS https://back.food-market.kz/ -o /dev/null -w "HTTP %{http_code}\n" ssh nns@192.168.1.190 'sudo journalctl -u food-market-server --since "10 minutes ago" --no-pager | grep -iE "error|fail" | head' ``` ## Rollback Если что-то сломается (миграция fails, EF Errors в логах), вернуться к старой конфигурации одной командой: ```bash ssh nns@192.168.1.190 'sudo cp /opt/food-market-server/appsettings.Production.json.bak.20260607-fms-rolemigration /opt/food-market-server/appsettings.Production.json && sudo systemctl restart food-market-server' curl -fsS https://back.food-market.kz/ -o /dev/null -w "HTTP %{http_code}\n" ``` Это вернёт `Username=postgres;Password=1q2w3e4r` — старый superuser. Новая роль `food_market_server_app` остаётся в БД (это идемпотентный `CREATE IF NOT EXISTS`), её можно дропнуть отдельно: ```sql -- Только после успешного rollback'а и подтверждения что приложение -- работает на postgres: DROP OWNED BY food_market_server_app; -- (если бы что-то создавала) DROP ROLE food_market_server_app; ``` ## Что НЕ покрыто (TODO) - **Ротация пароля postgres**. Сам `postgres` superuser остался с тем же `1q2w3e4r`. Пока он не используется в работе app'а (мы только что переключились на app-роль) — но всё равно нужно сменить на сильный, иначе кто-то с доступом к dev-машине или к /opt видит старый бэкап и пробует. Делать через `ALTER ROLE postgres PASSWORD '...'` под superuser-сессией. - **PGHBA**. Сейчас доверяется любой коннект с loopback (стандартный `pg_hba.conf` postgres-контейнера). Допустимо для single-host setup, но при сетевом расширении нужно ужесточать. - **Audit log внутри PG** (pgaudit) — не настроен. Логирует только app. - **Per-table RLS** — не используется, потому что приложение само фильтрует по `OrganizationId`. Под добавление RLS не подписывался. ## Дальше Аналогичную замену нужно провести в: - `food-market-stage-postgres` (port 5435) — там пользователь `food_market` уже без superuser-прав (см. `deploy/docker-compose.yml`, он создаётся через `POSTGRES_USER` env, что даёт superuser в рамках только этой БД — лучше чем кросс-БД superuser, но всё равно стоит ужесточить аналогично). - `food-market-postgres` (prod admin.food-market.kz, port 5434) — то же. - Локальный dev PG на host'е (brew postgresql@14) — там безразлично (dev-only, локальный сокет, пустой пароль работает по trust).