Статья VPC Security: сегментация сетей в облаке правильно

1777068805958.webp

Представим ситуацию: в проде поднимают временный сервис для интеграции с подрядчиком: отдельная виртуальная машина, пара правил в security group, маршрут через NAT, доступ “только на время теста”. Через месяц сервис уже никто не помнит, подрядчик давно закончил работу, а правило 0.0.0.0/0 на нестандартный порт всё ещё висит в VPC. Формально это одна строка в конфигурации. По факту - новая точка входа в сеть, где рядом лежат базы, внутренние API и сервисные аккаунты.

Облачные сети редко ломаются одним большим событием. Обычно всё начинается с мелочи: публичную подсеть используют как временную, приватную точку доступа добавили, но старый публичный доступ не убрали, peering настроили шире нужного, security group скопировали из соседнего проекта, таблицу маршрутов правили ночью и забыли вернуть обратно. Каждое такое изменение выглядит безобидно, пока не приходится восстанавливать путь трафика после инцидента.

VPC даёт удобную иллюзию порядка. Есть подсети, таблицы маршрутов, шлюзы, security groups, Network ACL, VPN, peering. На схеме всё делится на зоны, а стрелки идут только туда, куда нужно. В эксплуатации схема стареет быстрее документации. Новые рабочие нагрузки появляются без пересмотра сегментации, исключения копятся, сервисы начинают ходить друг к другу напрямую, а внутренний трафик постепенно становится серой зоной.

Сегментация в облаке нужна не для аккуратной архитектурной картинки. Её задача проще: при компрометации одной рабочей нагрузки атакующий не должен получить удобный маршрут ко всему остальному. В хорошем VPC путь до базы, внутреннего API, управляемого сервиса или соседнего контура должен упираться в маршруты, правила доступа и наблюдаемость трафика, а не в надежду, что “туда никто не полезет”.

В этой статье разберём VPC как рабочую сетевую конструкцию: из чего она состоит, где публичные и приватные подсети действительно расходятся по смыслу, когда hub-spoke помогает, чем security groups отличаются от Network ACL, зачем нужны приватные точки доступа, как проектировать межрегиональную связность и почему журналы потоков нужны не “для галочки”, а для понимания того, кто с кем реально разговаривает внутри облака.

VPC архитектура: основы​

Компоненты VPC: подсети, таблицы маршрутов, шлюзы​

VPC задаёт границу облачной сети: адресацию, маршруты, выходы наружу и правила доступа между ресурсами. Ошибка начинается там, где VPC воспринимают как “папку для серверов”. На деле это карта движения трафика. Если карта кривая, безопасность дальше живёт на честном слове.

Базовые элементы:
  • подсети - разделяют ресурсы по зонам и ролям;
  • таблицы маршрутов - решают, куда пойдёт трафик;
  • интернет-шлюз - даёт публичную связность;
  • NAT - выпускает приватные ресурсы наружу;
  • VPN, peering, transit-узлы - связывают сети между собой;
  • security groups и Network ACL - фильтруют доступ;
  • журналы потоков - показывают, кто с кем реально общается.
Главная ошибка - проектировать подсети раньше потоков. Сначала нужно понять, какие связи допустимы: пользовательский трафик, сервис-сервис, доступ админов, обращения к базам, выход к облачным API. Потом уже нарезать подсети и маршруты. Иначе через полгода в таблице маршрутов всплывает древний путь “для миграции”, который почему-то всё ещё ведёт в прод.

Таблица маршрутов - место, где тихо рождаются неприятные сюрпризы. Один широкий маршрут в peering или VPN может открыть доступ туда, куда по схеме никто ходить не должен. При расследовании это выглядит обидно: firewall вроде бы строгий, сервис вроде бы приватный, а маршрут всё это время лежал рядом и никого не стеснялся.

Шлюзы тоже стоит считать точками риска. Интернет-шлюз, NAT, VPN и peering расширяют поверхность сети. У каждого такого выхода должен быть понятный владелец, назначение и срок пересмотра. “Оставим, вдруг понадобится” - плохая сетевая архитектура, замаскированная под заботу о будущем.

