qf — эксплуатационный runbook
Операционные процедуры для запуска qf в проде.
Содержание
- Бэкап и восстановление
- Ротация секретов
- Управление сертификатами
- Обслуживание PostgreSQL
- Масштабирование qf-cp
- Debug-чеклист
- Управление политиками
- Известные ограничения
1. Бэкап и восстановление
1.1 Бэкап PKI-каталога
PKI-каталог (QF_PKI_DIR, дефолт /etc/qf/pki) содержит:
| Файл | Описание |
|---|---|
ca.crt | Сертификат CA (публичный, можно распространять) |
ca.key | Приватный ключ CA (AES-256-GCM, зашифрован QF_MASTER_KEY) |
server.crt / server.key | TLS-сертификат и ключ сервера CP |
bundle-signing.key / bundle-signing.pub | Пара ключей Ed25519 для подписи бандлов |
# Бэкап
PKI_DIR=/etc/qf/pki
BACKUP_FILE="qf-pki-$(date +%Y%m%d-%H%M%S).tar.gz"
tar -czf "$BACKUP_FILE" -C "$(dirname $PKI_DIR)" "$(basename $PKI_DIR)"
chmod 600 "$BACKUP_FILE"
# Хранить бэкап в защищённом месте (шифрование at-rest)
# Приватный ключ CA уже зашифрован QF_MASTER_KEY, но бэкап всё равно хранится безопасно
Важно: PKI всегда бэкапится вместе с
QF_MASTER_KEY. Бэкап PKI без соответствующего мастер-ключа бесполезен.
1.2 Восстановление PKI-каталога
BACKUP_FILE="qf-pki-20260101-120000.tar.gz"
TARGET_DIR=/etc/qf
tar -xzf "$BACKUP_FILE" -C "$TARGET_DIR"
chown -R root:root "$TARGET_DIR/pki"
chmod 700 "$TARGET_DIR/pki"
chmod 600 "$TARGET_DIR/pki"/*.key
# Перезапустить CP с тем же QF_MASTER_KEY, что был при снятии бэкапа
systemctl restart qf-cp
1.3 Бэкап PostgreSQL
# Полный дамп
pg_dump -h localhost -U qf -d qf -F c -f "qf-db-$(date +%Y%m%d-%H%M%S).pgdump"
# Восстановление
pg_restore -h localhost -U qf -d qf --clean "qf-db-20260101-120000.pgdump"
2. Ротация секретов
2.1 Ротация QF_MASTER_KEY
QF_MASTER_KEY шифрует приватный ключ CA at-rest (файл ca.key, AES-256-GCM). Ротация требует перешифровки ключа CA.
Процедура:
-
Сгенерировать новый мастер-ключ:
NEW_KEY=$(openssl rand -hex 32)echo "New key: $NEW_KEY" # сохранить безопасно до продолжения -
Остановить qf-cp (чтобы не читать ключ CA в процессе ротации):
systemctl stop qf-cp# Или: kubectl -n qf scale deployment qf-cp --replicas=0 -
Перешифровать ключ CA новым мастер-ключом Go-тулой (собрать из исходников):
# Расшифровать старым ключом, перешифровать новымgo run ./cp/cmd/rekey/ \--pki-dir /etc/qf/pki \--old-key "$OLD_MASTER_KEY" \--new-key "$NEW_KEY"Замечание: тула
rekeyперечитываетca.key, расшифровывает старым ключом, перешифровывает новым, атомарно пишет обратно. -
Обновить секрет и перезапустить:
# Systemd: править /etc/qf/cp.env или environment-файлsed -i "s/QF_MASTER_KEY=.*/QF_MASTER_KEY=$NEW_KEY/" /etc/qf/cp.envsystemctl start qf-cp# Kubernetes: обновить Secretkubectl -n qf create secret generic qf-cp-secrets \--from-literal=QF_MASTER_KEY="$NEW_KEY" \--from-literal=QF_DB_DSN="..." \--dry-run=client -o yaml | kubectl apply -f -kubectl -n qf rollout restart deployment/qf-cp -
Проверить старт:
journalctl -u qf-cp -n 20# Искать: "PKI initialized" без ошибок
Эффект: ноль простоя агентов. Агенты работают по mTLS с уже выпущенными сертами; ключ CA нужен только для подписи новых сертов (энролмент). Энролмент кратко недоступен на время перезапуска (~секунды).
2.2 Ротация QF_JWT_SECRET
JWT-секрет подписывает access- и refresh-токены (HMAC-SHA256). Ротация инвалидирует все активные сессии — пользователям придётся войти заново.
NEW_JWT=$(openssl rand -hex 32)
# Обновить и перезапустить
sed -i "s/QF_JWT_SECRET=.*/QF_JWT_SECRET=$NEW_JWT/" /etc/qf/cp.env
systemctl restart qf-cp
# Kubernetes
kubectl -n qf patch secret qf-cp-secrets \
--patch="{\"stringData\":{\"QF_JWT_SECRET\":\"$NEW_JWT\"}}"
kubectl -n qf rollout restart deployment/qf-cp
Эффект: все браузерные сессии и API-клиенты на JWT Bearer-токенах инвалидируются немедленно. API-токены (opaque-токены в БД) не затронуты.
2.3 Ротация ключа подписи бандлов
Ключ подписи бандлов (Ed25519) подписывает бандлы политик, доставляемые агентам. Ротация требует, чтобы агенты получили новый публичный ключ до вывода старого.
Процедура:
-
Сгенерировать новую пару ключей:
openssl genpkey -algorithm ed25519 -out /etc/qf/pki/bundle-signing-new.keyopenssl pkey -in /etc/qf/pki/bundle-signing-new.key -pubout -out /etc/qf/pki/bundle-signing-new.pub -
Настроить CP на двойную подпись обоими ключами (dual-sign в CP не реализован — обходной путь: rolling-restart с новым ключом, агенты переэнроллятся за обновлённым
bundle-signing.pub). -
Заменить файлы ключей атомарно:
mv /etc/qf/pki/bundle-signing.key /etc/qf/pki/bundle-signing-old.keymv /etc/qf/pki/bundle-signing-new.key /etc/qf/pki/bundle-signing.keymv /etc/qf/pki/bundle-signing-new.pub /etc/qf/pki/bundle-signing.pub -
Перезапустить CP. Новые бандлы подпишутся новым ключом.
-
Переэнроллить агентов (они получают обновлённый
bundle-signing.pubпри энролменте):# На каждом хосте агента: удалить старый PKI-материал и перезапуститьrm /etc/qf/agent.crt /etc/qf/agent.key /etc/qf/ca.crt /etc/qf/bundle-signing.pub# Задать enrollment-токен в /etc/qf/agent.confsystemctl restart qf-agent
3. Управление сертификатами
3.1 Проверить срок сертификата
Сертификаты агентов выпускаются при энролменте. Срок действия по умолчанию: 90 дней.
# Проверить серт конкретного агента
openssl x509 -in /etc/qf/agent.crt -noout -dates -subject
# Список всех сертов и их сроков в БД
psql "$QF_DB_DSN" -c "
SELECT h.hostname, c.serial, c.not_after, c.status
FROM certificates c
JOIN hosts h ON h.id = c.host_id
WHERE c.status = 'active'
ORDER BY c.not_after ASC;"
3.2 Переэнролмент агента (серт истёк или отозван)
Когда серт агента истекает или отзывается, CP отвергает его mTLS-соединение. Агент уходит в degraded и должен переэнроллиться.
На хосте агента:
# 1. Создать новый enrollment-токен через CP API (или UI)
TOKEN=$(curl -s -X POST http://cp.example.com:8080/tokens \
-H "Authorization: Bearer $JWT" \
-H "X-Tenant-ID: $TENANT_ID" \
-H 'Content-Type: application/json' \
-d '{"label_template":{},"ttl_seconds":3600,"max_uses":1}' | jq -r .token)
# 2. На агенте: удалить старый серт-материал и задать токен
rm /etc/qf/agent.crt /etc/qf/agent.key
echo "QF_ENROLL_TOKEN=$TOKEN" >> /etc/qf/agent.conf
# 3. Перезапустить агента — он переэнроллится и получит новый серт
systemctl restart qf-agent
После переэнролмента убрать QF_ENROLL_TOKEN из agent.conf (агент стирает его сам при успехе, но это стоит проверить):
grep QF_ENROLL_TOKEN /etc/qf/agent.conf # должно быть пусто или отсутствовать
3.3 Отозвать сертификат
# Через API: пометить хост как revoked
curl -s -X PATCH http://cp.example.com:8080/hosts/$HOST_ID \
-H "Authorization: Bearer $JWT" \
-H "X-Tenant-ID: $TENANT_ID" \
-H 'Content-Type: application/json' \
-d '{"status":"revoked"}'
# В БД (напрямую)
psql "$QF_DB_DSN" -c "
UPDATE certificates SET status='revoked', revoked_at=NOW()
WHERE host_id='<host-uuid>' AND status='active';"
Блоклист отзыва перечитывается CP автоматически каждые 5 минут; перезапуск не нужен.
3.4 Ротация TLS-серта сервера CP
TLS-серт сервера CP (server.crt/server.key) защищает HTTPS- и gRPC-соединения.
# Остановить CP, сгенерировать новый серт, подписанный существующим CA
systemctl stop qf-cp
# Серт регенерируется автоматически через LoadOrInit, если отсутствует
mv /etc/qf/pki/server.crt /etc/qf/pki/server.crt.bak
mv /etc/qf/pki/server.key /etc/qf/pki/server.key.bak
systemctl start qf-cp
# CP регенерирует server.crt/server.key при старте, если файлы отсутствуют
4. Обслуживание PostgreSQL
4.1 Управление партициями
Партициями управляет встроенный PartitionManager (работает в CP каждые 24 часа). Ретенция по умолчанию:
| Таблица | Тип партиции | Ретенция |
|---|---|---|
log_events | дневная | 7 дней |
flow_events | дневная | 14 дней |
counter_snapshots | недельная | 30 дней |
system_events | без партиций (DELETE) | 30 дней |
PartitionManager создаёт на каждом цикле 7 дней будущих дневных партиций и 2 недели будущих недельных.
Вручную расширить партиции:
psql "$QF_DB_DSN" <<'SQL'
-- Создать партицию на следующий день вручную
DO $$
DECLARE
d date := CURRENT_DATE + 8; -- на день дальше обычного lookahead
BEGIN
EXECUTE format(
'CREATE TABLE IF NOT EXISTS log_events_%s PARTITION OF log_events
FOR VALUES FROM (''%s'') TO (''%s'')',
to_char(d, 'YYYY_MM_DD'), d, d + 1
);
END $$;
SQL
Изменить ретенцию: TTL-константы захардкожены в cp/internal/ingest/partitions.go. Чтобы изменить — правка и пересборка qf-cp:
const (
ttlLog = 14 * 24 * time.Hour // дефолт 7 дней
ttlFlow = 30 * 24 * time.Hour // дефолт 14 дней
ttlCounter = 90 * 24 * time.Hour // дефолт 30 дней
)
4.2 Мониторинг здоровья партиций
# Список всех партиций со счётчиком строк
psql "$QF_DB_DSN" -c "
SELECT
c.relname AS partition,
pg_size_pretty(pg_relation_size(c.oid)) AS size,
pg_stat_get_live_tuples(c.oid) AS live_rows
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
JOIN pg_class p ON p.oid = i.inhparent
WHERE p.relname IN ('log_events','flow_events','counter_snapshots')
ORDER BY c.relname;"
4.3 Тюнинг PostgreSQL
Для high-throughput-деплоев:
-- В postgresql.conf
shared_buffers = 256MB -- 25% RAM
work_mem = 16MB -- под сложные запросы
maintenance_work_mem = 128MB -- под VACUUM / сборку индексов
max_connections = 100 -- qf-cp использует pgxpool
wal_level = replica -- при streaming-репликации
checkpoint_completion_target = 0.9
random_page_cost = 1.1 -- под SSD
Гонять VACUUM ANALYZE еженедельно на write-heavy таблицах:
psql "$QF_DB_DSN" -c "VACUUM ANALYZE log_events, flow_events, system_events;"
5. Масштабирование qf-cp
qf-cp stateless (состояние в PostgreSQL + PKI-каталог). Несколько реплик могут работать одновременно.
5.1 Горизонтальное масштабирование (Kubernetes)
kubectl -n qf scale deployment qf-cp --replicas=3
Требования к multi-replica-схеме:
- Общий PKI-том: все реплики монтируют один
QF_PKI_DIR(ReadWriteMany PVC либо преднастроенный NFS/CephFS). Ключ CA read-only после первичной генерации. - Одинаковый
QF_MASTER_KEY: должен быть идентичен на всех репликах. - Одинаковый
QF_JWT_SECRET: должен быть идентичен, чтобы валидация JWT работала между репликами. - Балансировщик: распределяет gRPC (8443) и enrollment (8444) по репликам; агенты переподключаются автоматически при обрыве.
Кросс-под доставка идёт через PG LISTEN/NOTIFY (pgbus) — Redis не нужен. UI-invalidate (qf_invalidate), SSE live-tail (qf_events, coalesced + gated) и push бандлов (qf_dispatch) достигают агентов/UI-клиентов, подключённых к любой реплике. Наблюдаемые метрики — qf_pgbus_notify_total / qf_pgbus_notify_suppressed_total; путь notify сериализуется на инстанс-wide PG-локе, поэтому gate держит notify на 0, когда ни один другой под не наблюдает хост.
# override values.yaml для 3 реплик
replicaCount: 3
pki:
persistentVolume:
storageClass: nfs-client # должен поддерживать ReadWriteMany
5.2 Вертикальное масштабирование
qf-cp CPU-bound на компиляции бандлов (компиляция правил политик). Если латентность BundleApplied высока:
- Поднять CPU-лимиты CP (Helm:
resources.limits.cpu) - Увеличить размер пула соединений PostgreSQL: размер пула задаётся в коде (
newPoolвcp/cmd/qf-cp/main.go, три пула) — пересобрать, чтобы изменить
5.3 Мониторинг
qf-cp отдаёт Prometheus-метрики на GET /metrics (promhttp-хендлер; исключён из access-логов вместе с /healthz и /version).
Grafana-дашборд: monitoring/grafana/qf-cp.json — импорт через Dashboards → Import → Upload JSON.
Рекомендуемые пороги алертов:
| Метрика | Условие | Severity |
|---|---|---|
up (scrape) | == 0 дольше 1 мин | critical |
qf_grpc_active_streams | падение >50% за 5 мин | warning |
qf_bundle_push_errors_total rate | > 0.1/с дольше 2 мин | warning |
qf_bundle_push_duration_seconds p99 | > 5с | warning |
qf_ingester_events_dropped_total rate | > 0 дольше 1 мин | warning |
qf_http_requests_total{status=~"5.."} rate | > 0.5/с дольше 2 мин | critical |
qf_auth_failures_total rate | > 5/с дольше 5 мин | warning |
qf_db_pool_acquired_conns / (qf_db_pool_acquired_conns + qf_db_pool_idle_conns) | > 0.9 | warning |
qf_pgbus_notify_total{channel="qf_events"} rate | растёт без кросс-под SSE-зрителей | warning (subreg-gate нездоров) |
6. Debug-чеклист
Хост завис в enrolling
# 1. Логи агента
journalctl -u qf-agent -n 50 --no-pager
# 2. Сеть: порт энролмента доступен с агента
nc -zv <cp-host> 8444
# 3. Токен не истёк
curl -s http://cp.example.com:8080/tokens \
-H "Authorization: Bearer $JWT" -H "X-Tenant-ID: $TENANT_ID" | jq '.[] | {id, expires_at, uses_count, max_uses}'
# 4. Mismatch TLS SAN: QF_ENDPOINT на агенте должен резолвиться на тот же host, что QF_CP_HOST на CP
# Агент коннектится на cp.example.com:8444, но SAN серта — для localhost → handshake падает
grep QF_ENDPOINT /etc/qf/agent.conf
# Должен совпадать с hostname в env-переменной QF_CP_HOST на CP
# 5. Фаервол: проверить, что порт 8444 открыт на хосте CP
ss -tlnp | grep 8444
Бандл не приходит на агента
# 1. gRPC агента подключён?
journalctl -u qf-agent | grep -E "connected|bundle|stream"
# 2. Серт агента валиден
openssl x509 -in /etc/qf/agent.crt -noout -dates
# если истёк → переэнроллить (см. раздел 3.2)
# 3. Логи пушера CP
# На хосте или поде CP
journalctl -u qf-cp | grep -E "bundle|push|stream" | tail -20
# kubectl -n qf logs deployment/qf-cp | grep bundle
# 4. Статус хоста в БД
psql "$QF_DB_DSN" -c "
SELECT h.id, h.hostname, s.status
FROM hosts h JOIN host_statuses s ON s.host_id = h.id
WHERE h.hostname='<hostname>';"
# Если status='stale' или 'needs_rebootstrap' → переэнроллить
# 5. Политика существует и компилируется
curl -s http://cp.example.com:8080/policies \
-H "Authorization: Bearer $JWT" -H "X-Tenant-ID: $TENANT_ID" | jq '.[] | {id, name}'
События не появляются в UI
# 1. Статус агента active?
curl -s http://cp.example.com:8080/hosts \
-H "Authorization: Bearer $JWT" -H "X-Tenant-ID: $TENANT_ID" | jq '.[] | {hostname, status}'
# 2. Агент шлёт события?
journalctl -u qf-agent | grep -E "event|batch|ingest"
# 3. Логи ingest на CP
journalctl -u qf-cp | grep -E "ingest|log_event|insert" | tail -20
# 4. PostgreSQL: строки в сегодняшней партиции
psql "$QF_DB_DSN" -c "
SELECT COUNT(*) FROM log_events
WHERE created_at >= CURRENT_DATE AND host_id='<host-uuid>';"
# 5. Сегодняшняя партиция существует
psql "$QF_DB_DSN" -c "
SELECT relname FROM pg_class
WHERE relname = 'log_events_' || to_char(CURRENT_DATE, 'YYYY_MM_DD');"
# Если отсутствует: PartitionManager ещё не отработал; см. раздел 4.1 для ручного создания
TLS / mTLS-ошибки
| Ошибка | Вероятная причина | Фикс |
|---|---|---|
certificate signed by unknown authority | У агента неверный ca.crt | Переэнроллить агента |
certificate has expired | Серт агента истёк | Переэнроллить агента (раздел 3.2) |
connection refused :8443 | gRPC CP не слушает | Проверить старт CP, биндинг порта |
remote error: tls: certificate required | Агент не прислал серт | Проверить, что agent.crt/agent.key есть в QF_PKI_DIR |
no such host | DNS эндпоинта CP не резолвится | Проверить QF_ENDPOINT в agent.conf |
CP не стартует
journalctl -u qf-cp -n 30 --no-pager
Частые ошибки:
| Сообщение | Причина | Фикс |
|---|---|---|
QF_MASTER_KEY must be 32-byte hex | Ключ отсутствует или неверной длины | Задать корректный 64-символьный hex-ключ |
migrations: ... | БД недоступна или нет прав | Проверить QF_DB_DSN, что PostgreSQL поднят |
listen ...: address already in use | Конфликт порта | Проверить ss -tlnp, поправить QF_HTTP_ADDR / QF_GRPC_ADDR |
PKI initialized нет в логах | Инициализация CA упала | Проверить, что QF_PKI_DIR доступен на запись, есть место на диске |
Высокие память или CPU на CP
# Профилировать CP через pprof (если открыт)
curl http://cp.example.com:8080/debug/pprof/goroutine?debug=1
# Проверить число горутин (должно быть стабильно под нагрузкой)
# Проверить сатурацию пула соединений БД: искать "acquire timeout" в логах
Деплой из приватного GitHub-репозитория
Образ контейнера и Helm-чарт хостятся на ghcr.io. С приватным репо они требуют аутентификации.
Однократная настройка
1. Helm registry login (один раз на машину, креды кешируются в ~/.config/helm/registry/config.json):
helm registry login ghcr.io -u qzmi4meister -p <GITHUB_PAT>
Нужные scope PAT: repo + read:packages.
2. K8s imagePullSecret (уже создан в namespace qf; пересоздать при истечении):
kubectl create secret docker-registry ghcr-secret \
--docker-server=ghcr.io \
--docker-username=qzmi4meister \
--docker-password=<GITHUB_PAT> \
--docker-email=qzmi4meister2@gmail.com \
-n qf --dry-run=client -o yaml | kubectl apply -f -
Деплой агента с приватными релизами
Передать PAT через env перед запуском Ansible:
GITHUB_TOKEN=<GITHUB_PAT> make deploy-agent
Либо override на прогон:
make deploy-agent qf_github_token=<GITHUB_PAT>
7. Управление политиками
Политики применяются к хостам по label-селектору (matchLabels). Политика активна на хосте, когда все пары key-value селектора присутствуют в эффективных лейблах хоста (собственные лейблы + унаследованные лейблы групп).
7.1 Назначить политику хостам
Открыть Policies → [имя политики] → вкладка Settings и нажать «Assign to hosts».
Два режима назначения:
| Режим | Кнопка | Эффект |
|---|---|---|
| Один хост | Выбрать хост → Assign | Добавляет лейбл host-id: <id> на хост и в селектор политики |
| Host-группа | Выбрать группу → Assign | Копирует matchLabels группы в селектор политики |
«Preview impact» до сохранения показывает затронутые хосты и diff ruleset.
7.2 Снять политику с хостов или host-групп
Открыть Policies → [имя политики] → вкладка Settings и нажать «Remove from hosts».
Два режима снятия:
| Режим | Кнопка | Эффект |
|---|---|---|
| Один хост | Выбрать матченный хост → Remove | Убирает матчащие лейблы политики из собственных лейблов хоста |
| Host-группа | Выбрать матченную группу → Remove | Убирает ключи matchLabels группы из селектора политики |
Guard — хост матчится через группу: Если выбранный хост матчится потому, что состоит в host-группе, чьи лейблы в селекторе политики, прямое снятие с хоста заблокировано. UI покажет:
«Matched through group 'X' — remove host from the group or remove the group below.»
В этом случае либо:
- Снять группу с политики (убирает всех членов группы разом), либо
- Убрать хост из группы (через страницу Host Groups или Host Detail → вкладка Groups)
7.3 Посмотреть политики, применённые к хосту
Открыть Hosts → [hostname] → вкладка Policies.
- Показывает все политики, матчащие этот хост (выводится из эффективного ruleset).
- Поле поиска фильтрует по имени политики или лейблу селектора (
key=value). - Клик по имени политики → переход в редактор политики.
7.4 Проверить назначение через API
# Список политик, матчащих хост (через эффективный ruleset)
curl -s https://<cp-host>/hosts/<host-id>/ruleset \
-H "Authorization: Bearer $JWT" \
| jq '[.rules[].policy_name] | unique'
# Посмотреть селектор политики
curl -s https://<cp-host>/policies/<policy-id> \
-H "Authorization: Bearer $JWT" \
| jq '.selector'
# Список лейблов хоста
curl -s https://<cp-host>/hosts/<host-id> \
-H "Authorization: Bearer $JWT" \
| jq '{labels, effective_labels}'
8. Известные ограничения
Задокументированные ограничения датапаса/CP.
8.1 CT_RELATED не реализован
matcher.h возвращает 0 для состояния CT_RELATED (conntrack-FSM не отслеживает связанные флоу, например ICMP-ошибки для TCP/UDP-сессий). Правила с related в match-спеке отвергаются API (HTTP 422). Эффект: ICMP unreachable для активных сессий не пропускаются автоматически — при нужде заводятся явные ICMP allow-правила. Для PMTUD при default-deny явно разрешается ICMPv4 type 3 code 4 / ICMPv6 type 2 (packet-too-big).
8.2 IPv6: полный энфорс (opt-in), ext-заголовки обходятся
IPv6 энфорсится наравне с IPv4 — CIDR, ipset и stateful conntrack матчат v6 (Phase 25) — при QF_DROP_IPV6=false; по умолчанию весь v6 режется целиком (opt-in гейт). При включённом v6 правила proto/port/TCP-flags/CIDR/ipset/conntrack применяются к IPv6, включая пакеты с extension-заголовками: парсер обходит цепочку extension-заголовков (Hop-by-Hop, Routing, Destination Options, AH, Mobility; до IPV6_EXT_MAX_HOPS = 6 заголовков) до реального L4-заголовка. ⚠️ На dual-stack/Cilium-нодах перед включением разрешить ICMPv6 ND/RA, иначе нода потеряет связность.
⚠️ Радиус. Дефолтный drop отбрасывает и служебный v6 (ICMPv6 Neighbor Discovery, link-local
fe80::/10). Dual-stack хост с v6-mgmt потеряет v6-связность. Нужен v6 →QF_DROP_IPV6=false.
Не вычисляются (проходят без rule-eval): фрагменты (fragment-заголовки IPv4 и IPv6 устроены симметрично), цепочки длиннее 6 extension-заголовков (патологические; fail-open) и неподдерживаемые L4-протоколы (см. 8.4).
8.3 Эфемерный JWT-секрет при рестарте
Когда QF_JWT_SECRET не задан, CP генерирует случайный секрет при старте. Все активные сессии инвалидируются при рестарте. В multi-replica-деплоях каждый под использует свой секрет — кросс-под аутентификация падает. Всегда задавайте QF_JWT_SECRET стабильным значением ≥32 байт в проде. Helm-чарт отказывается деплоиться при replicaCount > 1 и пустом secrets.jwtSecret.
8.4 Неподдержанные L4 (SCTP/GRE/ESP) по умолчанию проходят без энфорсмента
Датапас парсит и энфорсит только TCP / UDP / ICMP / ICMPv6. Любой другой IP-протокол — SCTP (132), GRE (47), ESP (50, IPsec), OSPF (89) и прочие — парсер не распознаёт: пакет попадает в ветку PARSE_SKIP и пропускается безусловно, не доходя до вычисления default-action. То есть default-deny на такой трафик не распространяется, а сам трафик не виден в Verdicts/Flows/Counters (вся телеметрия — ниже rule-eval). Правило по этим протоколам завести нельзя (их нет в enum протоколов; полноценная поддержка SCTP — отдельная задача B-26).
Включение fail-closed — agent-флаг QF_DENY_UNKNOWN_PROTO=true (дефолт false). При true IP-пакет с непарсимым L4 подчиняется per-direction default-action: дропается при default = DENY, проходит при ALLOW. Как и QF_DROP_IPV6, флаг agent-local и принудительно проставляется при каждой записи конфига — CP-бандл его не переопределяет. Фрагменты, truncated-пакеты и non-IP (ARP/LLDP) остаются fail-open даже при включённом флаге.
⚠️ Раскатывать осторожно. Fail-closed может заблокировать легитимный трафик: ESP = IPsec (вплоть до management-канала/VPN), GRE = VPN-туннели, SCTP = телеком-сигнализация. Включать только после проверки — вне qf, этот трафик в телеметрии не виден, — что таких протоколов на хосте нет.
Что мониторить: непарсимый L4 не считается счётчиками, поэтому связность до и после включения флага проверяется вне qf (ss -a, flow-логи вышестоящей сети, health приложений). Отдельный aggregate drop-счётчик для unknown-proto запланирован, но пока не отдаётся.
8.5 Fail-open окно ~6с после рестарта агента
После systemctl restart qf-agent (рестарт/краш/деплой) датапас загружается с чистого состояния: правил нет, конфиг по умолчанию. CP-политики переприменяются только через ~6с (reconnect → welcome → apply). В этом окне активен лишь mgmt-guard (allow-egress к CP), остальное идёт по default-ALLOW → deny-правила ~6с не энфорсятся (fail-open). Правила не теряются — восстанавливаются на ~t+6s.
Побочно: CP помечает хост converged (по generation из reconnect-handshake) сразу, до того как датапас применил правила — поэтому converged/Active в первые ~6с после рестарта не гарантируют энфорс.
Влияние: короткое окно на каждый рестарт; для хостов, полагающихся на deny-правила, — секунды обхода при рестарте агента. Митигация: QF_FAIL_CLOSED=true (deny-all до первого bundle) для критичных хостов, ценой связности в окне. Отслеживается в бэклоге (P24-AGENT-02, низкий приоритет).
8.6 Асимметрия по IP-версии
На IPv4 брешь неподдержанного L4 (8.4) — основной радиус. На IPv6 такой трафик уже режется дефолтным QF_DROP_IPV6=true (8.2); брешь возникает только если оператор снял v6-drop.