diff --git a/docs/devops-deploy.md b/docs/devops-deploy.md index 2c95cce..b85490e 100644 --- a/docs/devops-deploy.md +++ b/docs/devops-deploy.md @@ -1,140 +1,137 @@ # Деплой ipwhitelist — PRODUCTION -> Репозиторий: `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` (публичный) -> Стек: Node.js 20+ / Express 4 / EJS / PostgreSQL 16+ -> Требования: доступ к Keycloak Nubes (realm `cloud`), зарегистрированный OIDC-клиент +Репозиторий: `https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git` (публичный) +ОС: Ubuntu 22.04 / 24.04 (для других ОС команды могут отличаться) --- -## 1. Подготовка сервера +## 0. Что должно быть готово до начала + +| Что | Откуда берётся | +|---|---| +| Сервер (ВМ) с Ubuntu | Выделяет DevOps | +| Домен (например `white.nubes.ru`) | DNS запись на сервер | +| Доступ к Keycloak Nubes (realm `cloud`) | Существующий Keycloak в инфраструктуре Nubes | +| Зарегистрированный OIDC-клиент в Keycloak | Создаётся в Keycloak (шаг 6) | +| `KC_CLIENT_ID` и `KC_CLIENT_SECRET` | Выдаются после регистрации клиента | + +**Что будет установлено на сервер:** Node.js 20, PostgreSQL 16, nginx, certbot, PM2. + +--- + +## 1. PostgreSQL — установка + +**Где:** на этой же ВМ, рядом с приложением. +**Версия:** 16 (или любая 15+). +**Безопасность:** слушает только localhost (наружу не торчит). ```bash -# Node.js 20+ -curl -fsSL https://deb.nodesource.com/setup_20.x | bash - -apt install -y nodejs git nginx certbot python3-certbot-nginx postgresql +# 1.1 Установить PostgreSQL +sudo apt update +sudo apt install -y postgresql -# Код -git clone https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git /opt/ipwhitelist +# 1.2 Проверить что запустился и слушает localhost +sudo systemctl status postgresql --no-pager +ss -tlnp | grep 5432 +# Должно быть: 127.0.0.1:5432 (НЕ 0.0.0.0:5432) + +# 1.3 Сгенерировать пароль для БД — он понадобится в .env +DB_PASS=$(openssl rand -base64 24) +echo "Пароль БД: $DB_PASS" # сохранить — вписать в .env + +# 1.4 Создать пользователя и базу данных +sudo -u postgres psql -c "CREATE USER ipwhitelist WITH PASSWORD '${DB_PASS}';" +sudo -u postgres psql -c "CREATE DATABASE ipwhitelist OWNER ipwhitelist;" +``` + +**Таблицы создадим позже (шаг 2.4), после клонирования кода — схема лежит в репозитории.** + +--- + +## 2. Node.js + код приложения + таблицы БД + +```bash +# 2.1 Установить Node.js 20 +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - +sudo apt install -y nodejs + +# 2.2 Скачать код и установить зависимости +sudo git clone https://gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git /opt/ipwhitelist +sudo chown -R $(whoami):$(whoami) /opt/ipwhitelist # чтобы npm install не просил sudo cd /opt/ipwhitelist npm install --production + +# 2.4 Теперь код есть — можно создать таблицы (schema.sql в репозитории) +sudo -u postgres psql -d ipwhitelist -f /opt/ipwhitelist/sql/schema.sql + +# 2.5 Проверить что таблицы создались +sudo -u postgres psql -d ipwhitelist -c "\dt" +# Результат: companies, whitelist_entries, audit_log, session ``` --- -## 2. PostgreSQL +## 3. .env — настройка приложения ```bash -# Создать пользователя и БД -sudo -u postgres psql -c "CREATE USER ipwhitelist WITH PASSWORD '${PASSWORD}';" -sudo -u postgres psql -c "CREATE DATABASE ipwhitelist OWNER ipwhitelist;" - -# Применить схему -PGPASSWORD="${PASSWORD}" psql -h localhost -U ipwhitelist -d ipwhitelist -f /opt/ipwhitelist/sql/schema.sql - -# Настройка: слушать только localhost (безопасность) -grep -c "^listen_addresses = 'localhost'" /etc/postgresql/16/main/postgresql.conf \ - || echo "listen_addresses = 'localhost'" >> /etc/postgresql/16/main/postgresql.conf -systemctl restart postgresql +# 3.1 Скопировать шаблон +cd /opt/ipwhitelist +sudo cp .env.example .env +sudo chmod 600 .env # чтобы никто не прочитал пароли ``` +**3.2 Открыть .env и заполнить значения:** +```bash +sudo nano /opt/ipwhitelist/.env +``` + +**Что и куда писать:** + +| Строка в .env | Что вписать | +|---|---| +| `DB_PASS=` | Пароль, сгенерированный на шаге 1.3 | +| `DEV_MODE=false` | **ОБЯЗАТЕЛЬНО false** иначе будет mock-логин | +| `ADMIN_CLIENT_ID=WZ01112` | clientId администратора (получить у заказчика) | +| `KC_CLIENT_ID=` | ID клиента из Keycloak (шаг 6) | +| `KC_CLIENT_SECRET=` | Secret клиента из Keycloak (шаг 6) | +| `APP_URL=https://white.nubes.ru` | **Реальный домен** без слеша на конце | +| `SESSION_SECRET=` | Сгенерировать: `openssl rand -base64 32` | +| `CSRF_SECRET=` | Сгенерировать: `openssl rand -base64 32` | + +**Остальные строки не трогать** — значения по умолчанию подходят. + --- -## 3. .env (PRODUCTION) +## 4. nginx — HTTPS и прокси + +**Зачем:** браузер не пускает на HTTPS-сайты с localhost. Нужен nginx как прослойка. ```bash -cp /opt/ipwhitelist/.env.example /opt/ipwhitelist/.env -chmod 600 /opt/ipwhitelist/.env +# 4.1 Установить nginx и certbot +sudo apt install -y nginx certbot python3-certbot-nginx ``` -```ini -# ── База данных ── -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=ipwhitelist -DB_USER=ipwhitelist -DB_PASS=<сгенерировать: openssl rand -base64 24> -DB_SSLMODE=disable # localhost — шифрование не нужно +**4.2 Создать конфиг nginx.** Заменить `white.nubes.ru` на свой домен: -# ── Аутентификация ── -DEV_MODE=false # ОБЯЗАТЕЛЬНО false в production -ADMIN_CLIENT_ID=WZ01112 # clientId пользователя-администратора - -# ── Keycloak / OIDC (данные от DevOps) ── -KC_CLIENT_ID= # зарегистрированный OIDC-клиент -KC_CLIENT_SECRET= # секрет клиента -KC_BASE_URL=https://keycloak.nubes.ru/realms/cloud -APP_URL=https://<домен> # публичный URL приложения (без слеша) - -# ── Сессия ── -SESSION_SECRET= -CSRF_SECRET= - -# ── Лимит записей по умолчанию ── -DEFAULT_LIMIT=15 - -# ── Порт (для nginx upstream) ── -PORT=3001 - -# ── API_BASE — куда UI-слой ходит за API (обычно localhost) ── -API_BASE=http://localhost:3001 -``` - -**Генерация секретов:** ```bash -openssl rand -base64 32 # для SESSION_SECRET -openssl rand -base64 32 # для CSRF_SECRET -openssl rand -base64 24 # для DB_PASS +sudo nano /etc/nginx/sites-available/ipwhitelist ``` ---- - -## 4. Keycloak — регистрация OIDC-клиента - -**Что сделать в админке Keycloak (realm `cloud`):** - -1. Создать клиента: - - Client ID: `<домен>` (например `white.nubes.ru`) - - Client authentication: ON - - Standard flow: ON - - Valid redirect URIs: `https://<домен>/callback` - - Web origins: `https://<домен>` (или `+`) - -2. Забрать `Client secret` → в .env как `KC_CLIENT_SECRET` - -3. **Mapper'ы** (создать на клиенте, тип `User Attribute → Token Claim`): - -| Claim name | User attribute | Назначение | -|---|---|---| -| `ClientID` | `client_id` | ID компании (может быть через запятую) | -| `company_id` | `company_id` | ID компании в БД | -| `company_name` | `company_name` | Название компании | - -4. **Пользователи** — создать в Keycloak с заполненными атрибутами `client_id`, `company_id`, `company_name`. - -**Формат multi-company:** `client_id = "WZ11125,WZ03816"` — первое значение до запятой = активная компания. Приложение парсит автоматически. - ---- - -## 5. nginx + HTTPS +Вставить: ```nginx server { listen 80; - server_name <домен>; + server_name white.nubes.ru; # ← ЗАМЕНИТЬ на свой домен return 301 https://$host$request_uri; } server { listen 443 ssl http2; - server_name <домен>; + server_name white.nubes.ru; # ← ЗАМЕНИТЬ на свой домен - ssl_certificate /etc/letsencrypt/live/<домен>/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/<домен>/privkey.pem; - - # Security headers - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Frame-Options "DENY" always; + ssl_certificate /etc/letsencrypt/live/white.nubes.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/white.nubes.ru/privkey.pem; location / { proxy_pass http://127.0.0.1:3001; @@ -142,109 +139,146 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; } } ``` ```bash -# Получить сертификат -certbot --nginx -d <домен> +# 4.3 Включить сайт +sudo ln -s /etc/nginx/sites-available/ipwhitelist /etc/nginx/sites-enabled/ +sudo rm -f /etc/nginx/sites-enabled/default -# Проверить авто-обновление -certbot renew --dry-run +# 4.4 Получить SSL-сертификат (certbot сам подправит конфиг) +sudo certbot --nginx -d white.nubes.ru # ← ЗАМЕНИТЬ на свой домен + +# 4.5 Проверить что nginx работает +sudo nginx -t +sudo systemctl reload nginx ``` --- -## 6. PM2 — автозапуск +## 5. PM2 — автозапуск приложения + +**Зачем:** чтобы приложение само стартовало после перезагрузки сервера. ```bash -npm i -g pm2 +# 5.1 Установить PM2 +sudo npm i -g pm2 -# Запуск -pm2 start /opt/ipwhitelist/server.js --name ipwhitelist --update-env +# 5.2 Запустить приложение +cd /opt/ipwhitelist +sudo pm2 start server.js --name ipwhitelist --update-env -# Автостарт при перезагрузке -pm2 startup systemd -pm2 save +# 5.3 Настроить автозапуск при перезагрузке +sudo pm2 startup systemd +sudo pm2 save -# Проверка -pm2 status -pm2 logs ipwhitelist --lines 5 -# Должно быть: [auth] OIDC — Keycloak -# Если [auth] MOCK — значит KC_CLIENT_ID не задан +# 5.4 Проверить что запустилось +sudo pm2 status +# Должен быть статус: online ``` --- -## 7. Порядок ввода в эксплуатацию +## 6. Keycloak — регистрация OIDC-клиента -```bash -# 1. Убедиться что DEV_MODE=false и заданы KC_* -grep -E "DEV_MODE|KC_CLIENT_ID|KC_CLIENT_SECRET" /opt/ipwhitelist/.env +**Где:** в админке Keycloak Nubes (`https://keycloak.nubes.ru/admin`, realm `cloud`). +**Зачем:** чтобы Keycloak знал наше приложение и пускал пользователей обратно после входа. -# 2. Перезапустить -pm2 restart ipwhitelist --update-env +**6.1 Создать клиента:** -# 3. Проверить логи — должен быть OIDC-режим -pm2 logs ipwhitelist --lines 3 --nostream - -# 4. Проверить health -curl -s https://<домен>/healthz -# → OK - -# 5. Проверить SSO: открыть https://<домен>/login в браузере -# → редирект на Keycloak -# → после входа → редирект обратно на главную -``` - ---- - -## 8. Проверка - -| URL | Ожидаемый результат | +| Поле | Значение | |---|---| -| `https://<домен>/healthz` | `OK` | -| `https://<домен>/` | Редирект на Keycloak login (если нет сессии) | -| `https://<домен>/login` | Редирект на Keycloak (если `DEV_MODE=false`) | -| `https://<домен>/export` | CIDR-список (text/plain) | -| `https://<домен>/api/v1/entries` | 401 (нужен Bearer) | -| `https://<домен>/callback?code=...` | Обмен code → сессия → редирект на `/` | +| Client ID | `white.nubes.ru` (или свой домен) | +| Client authentication | ON (включено) | +| Standard flow | ON (включено) | +| Valid redirect URIs | `https://white.nubes.ru/callback` | +| Web origins | `https://white.nubes.ru` | + +**6.2 Забрать секрет:** после создания клиента открыть вкладка `Credentials` → `Client secret` → скопировать. + +**6.3 Создать mapper'ы** (вкладка `Client scopes` → `ipwhitelist-app-dedicated` → `Add mapper` → `By configuration` → `User Attribute`): + +Создать три mapper'а: + +| Имя | User Attribute | Claim name | Тип | +|---|---|---|---| +| `ClientID` | `client_id` | `ClientID` | String | +| `company_id` | `company_id` | `company_id` | String | +| `company_name` | `company_name` | `company_name` | String | + +**6.4 Создать пользователей** (вкладка `Users` → `Add user`): + +| Логин | Пароль | Атрибуты | +|---|---|---| +| `admin` | задать вкладка Credentials | `client_id=WZ01112`, `company_id=1`, `company_name=Nubes Admin` | +| `client` | задать | `client_id=WZ88888`, `company_id=2`, `company_name=Альфа` | + +**Как задать атрибуты:** вкладка `Attributes` → добавить по одному → `Save`. + +**Формат multi-company:** `client_id = "WZ11125,WZ03816"` — первое значение до запятой = активная компания. Приложение само разберёт. --- -## 9. Обновление +## 7. Финальная проверка + +```bash +# 7.1 Убедиться что в .env всё заполнено +grep -E "DEV_MODE|KC_CLIENT_ID|KC_CLIENT_SECRET|APP_URL|DB_PASS" /opt/ipwhitelist/.env +# Проверить: DEV_MODE=false, KC_CLIENT_ID и KC_CLIENT_SECRET не пустые + +# 7.2 Перезапустить приложение +cd /opt/ipwhitelist +sudo pm2 restart ipwhitelist --update-env + +# 7.3 Проверить что включился OIDC-режим +sudo pm2 logs ipwhitelist --lines 3 --nostream +# В логе должно быть: [auth] OIDC — Keycloak +# Если [auth] MOCK — значит KC_CLIENT_ID/KC_CLIENT_SECRET не заданы +``` + +**Открыть в браузере:** + +| URL | Что должно произойти | +|---|---| +| `https://white.nubes.ru/healthz` | Покажет `OK` | +| `https://white.nubes.ru/` | Редирект на страницу логина Keycloak | +| Ввести логин/пароль пользователя из Keycloak | Редирект обратно на главную с белым списком | +| `https://white.nubes.ru/export` | Список CIDR (текст) | +| `https://white.nubes.ru/logout` | Выход из системы | + +--- + +## 8. Обновление приложения ```bash cd /opt/ipwhitelist -git pull -npm install --production -pm2 restart ipwhitelist --update-env +sudo git pull +sudo npm install --production +sudo pm2 restart ipwhitelist --update-env ``` --- -## 10. Переменные окружения (справочно) +## 9. Переменные .env — полный список -| Переменная | Обязательна | По умолчанию | Описание | +| Переменная | Когда нужна | По умолчанию | Что писать | |---|---|---|---| -| `DB_HOST` | да | — | Хост PostgreSQL | -| `DB_PORT` | нет | 5432 | Порт PostgreSQL | -| `DB_NAME` | да | — | Имя БД | -| `DB_USER` | да | — | Пользователь БД | -| `DB_PASS` | да | — | Пароль БД | -| `DB_SSLMODE` | нет | disable | `require` для внешнего подключения | -| `DEV_MODE` | нет | true | `false` в production (ОБЯЗАТЕЛЬНО) | -| `ADMIN_CLIENT_ID` | нет | WZ01112 | clientId администратора | -| `KC_CLIENT_ID` | да* | — | OIDC client ID (обязателен для SSO) | -| `KC_CLIENT_SECRET` | да* | — | OIDC client secret | -| `KC_BASE_URL` | нет | `https://keycloak.nubes.ru/realms/cloud` | Keycloak realm URL | -| `APP_URL` | да* | — | Публичный URL приложения (для OIDC redirect) | -| `SESSION_SECRET` | да | — | Подпись сессионных cookie | -| `CSRF_SECRET` | нет | — | CSRF-токены | -| `PORT` | нет | 3000 | Порт Express | -| `DEFAULT_LIMIT` | нет | 15 | Лимит записей по умолчанию | -| `API_BASE` | нет | `http://localhost:PORT` | URL для UI→API запросов | - -\* — обязательны при `DEV_MODE=false` +| `DB_HOST` | всегда | — | `localhost` | +| `DB_PORT` | всегда | 5432 | — | +| `DB_NAME` | всегда | — | `ipwhitelist` | +| `DB_USER` | всегда | — | `ipwhitelist` | +| `DB_PASS` | всегда | — | пароль из шага 1.3 | +| `DB_SSLMODE` | никогда | disable | не трогать | +| `DEV_MODE` | всегда | true | **false** в production | +| `ADMIN_CLIENT_ID` | всегда | WZ01112 | clientId админа | +| `KC_CLIENT_ID` | для SSO | — | из Keycloak (шаг 6) | +| `KC_CLIENT_SECRET` | для SSO | — | из Keycloak (шаг 6) | +| `KC_BASE_URL` | для SSO | `https://keycloak.nubes.ru/realms/cloud` | не трогать | +| `APP_URL` | для SSO | — | `https://white.nubes.ru` | +| `SESSION_SECRET` | всегда | — | `openssl rand -base64 32` | +| `CSRF_SECRET` | опционально | — | `openssl rand -base64 32` | +| `PORT` | всегда | 3000 | `3001` | +| `DEFAULT_LIMIT` | опционально | 15 | не трогать | +| `API_BASE` | всегда | `http://localhost:PORT` | не трогать |