Public и private подсети​

Публичная подсеть - место для входного слоя: балансировщиков, edge-сервисов, иногда bastion-хостов. Всё, что не должно принимать входящий трафик из интернета, туда лучше не класть. Даже временно. Особенно временно.

Приватная подсеть не становится безопасной автоматически. Ресурс без публичного IP всё ещё может быть доступен через VPN, peering, соседнюю VPC или скомпрометированный прикладной слой. База в приватной подсети защищена только тогда, когда к ней ведут минимальные маршруты и минимальные разрешения.

Рабочая базовая схема:
1.webp

Смысл простой:
  • балансировщик принимает внешний трафик;
  • приложение доступно только с входного слоя;
  • база доступна только приложению;
  • административный доступ идёт отдельным путём;
  • выход к облачным сервисам по возможности уходит через приватные точки доступа.
Отдельно стоит следить за правилом allow internal. Оно удобно, пока сеть маленькая. Потом в этот “internal” попадают сервисы разных команд, временные окружения, тестовые машины и забытые интеграции. В итоге внутренний диапазон превращается в клуб по интересам, где все всем рады. Атакующему такое гостеприимство тоже нравится.

Адресацию лучше планировать с запасом, но без гигантских подсетей “на всякий случай”. Маленькие диапазоны быстро заканчиваются. Слишком большие провоцируют ленивую сегментацию: всё лежит рядом, фильтрация держится на паре правил, а потом никто не понимает, почему база видит половину прикладного слоя.

Когда публичный слой используют как место “быстро поднять временный сервис”, проблема редко ограничивается одной открытой машиной. Дальше в игру входят внутренние API, служебные адреса, metadata-доступ и слишком доверчивая сеть. В статье: "Web: SSRF to RCE через Redis" как раз про то, почему приватные ресурсы нельзя считать безопасными только потому, что они не торчат наружу.

Паттерны сегментации​

Hub-spoke topology​

Hub-spoke используют, когда сетей становится слишком много для прямых связей “каждый с каждым”. Есть центральная VPC - hub. Через неё проходят общие выходы: VPN, межрегиональная связность, доступ к общим сервисам, иногда inspection через firewall или IDS/IPS. Прикладные VPC становятся spoke и ходят друг к другу только через центр.

Смысл схемы - убрать сетевую лапшу. Без hub-spoke быстро получается набор peering-связей, где никто уже не помнит, какая команда кому открывала маршрут и зачем. В audit это выглядит особенно весело: “да, вот эта тестовая VPC имеет путь в production, но исторически так сложилось”.

Типовая логика:
6.webp

Где hub-spoke полезен:
  • много аккаунтов, проектов или сред;
  • нужен единый выход в on-prem;
  • трафик между сегментами должен проходить через контрольную точку;
  • общие сервисы нельзя размазывать по всем VPC;
  • нужна понятная схема владения маршрутами.
Слабое место - сам hub. Если в нём слишком широкие маршруты и правила, он превращается в центральный коридор для lateral movement. Поэтому hub нельзя проектировать как “общую сетевую прихожую”. В нём должны быть отдельные таблицы маршрутов, фильтрация между spoke, журналы потоков и понятный список разрешённых направлений.

Shared VPC​

Shared VPC удобен, когда сетевая команда хочет держать базовую сеть централизованно, а прикладные команды - разворачивать ресурсы без ручной настройки каждой подсети. Идея простая: сеть принадлежит одной команде, ресурсы в ней используют другие команды или проекты.

Плюс очевиден - меньше хаоса. Адресация, маршруты, DNS, точки выхода, приватные подключения к сервисам и базовые правила живут в одном месте. Прикладная команда не изобретает свою “маленькую безопасную сеть” на каждый сервис.

