Перейти к основному содержимому

qf — Руководство по развёртыванию

Обзор

КомпонентЧто это
qf-cpControl 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.6TCX (bpf_link)bpf_loop2048
<6.6 (с нужными eBPF-фичами)классический clsact+cls_bpfbpf_loop2048

Агент НЕ завязан на версию ядра — при старте пробит нужные 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 → TokensNew 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

Роль:

  1. Скачивает .deb или .rpm из GitHub Releases (версия автодетектится из version/version.go)
  2. Ставит через apt или dnf (сохраняет существующий конфиг через confold)
  3. Запускает и включает qf-agent.service
  4. Проверяет, что сервис активен (агент сам пробит нужные 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

При первом старте агент:

  1. Подключается к выведенному эндпоинту энролмента (QF_ENDPOINT:31444) и предъявляет bootstrap-токен
  2. Шлёт CSR; CP подписывает и возвращает mTLS-сертификаты
  3. Хранит серты в QF_PKI_DIR — последующие старты пропускают энролмент
  4. Подключается к выведенному 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

  1. journalctl -u qf-agent -n 50 — искать TLS- или connection-ошибки
  2. nc -zv qf.example.com 31444 — проверить доступность порта энролмента (голый QF_ENDPOINT выводит :31444; standalone CP использует :8444)
  3. Проверить, что токен валиден и не истёк: CP UI → Tokens
  4. Проверить, что QF_CP_HOST на CP совпадает с hostname/IP в QF_ENDPOINT — mismatch TLS SAN валит handshake
  5. Отсутствует/неверный CA энролмента → certificate signed by unknown authority: задать QF_ENROLL_CA / QF_ENROLL_CA_FINGERPRINT (см. install-airgapped.md)

Бандл не приходит

  1. В логах агента должны быть bundle received и bundle applied
  2. Проверить, что mTLS-серты есть: ls -la /etc/qf/agent.crt, agent.key, ca.crt должны присутствовать
  3. Проверить доступность gRPC-порта: nc -zv qf.example.com 31443 (standalone CP: :8443)
  4. Проверить срок серта: openssl x509 -in /etc/qf/agent.crt -noout -dates
  5. Если серт истёк: остановить агента, удалить /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 foundQF_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 bytesQF_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 YMismatch QF_CP_HOSTЗадать QF_CP_HOST на hostname, который используют агенты