# Портал администрирования: HAProxy + Django + AD → Backend admin panels ## Установка на виртуальную машину (без Docker) Единая точка входа для администраторов: логин через Active Directory, персональный список доступных ресурсов по AD-группам, переход в нужную админку по адресу `https://///...`. Все компоненты устанавливаются как обычные пакеты/systemd-сервисы непосредственно на одну ВМ (Ubuntu 22.04/24.04 или Debian 12). ## 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. Требования к ВМ - Ubuntu 22.04/24.04 LTS или Debian 12, root/sudo-доступ - 2 vCPU / 2 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 ``` Для теста — self-signed: ```bash openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout /tmp/key.pem -out /tmp/cert.pem -subj "/CN=portal.example.com" cat /tmp/cert.pem /tmp/key.pem | sudo tee /etc/haproxy/certs/fqdn.pem >/dev/null ``` Проверить и включить: ```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`.