qf — Руководство по развёртыванию
Обзор
| Компонент | Что это |
|---|---|
| qf-cp | Control plane — REST API, Web UI, gRPC-сервер, PKI, компилятор политик. Требует PostgreSQL. |
| qf-agent | Агент фаервола — eBPF/TC-датапас, работает на каждом защищаемом Linux-хосте. Требует ядро с нужными eBPF-фичами (bpf_loop + ring-buffer; mainline ≥5.17 или дистро-бэкпорт). |
Матрица ядро → датапас
Агент выбирает режим attach по возможностям ядра (пробит TCX, откат на classic); сборка матчера и лимит правил единые:
| Ядро | Attach | Сборка матчера | Лимит правил |
|---|---|---|---|
| ≥6.6 | TCX (bpf_link) | bpf_loop | 2048 |
| <6.6 (с нужными eBPF-фичами) | классический clsact+cls_bpf | bpf_loop | 2048 |
Агент НЕ завязан на версию ядра — при старте пробит нужные eBPF-фичи (bpf_loop-хелпер + ring-buffer мапа) и стартует, если они есть; иначе — падает с понятной ошибкой, называющей отсутствующую фичу. Причина: дистрибутивы бэкпортят хелперы на старые ядра (uname -r показывает версию ядра, но не отражает бэкпортнутые хелперы). На практике это mainline ≥5.17, но дистро-ядро с бэкпортом bpf_loop тоже подойдёт. На ядрах <6.6 при наличии Cilium загрузчик отказывается стартовать (конфликт общего clsact-qdisc) — для сосуществования с Cilium нужно ядро ≥6.6.
1. Control Plane в Kubernetes (Helm)
Предпосылки
- Kubernetes 1.24+
- PostgreSQL 14+, доступный из кластера
- Helm 3.10+
- CP нужен PersistentVolume под PKI-хранилище (CA, серты, ключ подписи бандлов)
1.1 Подготовить values-файл
Создать values-prod.yaml (не коммитить в git):
image:
repository: ghcr.io/qzmi4meister/qf-cp
pullPolicy: Always
secrets:
dbDSN: "postgres://qf:PASSWORD@postgres.qf.svc.cluster.local:5432/qf"
masterKey: "GENERATE_WITH: openssl rand -hex 32"
jwtSecret: "GENERATE_WITH: openssl rand -hex 32" # обязателен при replicaCount > 1 — helm откажет в деплое без него
adminEmail: "admin@example.com"
adminPassword: "CHANGE_ME"
env:
QF_CP_HOST: "qf.example.com" # hostname в TLS SAN; агенты подключаются по этому имени
QF_CP_ENDPOINT: "qf.example.com:31444" # адрес энролмента, анонсируемый агентам
QF_GRPC_ADDR: ":8443"
QF_ENROLL_ADDR: ":8444"
QF_HTTP_ADDR: ":8080"
QF_PKI_DIR: /etc/qf/pki
QF_LOG_LEVEL: info
pki:
persistentVolume:
storageClass: "longhorn" # или ваш storage class; "" = дефолт кластера
size: 1Gi
service:
type: ClusterIP # или NodePort / LoadBalancer — см. раздел 1.3
ingress:
enabled: true
className: "traefik" # или nginx
host: qf.example.com
tls:
- secretName: qf-cp-tls
hosts:
- qf.example.com
Сгенерировать секреты:
echo "masterKey: $(openssl rand -hex 32)"
echo "jwtSecret: $(openssl rand -hex 32)"
1.2 Установка / апгрейд
helm upgrade --install qf-cp \
oci://ghcr.io/qzmi4meister/helm/qf-cp \
--version 0.9.55 \
--namespace qf --create-namespace \
-f values-prod.yaml
Дождаться пода:
kubectl -n qf rollout status deployment/qf-cp
kubectl -n qf get pods
CP прогоняет миграции БД автоматически при первом старте. Логи:
kubectl -n qf logs -l app.kubernetes.io/name=qf-cp --tail=50
1.3 Опубликовать порты для агентов
Агентам нужен доступ к порту 8444 (энролмент) и порту 8443 (gRPC-стрим) на CP. Web UI (порт 8080) обычно публикуется только через Ingress.
Вариант A — NodePort (проще всего для on-prem / bare-metal):
Добавить в values:
service:
type: NodePort
grpcNodePort: 31443
enrollNodePort: 31444
Затем в env:
env:
QF_CP_ENDPOINT: "<any-node-ip>:31444"
QF_CP_HOST: "<any-node-ip-or-hostname>"
Вариант B — LoadBalancer (облако):
service:
type: LoadBalancer
После назначения EXTERNAL-IP задать QF_CP_HOST и QF_CP_ENDPOINT на этот IP/hostname.
Вариант C — отдельный Ingress под gRPC (advanced): TCP-passthrough Ingress/Gateway на порты 8443 и 8444 рядом с HTTP-Ingress под UI.
1.4 Проверка
# Health-check
curl https://qf.example.com/healthz
# Логин
curl -c cookies.txt -X POST https://qf.example.com/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"CHANGE_ME"}'
# Список хостов (изначально пуст)
curl -b cookies.txt -H 'X-Tenant-ID: <tenant-id>' \
https://qf.example.com/hosts | jq .
1.5 Апгрейд CP
helm upgrade qf-cp \
oci://ghcr.io/qzmi4meister/helm/qf-cp \
--version NEW_VERSION \
--namespace qf \
-f values-prod.yaml \
--set image.tag=NEW_VERSION
2. Control Plane на отдельной Linux-машине
Запуск qf-cp напрямую как systemd-сервис на любом Linux x86_64/arm64-сервере.
2.1 Предпосылки
- Linux x86_64 или arm64
- PostgreSQL 14+ (локальный или удалённый)
- Бинарь из GitHub Releases или собранный из исходников
2.2 Установить PostgreSQL и создать базу
# Debian/Ubuntu
sudo apt install -y postgresql
sudo -u postgres psql <<'SQL'
CREATE USER qf WITH PASSWORD 'CHANGE_ME';
CREATE DATABASE qf OWNER qf;
SQL
2.3 Скачать бинарь
VERSION=0.9.55
curl -LO https://github.com/qzmi4meister/qf/releases/download/v${VERSION}/qf-cp_${VERSION}_linux_amd64.tar.gz
tar xzf qf-cp_${VERSION}_linux_amd64.tar.gz
sudo mv qf-cp /usr/local/bin/qf-cp
sudo chmod +x /usr/local/bin/qf-cp
Либо собрать из исходников:
# Требует Go 1.25+, Node.js 20+
git clone https://github.com/qzmi4meister/qf && cd qf
make ui-build
go build -o /usr/local/bin/qf-cp ./cp/cmd/qf-cp/
2.4 Создать конфигурацию
sudo mkdir -p /etc/qf/pki
sudo tee /etc/qf/cp.env <<'EOF'
QF_DB_DSN=postgres://qf:CHANGE_ME@localhost:5432/qf
QF_MASTER_KEY=GENERATE_WITH_openssl_rand_-hex_32
QF_JWT_SECRET=GENERATE_WITH_openssl_rand_-hex_32
QF_CP_HOST=qf.example.com
QF_CP_ENDPOINT=qf.example.com:8444
QF_HTTP_ADDR=:8080
QF_GRPC_ADDR=:8443
QF_ENROLL_ADDR=:8444
QF_PKI_DIR=/etc/qf/pki
QF_LOG_LEVEL=info
QF_ADMIN_EMAIL=admin@example.com
QF_ADMIN_PASSWORD=CHANGE_ME
EOF
sudo chmod 600 /etc/qf/cp.env
Сгенерировать ключи перед правкой:
openssl rand -hex 32 # вставить как QF_MASTER_KEY
openssl rand -hex 32 # вставить как QF_JWT_SECRET
2.5 Создать systemd-юнит
sudo tee /etc/systemd/system/qf-cp.service <<'EOF'
[Unit]
Description=qf Control Plane
After=network.target postgresql.service
Requires=network.target
[Service]
Type=simple
User=root
EnvironmentFile=/etc/qf/cp.env
ExecStart=/usr/local/bin/qf-cp
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now qf-cp
sudo systemctl status qf-cp
2.6 Проверка
# Проверить, что миграции прошли
sudo journalctl -u qf-cp -n 30
# Health-эндпоинт
curl http://localhost:8080/healthz
# Web UI
# http://qf.example.com:8080/app
2.7 Фаервол
Открыть порты, чтобы агенты достучались до CP:
# ufw (Debian/Ubuntu)
sudo ufw allow 8443/tcp comment "qf gRPC"
sudo ufw allow 8444/tcp comment "qf enrollment"
sudo ufw allow 8080/tcp comment "qf UI"
3. Агент: развёртывание через Ansible
Ansible-роль скачивает пакет из GitHub Releases, ставит его и запускает/включает systemd-сервис. Поддерживает и apt (Debian/Ubuntu), и dnf/yum (RHEL/Fedora/Rocky).
3.1 Настроить инвентори
Отредактировать deploy/ansible/inventory.yml:
all:
children:
qf_agents:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
db1:
ansible_host: 192.168.1.20
vars:
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
3.2 Настроить агента перед деплоем
Роль пишет минимальный /etc/qf/agent.conf, когда заданы qf_endpoint и qf_enroll_token
(inventory-vars); иначе дефолт из пакета остаётся нетронутым.
Минимальный /etc/qf/agent.conf (два ключа — остальное выводится/автодетектится):
QF_ENDPOINT=qf.example.com
QF_ENROLL_TOKEN=<token-from-cp-ui>
QF_ENDPOINT выводит эндпоинты gRPC (:31443), энролмента (:31444) и REST (https://host).
С явным портом host:P → gRPC :P, энролмент :P+1. Интерфейс автодетектится из
дефолтного маршрута; переопределить — QF_IFACE.
CA энролмента: роли также нужен якорь доверия к CA для агента. Задаётся одна из
role-vars — qf_ca_src (путь к CA PEM CP на control-ноде, копируется на хост),
qf_enroll_ca_fingerprint (64-hex air-gap-пин) или qf_enroll_ca_fetch: true (TOFU,
только публичный CP). См. install-airgapped.md.
Получить enrollment-токен: CP Web UI → Tokens → New token → скопировать значение.
Либо через API:
curl -s -b cookies.txt -X POST https://qf.example.com/tokens \
-H 'Content-Type: application/json' \
-H 'X-Tenant-ID: <tenant-id>' \
-d '{"label_template":{"env":"prod"},"ttl_seconds":86400,"max_uses":0}' | jq -r .token
Bulk-токен может нести
"target_group_id":"<uuid>"— заенролленные хосты авто-вступают в эту host-группу (живое членство, наследуют её лейблы). См.fleet-management.md.
3.3 Деплой
# Установить pip-зависимости (однократно)
pip install ansible
# Деплой на все хосты группы qf_agents
make deploy-agent
# или напрямую:
ansible-playbook -i deploy/ansible/inventory.yml deploy/ansible/deploy-agent.yml
# Деплой на один хост
make deploy-agent target=web1
# или:
ansible-playbook -i deploy/ansible/inventory.yml deploy/ansible/deploy-agent.yml --limit web1
Роль:
- Скачивает
.debили.rpmиз GitHub Releases (версия автодетектится изversion/version.go) - Ставит через
aptилиdnf(сохраняет существующий конфиг черезconfold) - Запускает и включает
qf-agent.service - Проверяет, что сервис активен (агент сам пробит нужные eBPF-фичи при старте — если ядро их не даёт, сервис не поднимется и в логе будет понятная ошибка с именем фичи; версию ядра роль НЕ проверяет)
3.4 Переопределить версию пакета
ansible-playbook -i deploy/ansible/inventory.yml deploy/ansible/deploy-agent.yml \
-e qf_agent_version=0.9.55
4. Агент: ручная установка на Linux
Работает на любом Linux x86_64-хосте — bare metal, VM или k8s-нода. Нужно ядро с eBPF-фичами bpf_loop + ring-buffer (mainline ≥5.17 или дистро-бэкпорт).
4.1 Установить пакет
VERSION=0.9.55
# Debian / Ubuntu
curl -LO https://github.com/qzmi4meister/qf/releases/download/v${VERSION}/qf-agent_${VERSION}_amd64.deb
sudo dpkg -i qf-agent_${VERSION}_amd64.deb
# RHEL / Fedora / Rocky
curl -LO https://github.com/qzmi4meister/qf/releases/download/v${VERSION}/qf-agent-${VERSION}.x86_64.rpm
sudo rpm -i qf-agent-${VERSION}.x86_64.rpm
Пакет ставит:
/usr/sbin/qf-agent— бинарь/etc/qf/agent.conf— шаблон конфига/lib/systemd/system/qf-agent.service— systemd-юнит
4.2 Настроить
sudo nano /etc/qf/agent.conf
# CP endpoint (host или host:port). Выводит gRPC :31443, enroll :31444,
# REST https://<host>. С явным портом P: gRPC :P, enroll :P+1.
QF_ENDPOINT=qf.example.com
# Bootstrap-токен — из CP UI (страница Tokens) или API.
QF_ENROLL_TOKEN=tok_xxxxxxxxxxxx
# ── Якорь доверия к CA энролмента (выбрать один; обязателен, если REST-серт CP не доверен web-PKI) ──
# Enrollment gRPC использует внутренний CA CP, поэтому агент обязан ему доверять.
# QF_ENROLL_CA=/etc/qf/ca.crt # заранее положенный CA PEM (скопировать с CP)
# QF_ENROLL_CA_FINGERPRINT=<64-hex> # air-gap-пин: тянет CA по REST, принимает iff sha256(DER) совпал
# QF_ENROLL_CA_FETCH=true # TOFU-fetch — ТОЛЬКО если REST-серт CP публично доверен
# Полная матрица + как получить отпечаток: docs/guides/install-airgapped.md
# ── Опционально (разумные дефолты; добавлять по нужде) ──
# QF_IFACE= # пусто = автодетект из дефолтного маршрута
# QF_PKI_DIR=/etc/qf
# QF_LOG_LEVEL=info
# QF_FAIL_CLOSED=false
# QF_DROP_IPV6=true # ДЕФОЛТ: режет весь IPv6 (opt-in гейт).
# =false → полный v6-энфорс (CIDR/ipset/conntrack); на dual-stack/Cilium разрешить ICMPv6 ND/RA
# QF_DENY_UNKNOWN_PROTO=false # ДЕФОЛТ: неподдержанный L4 (SCTP/GRE/ESP/…) проходит
# без энфорсмента. =true → подчиняется default-action (дроп при
# DENY). ESP=IPsec / GRE=VPN / SCTP=телеком могут быть легитимны —
# сначала проверить. См. ops-runbook §8.4.
Интерфейс автодетектится из дефолтного маршрута. Переопределить или проверить:
ip route get 8.8.8.8 | awk '{print $5; exit}'
4.3 Запустить агента
sudo systemctl enable --now qf-agent
sudo systemctl status qf-agent
При первом старте агент:
- Подключается к выведенному эндпоинту энролмента (
QF_ENDPOINT:31444) и предъявляет bootstrap-токен - Шлёт CSR; CP подписывает и возвращает mTLS-сертификаты
- Хранит серты в
QF_PKI_DIR— последующие старты пропускают энролмент - Подключается к выведенному gRPC-эндпоинту (
QF_ENDPOINT:31443) по mTLS и ждёт бандлы политик
4.4 Проверить энролмент
# Логи агента
journalctl -u qf-agent -f
# На CP — хост должен появиться со статусом "active"
curl -b cookies.txt -H 'X-Tenant-ID: <tenant-id>' \
https://qf.example.com/hosts | jq '.[] | {hostname, status}'
4.5 Апгрейд агента
VERSION=NEW_VERSION
# Debian / Ubuntu — сохраняет /etc/qf/agent.conf
sudo dpkg -i qf-agent_${VERSION}_amd64.deb
# RHEL
sudo rpm -U qf-agent-${VERSION}.x86_64.rpm
sudo systemctl restart qf-agent
4.6 Сетевые топологии
- Один NIC — агент автодетектит iface дефолтного маршрута и цепляется туда.
- 2 NIC → bonding (рекомендуется) —
bond0держит маршрут, qf цепляется кbond0; failover slave'а прозрачен (без ре-attach). - Cilium-ноды — на ядре ≥6.6 qf TCX сосуществует с Cilium clsact на том же iface; на <6.6 + Cilium загрузчик не стартует.
- Multi-homed БЕЗ bond (2 активных NIC в разных подсетях) сейчас покрывает только один iface — нужен bonding.
Аутентификация и RBAC
- RBAC-роли:
admin(всё, включая управление пользователями),editor(мутации),auditor(только чтение). API-токены для автоматизации/CI. - OIDC SSO опционально (любой провайдер) рядом с локальными аккаунтами.
- Первичный admin создаётся при первом старте из
QF_ADMIN_EMAIL/QF_ADMIN_PASSWORD.
Устранение неполадок
Хост завис в enrolling
journalctl -u qf-agent -n 50— искать TLS- или connection-ошибкиnc -zv qf.example.com 31444— проверить доступность порта энролмента (голыйQF_ENDPOINTвыводит :31444; standalone CP использует :8444)- Проверить, что токен валиден и не истёк: CP UI → Tokens
- Проверить, что
QF_CP_HOSTна CP совпадает с hostname/IP вQF_ENDPOINT— mismatch TLS SAN валит handshake - Отсутствует/неверный CA энролмента →
certificate signed by unknown authority: задатьQF_ENROLL_CA/QF_ENROLL_CA_FINGERPRINT(см. install-airgapped.md)
Бандл не приходит
- В логах агента должны быть
bundle receivedиbundle applied - Проверить, что mTLS-серты есть:
ls -la /etc/qf/—agent.crt,agent.key,ca.crtдолжны присутствовать - Проверить доступность gRPC-порта:
nc -zv qf.example.com 31443(standalone CP: :8443) - Проверить срок серта:
openssl x509 -in /etc/qf/agent.crt -noout -dates - Если серт истёк: остановить агента, удалить
/etc/qf/agent.crtи/etc/qf/agent.key, вернутьQF_ENROLL_TOKENв конфиг, перезапустить
Агент не стартует — BPF-ошибки
journalctl -u qf-agent -n 20
BPF load failed: permission denied— не хватает capabilities; проверить, что в systemd-юните естьAmbientCapabilities=CAP_NET_ADMIN CAP_BPF CAP_PERFMON(CAP_SYS_ADMIN не требуется)kernel lacks required eBPF features: bpf_loop helper (...)— ядру не хватает eBPF-хелпера/мапы для датапаса; нуженbpf_loop+ ring-buffer (mainline ≥5.17 или дистро-бэкпорт). Агент называет отсутствующую фичу в логе.interface not found—QF_IFACEне совпадает с реальным именем интерфейса; проверитьip link
CP не стартует — ошибки БД
migrations failed— у PostgreSQL-пользователя нет правCREATE TABLE; выдать:GRANT ALL ON DATABASE qf TO qf;connection refused— проверить host/port вQF_DB_DSNи что PostgreSQL запущенmaster key must be 32 bytes—QF_MASTER_KEYдолжен быть 64-символьной hex-строкой (openssl rand -hex 32)
TLS-ошибки
| Ошибка | Причина | Фикс |
|---|---|---|
certificate signed by unknown authority | У агента неверный ca.crt или CP перегенерировал CA | Переэнроллить агента |
certificate has expired | Истёк TTL серта агента (дефолт 90д) | Удалить старые серты, переэнроллить |
x509: certificate is valid for X, not Y | Mismatch QF_CP_HOST | Задать QF_CP_HOST на hostname, который используют агенты |