Files
reverse-proxy/README.md
T

397 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# reverse-proxy
Периметровая конфигурация **HAProxy** для сети **kalinamall.ru**: Exchange, RDS (веб/шлюз по HTTPS), 1С, почтовый шлюз KSMG. Прямой RDP (3389) через HAProxy не используется — клиенты идут через RDS на **443** (`rds.kalinamall.ru`).
TLS: на целевых серверах используются **wildcard-сертификаты GlobalSign**; HAProxy на **443** работает в режиме **TCP + SNI** и **не терминирует** TLS — сертификаты остаются на бэкендах.
Репозиторий: [https://github.com/PTah/reverse-proxy](https://github.com/PTah/reverse-proxy)
## Клонирование на Ubuntu, обновление и доступ без постоянного ввода пароля
### Установка Git
```bash
sudo apt update
sudo apt install -y git
```
### Первое клонирование
**Публичный** репозиторий можно клонировать **без логина и пароля**:
```bash
cd ~
git clone https://github.com/PTah/reverse-proxy.git
cd reverse-proxy
```
Если репозиторий станет **приватным** или понадобятся **push**-правки с сервера, используйте один из способов ниже.
### Обновление локальной копии
```bash
cd ~/reverse-proxy
git pull origin main
```
После `git pull` снова скопируйте при необходимости файлы в `/etc/haproxy/` (см. раздел «Установка конфигурации»).
### Как не вводить логин и пароль при каждом `git pull` / `git push`
#### Способ 1: SSH-ключ (предпочтительно)
1. Создайте ключ (имя файла можно своё):
```bash
ssh-keygen -t ed25519 -C "ubuntu-haproxy" -f ~/.ssh/id_ed25519_github
```
Парольную фразу можно задать или оставить пустой (на сервере оцените риски).
2. Покажите **публичный** ключ и добавьте его в GitHub: **Settings → SSH and GPG keys → New SSH key**.
```bash
cat ~/.ssh/id_ed25519_github.pub
```
3. Проверьте доступ:
```bash
ssh -T git@github.com
```
4. Клонируйте по **SSH** (или смените URL у уже существующего клона):
```bash
git clone git@github.com:PTah/reverse-proxy.git
# уже склонировано по HTTPS — переключение:
# git remote set-url origin git@github.com:PTah/reverse-proxy.git
```
Дальше `git pull` / `git push` идут по SSH **без пароля GitHub** (при необходимости один раз вводится только passphrase ключа, если вы её задали; её можно кешировать через `ssh-agent`).
#### Способ 2: HTTPS + Personal Access Token (PAT)
GitHub **не принимает** обычный пароль от аккаунта для операций `git clone` / `git pull` / `git push` по **HTTPS**. Вместо пароля нужно создать **Personal Access Token (PAT)** и вводить его там, где Git спрашивает пароль.
##### Создание классического токена (Tokens classic) — проще всего для Git
1. Войдите на **github.com** под нужным аккаунтом.
2. В правом верхнем углу откройте меню **аватара** → **Settings** (Настройки).
3. В **левой колонке** прокрутите вниз до раздела **Developer settings** (Параметры разработчика) и откройте его.
4. Выберите **Personal access tokens** → **Tokens (classic)**.
5. Нажмите **Generate new token** → **Generate new token (classic)**.
6. Заполните поля:
- **Note** — произвольное имя (например `ubuntu-haproxy-pull`), чтобы потом понимать, зачем токен.
- **Expiration** — срок действия (чем короче, тем безопаснее; для сервера часто выбирают ограниченный срок и потом перевыпускают).
- **Select scopes** — для **приватного** репозитория и `pull`/`push` включите минимум **`repo`** (полный доступ к репозиториям). Если нужен только **чтение** приватного репо, в классических токенах по-прежнему часто отмечают **`repo`**, т.к. узкого «только read» в classic нет; для **только pull** удобнее **fine-grained** (ниже).
7. Нажмите **Generate token** внизу страницы.
8. **Сразу скопируйте** строку токена (начинается часто с `ghp_…`). GitHub покажет её **один раз**; после ухода со страницы повторно не отобразит.
##### Fine-grained token (по желанию, гибче по правам)
1. Те же **Settings** → **Developer settings** → **Personal access tokens** → **Fine-grained tokens**.
2. **Generate new token**.
3. Укажите **имя**, **срок**, в блоке **Repository access** выберите «только выбранный репозиторий» **reverse-proxy** (или «все» — по политике безопасности).
4. В **Permissions** для чтения/обновления кода задайте **Contents**: Read-only или Read and write, при необходимости **Metadata**: Read.
5. Создайте токен и **скопируйте** его (формат обычно `github_pat_…`).
##### Использование PAT в Git на Ubuntu
- **Username** (логин): ваш **логин GitHub** (короткое имя пользователя), не e-mail, если Git явно просит username.
- **Password**: вставьте **целиком PAT** (не пароль от сайта).
Пример первого клонирования по HTTPS:
```bash
git clone https://github.com/PTah/reverse-proxy.git
# при запросе: Username = ваш_логин_github
# Password = вставить PAT
```
##### Чтобы не вводить PAT каждый раз
Токен хранится **в открытом виде** в `~/.git-credentials` — используйте только на **доверенных** машинах:
```bash
git config --global credential.helper store
cd ~/reverse-proxy
git pull origin main
# один раз ввести Username + PAT — дальше подставятся из файла
```
Кеш в памяти на заданное время (секунды), без постоянной записи в файл:
```bash
git config --global credential.helper 'cache --timeout=28800'
```
После истечения срока кеша или срока действия токена на GitHub снова создайте новый PAT и при необходимости обновите сохранённые учётные данные (удалите строчку с `github.com` из `~/.git-credentials` или снова выполните `git pull` и введите новый токен).
#### Способ 3: только чтение, репозиторий публичный
Для **публичного** репозитория `git clone` и `git pull` по HTTPS **учётные данные не нужны** — отдельная настройка не требуется.
## Соответствие имён и адресов
| Служба | Внешнее имя (клиенты) | Внутренний хост / IP | Порт снаружи |
|--------|------------------------|----------------------|--------------|
| Microsoft Exchange (OWA `https://ext.kalinamall.ru/owa`, EWS, MAPI/HTTPS и т.д.) | `ext.kalinamall.ru` | fifth.kalinamall.ru — **192.168.160.42** | 443 (TCP/SNI) |
| Autodiscover | `autodiscover.kalinamall.ru` | тот же Exchange — **192.168.160.42** | 443 (TCP/SNI) |
| RDS | `rds.kalinamall.ru` (**443** на HAProxy) | k6a-dc3.b26.kalinamall.ru — **192.168.160.40**, служба **4430** | 443 (SNI → внутр. **4430**) |
| 1С | `erp.kalinamall.ru` | hp-serv.b26.kalinamall.ru — **192.168.160.150** | 443 |
| Почтовый шлюз (KSMG) | `ksmg.kalinamall.ru` | ksmg.kalinamall.ru — **192.168.160.57** | 25 |
DNS: записи **A** для `ext`, **`autodiscover`**, `rds`, `erp`, `ksmg` должны указывать на **публичный IP** хоста с HAProxy. Имя `autodiscover.kalinamall.ru` должно быть в **SAN** wildcard `*.kalinamall.ru` или отдельно в сертификате на Exchange.
Доступ по **SSH** к внутренним хостам (например Ubuntu **192.168.160.85**) в этом репозитории **не** настраивается через HAProxy — организуйте отдельно (прямой проброс, VPN, бастион и т.д.).
В режиме **TCP + SNI** отдельные строки в HAProxy для путей вроде `/owa` **не нужны**: после согласования TLS клиент шлёт HTTP внутри шифрования, разбор путей делает **IIS/Exchange** на `192.168.160.42`.
## Установка HAProxy на Ubuntu 24.04 (Noble)
Для этого репозитория нужен HAProxy **не ниже 2.4**. Конструкции `req_ssl_sni` и `tcp-request content accept` (разбор ClientHello для SNI в TCP-режиме) входят в штатные сборки **2.8.x** из Ubuntu 24.04 и во все актуальные ветки **3.x**.
### Вариант 1: пакет из репозитория Ubuntu (HAProxy 2.8.x)
Обычно этого достаточно для данного конфига:
```bash
sudo apt update
sudo apt install -y haproxy
haproxy -v
```
Проверьте, что версия **2.4+** (на Noble обычно **2.8.x**).
### Вариант 2: последняя ветка 3.x (PPA Vincent Bernat)
Актуальные пакеты **HAProxy 3.3.x** для Noble публикуются в PPA [vbernat/haproxy-3.3](https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-3.3) (номер микроверсии может обновляться; см. страницу PPA). Не подключайте одновременно несколько PPA с разными ветками HAProxy.
```bash
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:vbernat/haproxy-3.3
sudo apt update
sudo apt install -y haproxy
haproxy -v
sudo systemctl enable --now haproxy
```
Другие ветки того же автора (при необходимости): [haproxy-3.2](https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-3.2), [haproxy-3.1](https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-3.1), [haproxy-3.0](https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-3.0), а также [haproxy-2.8](https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-2.8) — всегда выбирайте **одну** строку версии.
### Проверка синтаксиса конфига
```bash
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
```
## Установка конфигурации из репозитория
1. Убедитесь, что установлен HAProxy **2.4+** (см. раздел выше).
2. Скопируйте `haproxy.cfg` в `/etc/haproxy/haproxy.cfg`. Пример, если репозиторий лежит в домашнем каталоге:
```bash
sudo cp ~/reverse-proxy/haproxy.cfg /etc/haproxy/haproxy.cfg
```
3. Проверка и перезапуск:
```bash
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
```
## Проверка `ssl verify none` на бэкендах
В `backend` для 443 указано `ssl verify none`, чтобы health-check и при необходимости установка соединения не ломались на внутренних именах/SAN. Если вы выпустите внутренние доверенные CA и зададите их в системе, можно включить проверку (`verify required` + `ca-file`).
## Порты на фаерволе
На хосте с HAProxy с интернета обычно открывают только то, что слушает конфиг: **25**, **443** (и при использовании — **8404** для stats). Остальной трафик — по политике безопасности.
## Порт 80 и Lets Encrypt / ACME (на будущее)
Сейчас в `haproxy.cfg` **порт 80 не используется**. Ниже — что иметь в виду, если появится, например, **Ubuntu** во внутренней сети, на которую нужно выпускать сертификаты **Let’s Encrypt** с автообновлением.
### Нужен ли для ACME именно порт 80?
**Не всегда.** У Let’s Encrypt (протокол **ACME**) есть несколько типов проверки владения доменом:
| Тип | Порт | Комментарий |
|-----|------|-------------|
| **HTTP-01** | **80** (HTTP) | Центр сертификации делает запрос вида `http://ваш-домен/.well-known/acme-challenge/...`. С **интернета** до отвечающего хоста порт **80** должен быть **доступен** (напрямую или через проброс на HAProxy). |
| **DNS-01** | не нужен 80/443 | Запись в DNS; удобно, когда **80 снаружи открывать нельзя** или сертификат для внутреннего имени. |
| **TLS-ALPN-01** | **443** | Проверка по TLS; порт 80 не обязателен. |
Итого: при классическом **Certbot + webroot/standalone** с проверкой **HTTP-01** порт **80** действительно участвует. При **DNS-01** отдельный HTTP на 80 для выпуска не нужен.
Автообновление обычно делают **`certbot renew`** по **systemd timer** или cron — это на стороне машины с Certbot, не в HAProxy.
### Если позже понадобится HTTP на 80 через этот HAProxy (например, HTTP-01)
1. На **пограничном роутере** разрешить с интернета **TCP 80** на IP хоста с HAProxy (или на тот хост, где будет отвечать ACME).
2. В **`haproxy.cfg`** добавить отдельный **HTTP**-фронтенд на **80** и отправлять только путь проверки на бэкенд с ответом ACME (остальное — редирект на HTTPS, пустой ответ или отказ — по политике).
### Пример: два Ubuntu в LAN и HTTP-01 через один периметровый HAProxy
Допустим, во внутренней сети два сервера **Ubuntu**, для обоих нужны публичные имена и периодическое обновление сертификатов Let’s Encrypt по **HTTP-01**:
| Имя (снаружи) | Внутренний IP |
|----------------|-----------------|
| **ubuntu1.kalinamall.ru** | **192.168.160.90** |
| **ubuntu2.kalinamall.ru** | **192.168.160.60** |
**DNS:** записи **A** для `ubuntu1.kalinamall.ru` и `ubuntu2.kalinamall.ru` должны указывать на **тот же публичный IP**, что и для остальных сервисов за HAProxy (чтобы запросы Let’s Encrypt пришли на **порт 80** этого хоста).
**На каждой Ubuntu:** нужен **HTTP на порту 80** и каталог под **webroot** Certbot (`/.well-known/acme-challenge/…`). Ниже — пошагово для **nginx** (типичный вариант); в конце — кратко про **Apache**. Если открывать 80 не хотите, используйте **DNS-01** на той же машине (без этого блока).
#### Пошагово: nginx + Certbot webroot на Ubuntu
Выполняйте **на каждой** Ubuntu отдельно; подставляйте своё имя (`ubuntu1.kalinamall.ru` или `ubuntu2.kalinamall.ru`).
1. **Установите nginx и certbot**
```bash
sudo apt update
sudo apt install -y nginx certbot
```
2. **Каталог для challenge** (Certbot будет писать сюда файлы, nginx — отдавать):
```bash
sudo mkdir -p /var/www/certbot/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/certbot
```
3. **Виртуальный хост только под ACME и имя сервера**
Создайте файл, например `/etc/nginx/sites-available/acme-le.conf` (на одной машине в `server_name` — `ubuntu1.kalinamall.ru`, на другой — `ubuntu2.kalinamall.ru`):
```nginx
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name ubuntu1.kalinamall.ru;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
return 404;
}
}
```
4. **Включите сайт и перезапустите nginx**
```bash
sudo ln -sf /etc/nginx/sites-available/acme-le.conf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
```
Убедитесь, что снаружи (через HAProxy и открытый WAN **80**) до этой машины доходит запрос с правильным **Host** — иначе Lets Encrypt не пройдёт проверку.
5. **Первый выпуск сертификата (webroot)**
Подставьте свой домен и e-mail:
```bash
sudo certbot certonly --webroot -w /var/www/certbot \
-d ubuntu1.kalinamall.ru \
--email admin@example.com --agree-tos --non-interactive
```
При первом запуске можно убрать `--non-interactive`, чтобы согласиться с условиями в диалоге.
**Где лежат файлы и что с ними делать (HTTPS в nginx)**
После успешного `certbot certonly` каталог (симлинки) будет таким:
- **`/etc/letsencrypt/live/ubuntu1.kalinamall.ru/`** — имя совпадает с **первым** `-d` в команде certbot (на второй машине — `ubuntu2...`).
- **`fullchain.pem`** — ваш сертификат **и** цепочка до Let’s Encrypt; его указывают в **`ssl_certificate`** (так браузеры доверяют сайту).
- **`privkey.pem`** — закрытый ключ; в **`ssl_certificate_key`**. Файлы в `archive/`, в `live/` — только ссылки; при **`certbot renew`** обновляются именно они, пути в nginx **менять не нужно**.
Добавьте **второй** `server` для **HTTPS** (отдельный файл или тот же `acme-le.conf` ниже блока `:80`), подставив своё имя и корень сайта:
```nginx
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ubuntu1.kalinamall.ru;
ssl_certificate /etc/letsencrypt/live/ubuntu1.kalinamall.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ubuntu1.kalinamall.ru/privkey.pem;
# Рекомендуемые параметры TLS (если пакет certbot их положил):
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
Проверка и применение:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Если **443** с интернета идёт **на эту же** Ubuntu (напрямую или отдельным пробросом с маршрутизатора), снаружи откройте **TCP 443** до неё. Если HTTPS терминирует только **HAProxy**, а до Ubuntu — снова прокси, то сертификат Lets Encrypt на Ubuntu нужен **той службе**, которая реально открывает **TLS** на этой машине (или выпускайте сертификат на том узле, где крутится HTTPS).
**Редирект HTTP → HTTPS на :80 (по желанию, после того как HTTPS завёлся):** в блоке `server` на **порту 80** оставьте **`location ^~ /.well-known/acme-challenge/`** как есть, а вместо `return 404` для **`location /`** поставьте **`return 301 https://$host$request_uri;`** — обновление сертификата по-прежнему идёт по **HTTP** на путь **`.well-known`**, остальное уходит на **https://**.
6. **Проверка автообновления**
```bash
sudo systemctl enable --now certbot.timer
sudo certbot renew --dry-run
```
7. **Перезагрузка nginx после успешного renew**
Создайте скрипт и сделайте его исполняемым:
```bash
sudo install -d /etc/letsencrypt/renewal-hooks/deploy
echo 'systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/01-reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/01-reload-nginx.sh
```
Таймер **`certbot.timer`** по расписанию вызывает `certbot renew`; при обновлении сертификата выполняются hook’и из `renewal-hooks/deploy`.
#### Кратко: Apache вместо nginx
1. `sudo apt install -y apache2 certbot python3-certbot-apache` (или только `certbot` и webroot без плагина apache).
2. Корень для challenge: например `sudo mkdir -p /var/www/certbot/.well-known/acme-challenge` и в виртуальном хосте на **:80** для вашего `ServerName` — `Alias /.well-known/acme-challenge /var/www/certbot/.well-known/acme-challenge` и доступ `Require all granted` для этого каталога.
3. `sudo apachectl configtest && sudo systemctl reload apache2`
4. `sudo certbot certonly --webroot -w /var/www/certbot -d ubuntu1.kalinamall.ru` (и аналогично для второго имени на второй машине).
5. Таймер **`certbot.timer`** и при необходимости hook с `systemctl reload apache2`.
**На HAProxy:** один фронтенд на **80**, маршрутизация по заголовку **Host** + путь ACME на разные бэкенды:
```text
frontend fe_http
bind *:80
mode http
option httplog
acl path_acme path_beg /.well-known/acme-challenge/
acl host_u1 hdr(host) -i ubuntu1.kalinamall.ru
acl host_u2 hdr(host) -i ubuntu2.kalinamall.ru
use_backend be_acme_u1 if path_acme host_u1
use_backend be_acme_u2 if path_acme host_u2
http-request deny if !path_acme
backend be_acme_u1
mode http
server u1 192.168.160.90:80 check
backend be_acme_u2
mode http
server u2 192.168.160.60:80 check
```
Lets Encrypt при проверке обращается к **`http://ubuntu1.kalinamall.ru/.well-known/...`** или **`http://ubuntu2...`** — HAProxy по **Host** пересылает запрос на нужный внутренний **:80**, где локальный веб-сервер отдаёт файлы challenge.
Варианты без проброса HTTP на две машины: на каждой Ubuntu **DNS-01** (порт 80 на периметре тогда не обязателен для этих сертификатов); либо **TLS-ALPN-01** по **443** (потребуется уже другая схема на HAProxy, не этот пример).
После добавления `bind :80` допишите в раздел **«Порты на фаерволе»** выше порт **80** и выполните `sudo haproxy -c -f /etc/haproxy/haproxy.cfg`.
## Лицензия
Конфигурация поставляется как есть, без гарантий. Используйте на свой риск.