Усачёв Денис Евгеньевич, РыбинскЛАБ
Введение
Гибридный контур, в котором PHP-сервисы на виртуальных машинах соседствуют с Python-сервисами в Kubernetes, почти всегда живёт с неявным доверием к «внутренней сети». Zero-Trust убирает это допущение: каждый вызов аутентифицируется, авторизуется и шифруется независимо от сегмента, из которого он пришёл. Ниже — рабочая схема на HashiCorp Consul и Istio, собранная так, чтобы её можно было сопоставить с требованиями законодательства РФ, а не только с вендорской диаграммой.
Что Zero-Trust меняет в PHP и Python
Для прикладного кода принцип сводится к четырём правилам.
- Нет доверенных подсетей. Факт нахождения в VLAN, VPC или namespace ничего не доказывает.
- Субъект доступа — идентичность сервиса (SPIFFE ID, учётная запись Kubernetes, регистрация в каталоге Consul), а не IP-адрес.
- По умолчанию связь запрещена. Разрешение явно и минимально: кто, куда, каким методом и по какому пути.
- Канал между сервисами взаимно аутентифицирован и зашифрован. Срок жизни сертификата короткий, закрытый ключ не лежит в репозитории.
PHP-FPM за nginx и FastAPI или Django за gunicorn исторически рассчитывают, что «снаружи уже отфильтровал файрвол». В гибриде этот файрвол не видит восток-западный трафик внутри кластера и не отличает скомпрометированный соседний сервис от легитимного. Контроль переносится в сервисную сеть, а приложение перестаёт принимать соединения в обход локального sidecar.
Нормативная рамка, которую архитектура обязана закрывать
Zero-Trust сам по себе не равен соответствию закону. Он даёт технические меры под уже определённый класс системы. Тип системы фиксируется до выбора криптографии и глубины журналирования.
- Если обрабатываются персональные данные — 152-ФЗ, уровни защищённости по постановлению Правительства РФ № 1119, состав мер по приказу ФСТЭК России № 21. Отдельно учитывается локализация: базы с персональными данными граждан РФ размещаются на территории России.
- Если система государственная — ориентир приказа ФСТЭК России № 17.
- Если речь о значимом объекте критической информационной инфраструктуры — 187-ФЗ и приказ ФСТЭК России № 239: сегментация, управление доступом, регистрация событий, реагирование.
Для архитектора из этого следует набор свойств, а не лозунг. Идентификация и аутентификация каждой стороны обмена. Разграничение доступа по ролям сервисов, а не по «внутреннему порту». Конфиденциальность и целостность канала. Регистрация отказов и успешных вызовов к данным с привязкой к идентичности. Управление изменениями политик. Отзыв доступа без пересборки приложения.
Отдельная граница — средства криптографической защиты информации. Взаимный TLS в Istio и Consul Connect закрывает инженерную задачу шифрования и взаимной аутентификации, но штатный стек Envoy не является автоматически сертифицированным СКЗИ. Там, где модель угроз и класс системы требуют сертифицированные средства, это закладывается в проект отдельно, а не галочкой «mTLS включён». Описание архитектуры не заменяет модель угроз и правовое заключение по применимым нормам.
Почему Consul и Istio вместе, а не два меша на одном пути
Istio силён там, где нагрузка уже в Kubernetes: sidecar Envoy, PeerAuthentication, AuthorizationPolicy, выпуск сертификатов, телеметрия. Consul силён там, где PHP живёт на виртуальной машине: каталог сервисов, проверки здоровья, intentions, локальный sidecar без переноса монолита в кластер.
Ошибка — включить оба полных контура политик на один и тот же путь. Два источника истины расходятся в инциденте: в итоге либо непредсказуемый отказ, либо дыра, которую каждый контур считает закрытой другим. Рабочее разделение такое.
- Трафик внутри Kubernetes авторизует Istio. Consul в кластере, если он нужен, остаётся каталогом и проверкой здоровья, но не вторым независимым L7-фильтром на том же переходе.
- Трафик с виртуальных машин и между контурами авторизуют intentions Consul и политики шлюза. В кластер такой трафик входит только через Ingress или Mesh Gateway с собственной идентичностью.
- На каждый маршрут в паспорте сервиса записан один policy decision point. Это требование эксплуатации и требование доказуемости для аудита.
Целевая схема
Контур делится на три зоны, между которыми нет общего доверия на уровне сети.
- Зона кластера. Python-сервисы и те PHP, что уже в контейнерах. У каждого Deployment свой ServiceAccount, у каждого пода — sidecar. Режим mTLS — STRICT.
- Зона виртуальных машин. PHP-FPM слушает только локальный интерфейс. Рядом агент Consul и sidecar Envoy. Наружу открыт порт sidecar, а не порт приложения.
- Зона стыка. Шлюз с коротким сертификатом и узким списком маршрутов. Шлюз — не доверенный посредник на все порты, а отдельный сервис со своим allow-списком. Его идентичность не раздаёт вызываемым backend права клиента: где нужен сквозной субъект, он передаётся явно и проверяется.
Внешние зависимости выпускаются через egress-шлюз. Прямой выход пода или виртуальной машины в интернет закрыт. Так сужается перечень внешних потоков, который нужен и для минимизации каналов, и для оценки обработки персональных данных.
Жёсткий mTLS и запрет по умолчанию в Istio
Режим STRICT отклоняет открытый текст. Политика вешается на рабочий namespace только после того, как у всех штатных нагрузок есть sidecar. Проверки с узла, если они действительно нужны, оформляются отдельным узким исключением, а не ослаблением всего namespace.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: prod
spec:
mtls:
mode: STRICT
Пустая AuthorizationPolicy без селектора переводит namespace в запрет. Действие по умолчанию — ALLOW, правил нет, совпадений нет, а само наличие ALLOW-политики отменяет прежний режим «всё разрешено». Дальше проходит только то, что описано отдельными разрешениями.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: prod
spec: {}
Разрешение для сервиса заказов к сервису биллинга привязано к ServiceAccount, методу и префиксу пути. Подстановка «любой под в namespace» сюда не годится.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: php-billing-from-orders
namespace: prod
spec:
selector:
matchLabels:
app: php-billing
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/prod/sa/python-orders
to:
- operation:
methods:
- GET
- POST
paths:
- /api/v1/invoice/
Учётная запись python-orders не переиспользуется воркерами, кронами и отладочными подами. Иначе политика формально верна, а субъект размыт: для разграничения доступа по мерам ФСТЭК это слабая конструкция.
Intentions Consul для PHP на виртуальных машинах
На стороне виртуальных машин включается тот же запрет по умолчанию. Сначала запрет всем, затем точечное разрешение. Запрет применяется после инвентаризации живых вызовов: иначе остановится легитимный трафик, который ещё не описан. Более конкретное намерение перекрывает шаблонный запрет.
Kind = "service-intentions"
Name = ""
Sources = [
{
Name = "*"
Action = "deny"
}
]
Kind = "service-intentions"
Name = "php-billing"
Sources = [
{
Name = "python-orders"
Action = "allow"
Permissions = [
{
Action = "allow"
HTTP {
PathPrefix = "/api/v1/invoice/"
Methods = ["GET", "POST"]
}
}
]
}
]
Имя сервиса в Consul совпадает с именем, под которым его вызывает шлюз. Расхождение алиасов — частая причина временных разрешений «для всех», которые забывают снять. Протокол в каталоге задаётся явно, http или grpc: ограничение пути не работает, если соединение зарегистрировано как tcp.
Как не вернуть доверие в код PHP и Python
Sidecar снимает с приложения выпуск сертификатов, но не снимает ответственность за субъект операции. Istio помещает цепочку клиентского сертификата в заголовок X-Forwarded-Client-Cert и перезаписывает его на sidecar. Приложение читает этот заголовок только у соединения, которое пришло с локального прокси. На ingress тот же заголовок, присланный клиентом, отбрасывается. Иначе вызывающая сторона подделает SPIFFE URI, и журнал соответствия будет фиксировать ложного субъекта.
PHP-FPM и gunicorn слушают локальный интерфейс. В приложении фиксируется, какой peer считается локальным sidecar именно в вашем режиме перехвата: REDIRECT и TPROXY ведут себя по-разному, это подтверждается на стенде. Идентичность для аудита извлекается из URI SPIFFE. Решение «пускать или нет» остаётся в mesh. Дублировать полный список доступа в каждом репозитории на PHP и Python вредно: два списка снова разъедутся.
В Python идентичность вызывающего пишется в структурированный журнал вместе с идентификатором запроса. В PHP — в существующий журнал приложения, без отдельного файла в каталоге временных данных. Состав полей согласуется с политикой обработки персональных данных: тело запроса с персональными данными в журнал доступа не попадает.
Журналы, без которых соответствие нельзя показать
Проверке и собственному расследованию нужны связанные события, а не разрозненные файлы доступа.
- Журнал sidecar: источник (principal), назначение, метод, путь, решение allow или deny, код ответа, время.
- Журнал приложения: бизнес-операция и корреляционный идентификатор, без дампа персональных данных.
- Журнал изменений: кто изменил AuthorizationPolicy или intention, из какого репозитория и каким запросом на изменение.
Политики хранятся в Git. Ручная правка в кластере закрывается технически, через контроль допуска и права, а не абзацем в инструкции. Отзыв доступа — удаление правила и истечение или перевыпуск короткого сертификата, а не заявка на следующую неделю. Для объектов КИИ и высоких уровней защищённости заранее проверяется, что сбор журналов выдерживает пик: потерянный отказ в момент инцидента обесценивает всю схему.
Порядок внедрения без остановки промышленного контура
Включение STRICT и запрета по умолчанию одним релизом на живом гибриде даёт простой, а не соответствие. Последовательность другая.
- Инвентаризация. Какие сервисы есть, какие держат персональные данные или значимые функции, кто кого вызывает фактически. Источник — телеметрия, журналы и намерения в режиме наблюдения, а не схема двухлетней давности.
- Идентичности. Отдельные ServiceAccount и отдельные регистрации Consul. Общие учётки вида «весь php» и «весь backend» разбираются до включения запретов.
- Короткий период PERMISSIVE, только чтобы измерить долю открытого текста. Это не целевое состояние и не мера защиты. Дата выхода из этого режима фиксируется до включения.
- Сначала списки разрешений при ещё открытом трафике, сверка на стенде, затем запрет по умолчанию на одном некритичном сервисе.
- STRICT на этот сервис, затем на следующий. Шлюз между виртуальными машинами и кластером вводится до того, как с машин заберут прямой маршрут в подсеть подов.
- Прямой маршрут удаляется последним. Пока он жив, Zero-Trust существует только в документе.
Сервис готов, когда нет трафика в обход sidecar, сертификаты выпускаются автоматически, список вызывающих явный, события отказа доходят до журналирования, а отзыв идентичности проверен учением.
Типовые ошибки гибрида
- Оставить PERMISSIVE после пилота. Открытый текст снова становится нормой.
- Разрешить весь namespace одной политикой, чтобы не тормозить релизы. Так восстанавливается доверенная сеть под новым именем.
- Один ServiceAccount на все Python-воркеры. Компрометация наименее важного воркера получает права самого важного.
- Считать шлюз доверенным и не ограничивать его намерения. Шлюз становится универсальным ключом.
- Забыть задания без sidecar: миграции, cron, очереди. Они продолжают ходить открытым текстом.
- Писать персональные данные в трассировки. Путь и заголовок тоже могут быть персональными данными, набор полей телеметрии согласуется заранее.
- Держать ключ удостоверяющего центра mesh без ротации и без ограничения доступа. Компрометация центра отменяет смысл коротких сертификатов.
Что получает эксплуатация
После внедрения PHP на виртуальных машинах и Python в кластере общаются только поименованными маршрутами. Новый сервис не видит чужие порты, пока для него не согласовано изменение политики. Инцидент локализуется отзывом идентичности, а не поиском всех узлов, которые были в той же подсети. Для проверки соответствия сохраняется цепочка: класс системы, модель угроз, политика в Git, журналы решений, протокол учения по отзыву.
Consul не заменяет Istio и наоборот. Consul даёт каталог, здоровье и намерения там, где нет Kubernetes. Istio даёт взаимную аутентификацию и авторизацию на уровне запроса там, где есть поды. Стык между ними — узкий шлюз с собственной идентичностью. Остальные пути закрыты.
Разработка и сопровождение
РыбинскЛАБ выполняет разработку гибридных PHP и Python сервисов и внедрение для них сети Zero-Trust: инвентаризацию потоков, политики HashiCorp Consul и Istio, перевод на взаимный TLS без остановки критичных сервисов, журналирование решений доступа и подготовку контура к требованиям 152-ФЗ и объектов КИИ. В работу входят архитектура, стенд, вывод в промышленную эксплуатацию и последующее сопровождение политик.