diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0589f20 --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +# Копируется в /etc/admin-portal/admin-portal.env (см. README.md, шаг 5) +# Владелец файла — root, права 640, группа adminportal (см. install.sh) + +# Django +DJANGO_SECRET_KEY=change-me +DJANGO_DEBUG=false +DJANGO_ALLOWED_HOSTS=portal.example.com,127.0.0.1 + +# PostgreSQL (локальный на этой же ВМ) +POSTGRES_DB=admin_portal +POSTGRES_USER=admin_portal +POSTGRES_PASSWORD=change-me +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 + +# Redis (локальный на этой же ВМ, сессии + кэш групп AD) +REDIS_URL=redis://127.0.0.1:6379/1 + +# Active Directory / LDAP +AD_SERVER_URI=ldaps://dc01.example.com:636 +AD_BIND_DN=CN=svc-portal,OU=ServiceAccounts,DC=example,DC=com +AD_BIND_PASSWORD=change-me +AD_USER_SEARCH_BASE=OU=Users,DC=example,DC=com +AD_GROUP_SEARCH_BASE=OU=Groups,DC=example,DC=com +AD_GROUP_CACHE_TTL_SECONDS=600 + +# Токен для backend admin panels +RESOURCE_TOKEN_SECRET=change-me-separate-secret +RESOURCE_TOKEN_TTL_SECONDS=60 diff --git a/README.md b/README.md index 22bb1c4..2493e18 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,322 @@ -# Test1 - -Что-то тестируем \ No newline at end of file +# Портал администрирования: HAProxy + Django + AD → Backend admin panels +## Общее описание реализованного проекта + +Единая точка входа для администраторов: логин через Active Directory, +персональный список доступных ресурсов по AD-группам, переход в нужную +админку по адресу `https://///...`. + +Все компоненты устанавливаются как обычные пакеты/systemd-сервисы +непосредственно на одну ВМ. + +## 1. Состав решения + +``` +admin-portal-vm/ +├── README.md — этот файл +├── .env.example — шаблон переменных окружения +├── haproxy/ +│ ├── haproxy.cfg — полный конфиг HAProxy (-> /etc/haproxy/haproxy.cfg) +│ └── group_map.cfg — карта "путь -> backend" (-> /etc/haproxy/group_map.cfg) +├── systemd/ +│ └── admin-portal.service — юнит Gunicorn (Django-приложение) +├── django_app/ — код портала (Django) +│ ├── manage.py +│ ├── requirements.txt +│ ├── config/ — settings.py, urls.py, wsgi.py +│ ├── portal/ — модели, вью, шаблоны, LDAP-логика +│ └── resources.example.yaml — пример декларативного каталога ресурсов +├── backend_demo/ — 2 демо-админки (Nginx) для проверки маршрутизации +│ ├── resource1/{index.html,nginx.conf} +│ └── resource2/{index.html,nginx.conf} +├── scripts/ +│ ├── install.sh — автоматизация шагов 3–9 (см. ниже) +│ └── generate_secrets.sh — генерация DJANGO_SECRET_KEY / RESOURCE_TOKEN_SECRET +└── docs/ + └── architecture.md — полное описание архитектуры и решений +``` + +## 2. Архитектура (кратко) + +``` +Браузер админа + │ HTTPS (443) + ▼ + HAProxy (systemd-сервис haproxy) ── TLS termination, ACL-роутинг по пути + │ + ├── путь портала / нет валидного resource_token ──► Gunicorn (127.0.0.1:8000) + │ └─ Django frontend + │ ├─ django-auth-ldap -> AD + │ ├─ PostgreSQL (localhost) + │ └─ Redis (localhost, сессии) + │ + └── путь /{adminusergroup}/{resource}/... + resource_token + │ + ▼ + backend_<группа> — Nginx на 127.0.0.1:808x (демо) или + реальная админка на другом хосте сети +``` + +Полное описание потоков, модели данных и вопросов безопасности — в +`docs/architecture.md`. + +## 3. Требования к ВМ + +- РедОС либо АстраЛинукс +- 2 vCPU / 4 GB RAM достаточно для старта (PostgreSQL, Redis, Gunicorn, + HAProxy, Nginx — все на одной машине) +- Сетевой доступ до контроллера домена AD (LDAP/LDAPS, порт 389/636) +- Технический пользователь AD с правом bind + поиска (service account), + без прав записи +- Валидный TLS-сертификат на FQDN портала (или self-signed для теста) +- DNS-запись FQDN, указывающая на эту ВМ +- Порты 80/443 открыты снаружи; всё остальное (5432, 6379, 8000, 808x) — + только на loopback, наружу не публикуется + +## 4. Установка — автоматически (рекомендуемый способ) + +```bash +# 1. Скопировать архив решения на ВМ и распаковать +unzip admin-portal-vm.zip +cd admin-portal-vm + +# 2. Подготовить .env +cp .env.example .env +bash scripts/generate_secrets.sh # подставить значения ключей в .env вручную +nano .env # заполнить AD_*, POSTGRES_PASSWORD и т.д. + +# 3. Запустить установку (пакеты, БД, Redis, venv, миграции, systemd, HAProxy, Nginx) +sudo bash scripts/install.sh ./.env +``` + +Скрипт последовательно: +1. ставит `haproxy`, `nginx`, `postgresql`, `redis-server`, Python и dev-библиотеки + для сборки `python-ldap`; +2. создаёт системного пользователя `adminportal` без права логина; +3. копирует `django_app/` в `/opt/admin-portal/django_app`; +4. создаёт venv и ставит зависимости из `requirements.txt`; +5. кладёт `.env` в `/etc/admin-portal/admin-portal.env` (права 640, владелец root:adminportal); +6. создаёт роль и базу PostgreSQL по значениям из `.env`, включает PostgreSQL и Redis; +7. прогоняет `migrate` и `collectstatic` от имени `adminportal`; +8. устанавливает и включает `systemd`-юнит `admin-portal.service` (Gunicorn на 127.0.0.1:8000); +9. раскладывает `haproxy.cfg`/`group_map.cfg`, поднимает 2 демо-бэкенда через Nginx + на 127.0.0.1:8081/8082, открывает 80/443 в `ufw`. + +После скрипта останутся 3 ручных шага (секреты и сертификат сознательно не +автоматизированы): + +```bash +# TLS-сертификат +cat fullchain.pem privkey.pem > /etc/haproxy/certs/fqdn.pem +haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy + +# Суперпользователь Django +sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py createsuperuser' + +# Наполнение каталога ресурсов +sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py \ + sync_resources /opt/admin-portal/django_app/resources.example.yaml' +``` + +Дальше — к разделу 6 (проверка). + +## 5. Установка — вручную, шаг за шагом + +Если нужно понимать/контролировать каждое действие (или адаптировать под +другой дистрибутив), либо не доверяете единому скрипту — то же самое руками. + +### Шаг 1. Системные пакеты + +```bash +sudo apt-get update +sudo apt-get install -y haproxy nginx postgresql redis-server \ + python3 python3-venv python3-dev \ + libldap2-dev libsasl2-dev libssl-dev build-essential ufw +``` + +### Шаг 2. Системный пользователь приложения + +```bash +sudo useradd --system --home /opt/admin-portal --shell /usr/sbin/nologin adminportal +``` + +### Шаг 3. Код приложения и виртуальное окружение + +```bash +sudo mkdir -p /opt/admin-portal +sudo cp -r django_app /opt/admin-portal/django_app +sudo chown -R adminportal:adminportal /opt/admin-portal + +sudo python3 -m venv /opt/admin-portal/venv +sudo /opt/admin-portal/venv/bin/pip install --upgrade pip +sudo /opt/admin-portal/venv/bin/pip install -r /opt/admin-portal/django_app/requirements.txt +sudo chown -R adminportal:adminportal /opt/admin-portal/venv +``` + +### Шаг 4. Переменные окружения + +```bash +cp .env.example .env +bash scripts/generate_secrets.sh # вставить значения в .env +nano .env +``` + +Заполнить как минимум: `DJANGO_SECRET_KEY`, `DJANGO_ALLOWED_HOSTS`, +`POSTGRES_PASSWORD`, `AD_SERVER_URI`, `AD_BIND_DN`, `AD_BIND_PASSWORD`, +`AD_USER_SEARCH_BASE`, `AD_GROUP_SEARCH_BASE`, `RESOURCE_TOKEN_SECRET`. + +```bash +sudo mkdir -p /etc/admin-portal +sudo install -o root -g adminportal -m 640 .env /etc/admin-portal/admin-portal.env +``` + +### Шаг 5. PostgreSQL + +```bash +source .env # только для этой команды, читаем значения POSTGRES_* +sudo -u postgres psql -c "CREATE ROLE ${POSTGRES_USER} LOGIN PASSWORD '${POSTGRES_PASSWORD}';" +sudo -u postgres psql -c "CREATE DATABASE ${POSTGRES_DB} OWNER ${POSTGRES_USER};" +sudo systemctl enable --now postgresql +``` + +### Шаг 6. Redis + +```bash +sudo systemctl enable --now redis-server +``` + +По умолчанию Redis слушает только `127.0.0.1` — это то, что нужно, менять +`bind` в `/etc/redis/redis.conf` не требуется. + +### Шаг 7. Миграции и статика + +```bash +sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py migrate --noinput && \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py collectstatic --noinput' +``` + +### Шаг 8. Gunicorn как systemd-сервис + +```bash +sudo install -m 644 systemd/admin-portal.service /etc/systemd/system/admin-portal.service +sudo systemctl daemon-reload +sudo systemctl enable --now admin-portal +sudo systemctl status admin-portal # должен быть active (running) +``` + +### Шаг 9. HAProxy + +```bash +sudo install -m 644 haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg +sudo install -m 644 haproxy/group_map.cfg /etc/haproxy/group_map.cfg +sudo mkdir -p /etc/haproxy/certs && sudo chmod 700 /etc/haproxy/certs +``` + +Положить сертификат: + +```bash +cat fullchain.pem privkey.pem | sudo tee /etc/haproxy/certs/fqdn.pem >/dev/null +sudo chmod 600 /etc/haproxy/certs/fqdn.pem +``` + +Проверить и включить: + +```bash +sudo haproxy -c -f /etc/haproxy/haproxy.cfg +sudo systemctl enable --now haproxy +``` + +### Шаг 10. Демо-бэкенды (Nginx) — опционально, для сквозной проверки + +```bash +for res in resource1 resource2; do + sudo mkdir -p /var/www/admin-demo-${res} + sudo cp backend_demo/${res}/index.html /var/www/admin-demo-${res}/index.html + sudo install -m 644 backend_demo/${res}/nginx.conf /etc/nginx/sites-available/admin-demo-${res} + sudo ln -sf /etc/nginx/sites-available/admin-demo-${res} /etc/nginx/sites-enabled/admin-demo-${res} +done +sudo rm -f /etc/nginx/sites-enabled/default +sudo nginx -t && sudo systemctl enable --now nginx && sudo systemctl reload nginx +``` + +### Шаг 11. Фаервол + +```bash +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable # если ещё не включён — проверить, что не потеряете SSH (22/tcp) +``` + +### Шаг 12. Суперпользователь и каталог ресурсов + +```bash +sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py createsuperuser' +``` + +Дальше зайти на `https:///django-admin/` и создать **ResourceGroup** +(привязка к реальной AD-группе) и **Resource** (конкретный ресурс), либо +воспользоваться декларативным файлом: + +```bash +sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py \ + sync_resources /opt/admin-portal/django_app/resources.example.yaml' +``` + +## 6. Проверка + +```bash +systemctl status admin-portal haproxy nginx postgresql redis-server +curl -kI https://portal.example.com/ # ожидаем редирект на /login/ +``` + +Залогиниться доменной учёткой, убедиться, что на портале появились ресурсы, +доступные группам этого пользователя, и что клик по ресурсу открывает +демо-страницу (`Demo Resource 1/2`) — это подтверждает, что вся цепочка +HAProxy → Django → AD → выдача токена → маршрутизация на backend работает. + +## 7. Добавление нового ресурса/backend'а в дальнейшем + +1. Поднять реальную админку ресурса — на этой же ВМ (новый порт на + `127.0.0.1`) либо на отдельном хосте сети, доступном от HAProxy, но не + доступном напрямую из интернета. +2. Добавить в `/etc/haproxy/haproxy.cfg` новую `backend be_` секцию со + `server` этой админки (по образцу `be_admin_resource1`). +3. Добавить строку в `/etc/haproxy/group_map.cfg`: + `/adgroup/resource-slug/ be_`. +4. Проверить и перечитать конфиг без разрыва соединений: + `sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy`. +5. Создать запись `Resource` в Django admin или через `sync_resources`. + +## 8. Эксплуатация + +- **Логи**: `journalctl -u admin-portal -f` (Django/Gunicorn), + `journalctl -u haproxy -f`, `/var/log/nginx/*.log` (демо-бэкенды). +- **Обновление кода портала**: + ```bash + sudo -u adminportal rsync -a --delete django_app/ /opt/admin-portal/django_app/ + sudo -u adminportal bash -c \ + 'set -a; source /etc/admin-portal/admin-portal.env; set +a; \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py migrate --noinput && \ + /opt/admin-portal/venv/bin/python /opt/admin-portal/django_app/manage.py collectstatic --noinput' + sudo systemctl restart admin-portal + ``` +- **Резервное копирование**: штатный `pg_dump ${POSTGRES_DB}` по расписанию + (cron/systemd timer). Redis бэкапить не нужно — там только сессии/кэш. +- **Обновление TLS-сертификата**: заменить `/etc/haproxy/certs/fqdn.pem` и + выполнить `systemctl reload haproxy` (без разрыва активных соединений). +- **Масштабирование**: вынести PostgreSQL/Redis на отдельные ВМ (поменять + `POSTGRES_HOST`/`REDIS_URL` в `.env`), поднять второй Django+Gunicorn на + другой ВМ и добавить вторую строку `server` в `backend be_django`; + HAProxy балансирует между ними без изменения остальной конфигурации. + +Полное описание архитектуры, потоков данных, вариантов передачи identity в +backend и вопросов безопасности — см. `docs/architecture.md`.