Минус тоже быстрый. Shared VPC легко превращается в общую коммуналку. Если всем дать слишком похожие правила и положить разные контуры рядом, изоляция станет формальной. Особенно плохо, когда в одной сетевой зоне оказываются dev, stage и prod, потому что “так быстрее подключить”.

Что нужно закрепить сразу:
  • отдельные подсети под разные контуры;
  • разные правила доступа для команд и сред;
  • запрет на широкие внутренние разрешения;
  • понятный процесс запроса новых маршрутов;
  • регулярный пересмотр исключений.
Shared VPC хорошо работает, когда платформа зрелая. Если процессов нет, он просто централизует бардак.

Multi-tier architecture: web / app / db​

Классическая трёхслойная схема до сих пор жива, потому что хорошо режет лишние связи. Входной слой принимает трафик. Прикладной слой обрабатывает запрос. Слой данных хранит состояние и не должен светиться наружу.
Код:
Internet
  -> web: load balancer / ingress
  -> app: services
  -> db: databases / queues / internal state
Правила должны быть скучными:
  • web -> app только по нужным портам;
  • app -> db только от конкретных сервисов;
  • web -> db запрещён;
  • db -> internet по умолчанию закрыт;
  • админский доступ идёт отдельным контролируемым маршрутом.
Главная ошибка - разрешить всё внутри одного tier “для удобства”. Потом один сервис в app-слое получает доступ ко всем базам, очередям и внутренним API. Формально сегментация есть. По факту - один удачный вход открывает половину прикладного контура.

Для production лучше проектировать не только tiers, но и границы между сервисами внутри них. Например, платежный app-сервис не должен иметь такой же сетевой профиль, как сервис отчётов. Оба живут в app-слое, но цена компрометации у них разная. Тут уже начинается микросегментация: меньше общих правил, больше разрешений по конкретным группам и ролям.

Security Groups и Network ACL​

Stateful vs stateless правила​

Security group лучше воспринимать как фильтр рядом с ресурсом. Она отвечает на вопрос: кто может подключиться к этой виртуальной машине, балансировщику, базе или другому сетевому объекту. Правила обычно привязываются к самому ресурсу или интерфейсу, поэтому этот слой удобен для прикладной сегментации: web ходит в app, app ходит в db, админский доступ идёт только с jump host.

Главное свойство security group - состояние соединения. Если входящее соединение разрешено, обратный трафик по нему обычно проходит автоматически. Не нужно отдельно открывать ephemeral ports для ответа, как в старом добром сетевом мазохизме. Из-за этого security groups хорошо подходят для большинства повседневных правил между слоями приложения.

Network ACL работает грубее. Это фильтр на уровне подсети, который обрабатывает входящий и исходящий трафик отдельно. У него нет памяти о соединении: разрешили вход - отдельно подумайте про выход. Такой механизм полезен для широких сетевых ограничений: закрыть опасные диапазоны, отрезать подсеть от нежелательных направлений, сделать дополнительный барьер перед чувствительным слоем.

Проблема начинается, когда Network ACL используют как основной способ прикладной сегментации. Через пару месяцев в нём появляется смесь из CIDR, портов, временных исключений, правил для диагностики и старых дырок “на миграцию”. Потом никто не понимает, почему приложение работает только при открытом диапазоне портов размером с пол-облака.
2.webp


Private Endpoints​

Доступ к сервисам без публичного IP​

Приватная точка доступа нужна там, где рабочая нагрузка должна обращаться к облачному сервису, но вы не хотите выпускать этот трафик в публичный интернет. Типичный пример - приложение из приватной подсети ходит в объектное хранилище, реестр образов, секреты, базу или управляющий API провайдера. Без private endpoint путь часто идёт через NAT и публичный адрес сервиса. Работает, но для защищённой сети это лишний крюк.

Смысл private endpoint простой: сервис провайдера получает приватный адрес или приватный маршрут внутри вашей сетевой среды. Для приложения меняется немногое - оно всё так же обращается к нужному имени или API. Для сетевой модели меняется многое: чувствительный трафик не выходит наружу, NAT становится менее загруженным, а правила доступа можно писать точнее.

