Портал администрирования: HAProxy + Django + AD → Backend admin panels
Общее описание реализованного проекта
Единая точка входа для администраторов: логин через Active Directory,
персональный список доступных ресурсов по AD-группам, переход в нужную
админку по адресу https://<FQDN>/<adminusergroup>/<resource>/....
Все компоненты устанавливаются как обычные пакеты/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. Установка — автоматически (рекомендуемый способ)
# 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
Скрипт последовательно:
- ставит
haproxy,nginx,postgresql,redis-server, Python и dev-библиотеки для сборкиpython-ldap; - создаёт системного пользователя
adminportalбез права логина; - копирует
django_app/в/opt/admin-portal/django_app; - создаёт venv и ставит зависимости из
requirements.txt; - кладёт
.envв/etc/admin-portal/admin-portal.env(права 640, владелец root:adminportal); - создаёт роль и базу PostgreSQL по значениям из
.env, включает PostgreSQL и Redis; - прогоняет
migrateиcollectstaticот имениadminportal; - устанавливает и включает
systemd-юнитadmin-portal.service(Gunicorn на 127.0.0.1:8000); - раскладывает
haproxy.cfg/group_map.cfg, поднимает 2 демо-бэкенда через Nginx на 127.0.0.1:8081/8082, открывает 80/443 вufw.
После скрипта останутся 3 ручных шага (секреты и сертификат сознательно не автоматизированы):
# 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. Системные пакеты
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. Системный пользователь приложения
sudo useradd --system --home /opt/admin-portal --shell /usr/sbin/nologin adminportal
Шаг 3. Код приложения и виртуальное окружение
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. Переменные окружения
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.
sudo mkdir -p /etc/admin-portal
sudo install -o root -g adminportal -m 640 .env /etc/admin-portal/admin-portal.env
Шаг 5. PostgreSQL
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
sudo systemctl enable --now redis-server
По умолчанию Redis слушает только 127.0.0.1 — это то, что нужно, менять
bind в /etc/redis/redis.conf не требуется.
Шаг 7. Миграции и статика
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-сервис
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
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
Положить сертификат:
cat fullchain.pem privkey.pem | sudo tee /etc/haproxy/certs/fqdn.pem >/dev/null
sudo chmod 600 /etc/haproxy/certs/fqdn.pem
Проверить и включить:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl enable --now haproxy
Шаг 10. Демо-бэкенды (Nginx) — опционально, для сквозной проверки
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. Фаервол
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable # если ещё не включён — проверить, что не потеряете SSH (22/tcp)
Шаг 12. Суперпользователь и каталог ресурсов
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://<FQDN>/django-admin/ и создать ResourceGroup
(привязка к реальной AD-группе) и Resource (конкретный ресурс), либо
воспользоваться декларативным файлом:
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. Проверка
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'а в дальнейшем
- Поднять реальную админку ресурса — на этой же ВМ (новый порт на
127.0.0.1) либо на отдельном хосте сети, доступном от HAProxy, но не доступном напрямую из интернета. - Добавить в
/etc/haproxy/haproxy.cfgновуюbackend be_<name>секцию соserverэтой админки (по образцуbe_admin_resource1). - Добавить строку в
/etc/haproxy/group_map.cfg:/adgroup/resource-slug/ be_<name>. - Проверить и перечитать конфиг без разрыва соединений:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy. - Создать запись
Resourceв Django admin или черезsync_resources.
8. Эксплуатация
- Логи:
journalctl -u admin-portal -f(Django/Gunicorn),journalctl -u haproxy -f,/var/log/nginx/*.log(демо-бэкенды). - Обновление кода портала:
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.