3.webp


Главная ошибка - добавить private endpoint и оставить старый публичный путь. На схеме всё красиво: “трафик теперь приватный”. В реальности сервис продолжает принимать обращения и через публичный адрес, потому что никто не закрыл старый маршрут, DNS или правила доступа. Получается не изоляция, а второй способ подключиться.

Что нужно проверять после внедрения:
  • резолвинг имени действительно ведёт на приватный адрес;
  • у рабочей нагрузки нет лишнего выхода через NAT;
  • публичный доступ к сервису закрыт или ограничен;
  • правила доступа разрешают только нужные источники;
  • журналы показывают обращения через приватный путь.
Private endpoint особенно полезен для сервисов с чувствительными данными: объектное хранилище, базы, хранилища секретов, очереди, реестры образов. Если приложение ходит туда через публичный интернет только потому, что “так было быстрее настроить”, это стоит исправить до первого аудита. Аудитор обычно приходит не с кофе, а с вопросами.

Конфигурация в Yandex Cloud / AWS​

В AWS эта модель чаще всего реализуется через VPC Endpoints. Есть два основных типа: gateway endpoints и interface endpoints. Gateway endpoints применяют для отдельных сервисов вроде S3 и DynamoDB. Interface endpoints создают сетевой интерфейс с приватным IP в вашей VPC и работают через AWS PrivateLink. Для архитектуры важнее не название типа, а место в маршруте: сервис должен быть доступен из приватной сети без выхода через интернет-шлюз или NAT.

После настройки нужно проверить таблицы маршрутов, DNS и policies endpoint. Частая ошибка - создать endpoint, но не ограничить, какие ресурсы и действия через него разрешены. Тогда вы убрали публичный путь, но оставили слишком широкий доступ внутри приватного.

В Yandex Cloud похожую роль играет Private Endpoint для доступа к сервисам платформы из облачной сети по приватным адресам. Типовой сценарий - доступ из VPC к Object Storage или другим поддерживаемым сервисам без публичного IP. Здесь тоже важно не ограничиться фактом создания endpoint: нужно проверить DNS, маршрут, права сервисного аккаунта и настройки самого сервиса.

Отдельный момент - журналы. После внедрения private endpoint стоит убедиться, что обращения действительно идут по приватному пути. В противном случае можно месяцами жить с “улучшенной архитектурой”, где половина клиентов всё ещё ходит старым публичным маршрутом.

Private endpoints не заменяют security groups и IAM. Они решают сетевой путь. Доступ к самому сервису всё равно должен ограничиваться политиками, ролями и правилами на стороне ресурса. Иначе получится приватная дорога к слишком открытому складу. Формально красиво, практически так себе.

Дальше можно переходить к межрегиональной связности: VPN, peering, interconnect и главная боль таких схем - лишний транзит между сегментами, который появляется “временно”, а потом живёт дольше некоторых микросервисов.

Межрегиональная связность​

Межрегиональная сеть почти всегда появляется с хорошими намерениями. Надо связать два региона на время миграции, дать приложению доступ к общему сервису, подключить резервный контур, протянуть мониторинг или сделать маршрут в on-prem. Через пару месяцев временная связность уже лежит в таблицах маршрутов как часть “исторически сложившейся архитектуры”. Никто её не трогает, потому что страшно сломать прод.

Самая опасная ошибка здесь - широкий транзит между сетями. Он редко выглядит страшно в момент добавления. Примерно так и появляются маршруты, которые потом открывают половину облака:
Код:
# route table: app-prod-eu
10.0.0.0/8      -> transit-hub
172.16.0.0/12   -> vpn-to-dc
0.0.0.0/0       -> nat-gateway
Выглядит удобно: всё внутреннее доступно, ЦОД виден, интернет есть. Для эксплуатации - быстро. Для безопасности - подарок. Если один сервис в app-prod-eu скомпрометирован, у него появляется широкий маршрут в соседние контуры. Дальше всё зависит от правил доступа, а они в таких схемах обычно тоже “временно широкие”.

Нормальная маршрутизация немного иначе:
Код:
# route table: app-prod-eu
10.20.10.0/24   -> transit-hub   # shared logging
10.20.20.0/24   -> transit-hub   # artifact registry
10.30.5.0/24    -> vpn-to-dc     # конкретный внутренний API
0.0.0.0/0       -> nat-gateway
Здесь сразу видно, зачем открыт каждый путь. Не весь корпоративный диапазон, не вся соседняя VPC, не “всё внутреннее”, а конкретные подсети и сервисы. При расследовании это экономит часы: маршрут либо есть и у него понятная причина, либо его быть не должно.

Peering хорош, пока связей мало. Две VPC, один поток, понятная зона ответственности. Потом появляется третья VPC, четвёртая, отдельная сеть под аналитику, отдельная под общие сервисы, отдельная под подрядчика. Если всё связать напрямую, получится сетевой ком, который никто не хочет разворачивать.

4.webp


Transit-узел нужен, когда прямые связи начинают мешать управлению. Он даёт центральную точку для маршрутов, firewall, IDS/IPS и журналов потоков. Но сам по себе transit ничего не защищает. Если через него разрешить все сети во все сети, получится просто дорогая развязка для lateral movement.

Лучше сразу разделять таблицы маршрутов по зонам. Например, production видит только shared-services и нужный on-prem API. Stage не видит production. Dev не видит базы и внутренние управляющие сервисы. Общие сервисы не получают обратный маршрут во все прикладные сети без отдельной причины.
Код:
# transit route table: prod
10.20.10.0/24   -> shared-logging
10.20.20.0/24   -> shared-registry
10.30.5.0/24    -> on-prem-api

# transit route table: stage
10.20.10.0/24   -> shared-logging
10.20.30.0/24   -> test-registry

# transit route table: dev
10.20.30.0/24   -> test-registry
В такой схеме сразу видно, где контуры расходятся. Production не становится соседом dev просто потому, что оба подключены к одному transit-узлу. Это мелочь на диаграмме и большая разница при инциденте.

Отдельная боль - миграции. На время переезда часто открывают широкий маршрут между старым и новым регионом:
Код:
# временно, на время миграции
10.40.0.0/16 -> old-region
10.50.0.0/16 -> new-region
Проблема не в самом временном маршруте. Проблема в слове “временно”, которое в инфраструктуре часто означает “до следующего аудита, если повезёт”. У таких маршрутов должен быть срок жизни, владелец и тикет на удаление. Иначе через полгода никто уже не вспомнит, почему новый регион видит старую базу напрямую.

В межрегиональной связности хорошая архитектура узнаётся по простому признаку: любой маршрут можно объяснить одной строкой. Если для объяснения нужен созвон с тремя командами и поиск старого чата, маршрут уже подозрительный. Сеть не обязана быть примитивной, но она должна быть читаемой.

Дальше нужен мониторинг. Без журналов потоков вся эта сегментация остаётся красивой гипотезой: маршруты вроде правильные, правила вроде строгие, а реальный трафик живёт своей жизнью.

Мониторинг сетевого трафика​

Сегментация без журналов трафика быстро превращается в гадание. На схеме app ходит только в db, админский доступ идёт через bastion, dev не видит prod, а приватные сервисы не выходят наружу. Потом включаешь журналы потоков - и находишь старый сервис, который ночью стучится в соседнюю VPC, базу, которая ходит в интернет за обновлениями, и тестовую машину, которая почему-то видит production API.

Журналы потоков нужны не для архива “на всякий случай”. Они отвечают на приземлённые вопросы:
Код:
кто и куда
по какому порту
из какой подсети
через какой интерфейс
принято или отброшено
когда началось и сколько длилось
Если эти поля не попадают в SIEM или хотя бы в нормальное хранилище для расследований, сетевой инцидент потом восстанавливается по косвенным следам: логам приложения, NAT, firewall, балансировщикам и памяти людей. Память людей - худшая база данных, особенно после пятничного релиза.

Минимальный набор проверок после включения flow logs:
Код:
# неожиданные исходящие соединения
private-subnet -> internet

# прямой доступ к базам
web-subnet -> db-subnet

# обход входного слоя
external-vpn -> app-subnet

# лишняя связность между средами
dev-vpc -> prod-vpc

# шум сканирования
many ports -> one host
Это не готовые правила для конкретного SIEM, а классы сигналов. Их удобно сначала смотреть как отчёт по фактическим потокам, а уже потом превращать в детекты. Иначе легко написать красивое правило, которое ни разу не сработает, потому что реальная сеть живёт совсем не так, как нарисована в документации.

Для production полезно вести две карты: проектную и фактическую. Проектная отвечает, как сеть должна работать. Фактическая строится из журналов потоков. Разница между ними - самый интересный материал для разбора. Если сервис общается с тем, с кем не должен, это либо ошибка сегментации, либо забытая зависимость, либо след активности, которую нужно расследовать.

IDS/IPS лучше ставить не “везде, где можно”, а в местах, где трафик реально пересекает границы доверия: выход в интернет, связь с on-prem, transit-узел, межрегиональные маршруты, shared services, входной слой. На внутренних участках с большим объёмом east-west трафика без фильтрации по контексту можно быстро утонуть в шуме. Датчик должен видеть важный переход, а не просто всё подряд.

5.webp


Отдельно стоит проверять не только разрешённый, но и отброшенный трафик. REJECT часто показывает, где приложения пытаются ходить по старой схеме, где правила стали слишком широкими или где кто-то сканирует соседние диапазоны. Иногда именно отказы первыми показывают, что сегментация начала работать. Иногда - что она ломает то, о чём никто не знал.

Последний слой - связь мониторинга с изменениями. Если после изменения таблицы маршрутов внезапно появляется новый поток app -> internet, это должно быть видно не через месяц на аудите, а в ближайшем окне наблюдения. Для этого журналы потоков надо связывать с инфраструктурными изменениями: кто правил security group, кто добавил маршрут, какой pipeline применил Terraform, какой тикет объясняет изменение.

Сеть в облаке нельзя считать защищённой только потому, что правила выглядят строго. Защищённая сеть наблюдаема. Видно, какие маршруты используются, какие соединения режутся, какие сервисы обходят ожидаемый путь и где фактический трафик расходится с архитектурой. Без этого VPC остаётся красивой схемой с неизвестным поведением внутри.

Где сеть заканчивается, там начинается эксплуатация​

Хорошо собранная VPC не делает облако безопасным сама по себе. Она просто убирает самый удобный сценарий для атакующего: зайти в одну точку и спокойно идти дальше по внутренним маршрутам. Публичный слой принимает только то, что должен принять. Приватные подсети не превращаются в общий коридор. Базы не торчат рядом с приложениями “потому что так проще”. Временные маршруты не живут дольше самих проектов.

Но VPC не застывает после проектирования. Сегодня сеть выглядит аккуратно, завтра команда добавляет интеграцию, послезавтра появляется новый регион, потом кто-то открывает доступ на миграцию, а через месяц этот маршрут уже никто не помнит. Сегментация держится не на первой схеме в draw.io, а на привычке проверять изменения, смотреть журналы потоков и не оставлять исключения без срока жизни.

Облако не прощает сетевой романтики. Всё, что можно связать, рано или поздно кто-нибудь свяжет. Всё, что можно открыть “на время”, однажды забудут закрыть. Поэтому хорошая VPC-архитектура - это не финальная картинка, а рабочий процесс: маршруты, правила, приватные точки доступа и мониторинг постоянно сверяются с тем, как система реально живёт. Сегодня этого хватает. Что придётся закрывать завтра - покажут новые сервисы, новые интеграции и первый неожиданный поток в журналах.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab