Кто-то спрашивает, охватывает ли GuardDuty все учетные записи. Вопрос касается пробелов в покрытии, а не того, включена ли служба. Для плоского списка ресурсов это по-настоящему раздражающий вопрос. Вы экспортируете учетные записи, экспортируете детекторы, выстраиваете их в электронной таблице, и ответ устаревает в тот же момент, когда кто-то запускает terraform apply.
Это раздражает, потому что это не вопрос инвентаризации. Он касается отсутствующей связи между двумя объектами, которые у вас уже есть, и список — неподходящий формат для этого. Связь здесь — это в такой же степени концептуальное утверждение, как и сохраненный факт. Она фиксирует, что должна делать инфраструктура, откуда возникло это намерение и как оно обеспечивается. Область действия детектора подтверждает покрытие, а правила групп безопасности и маршруты подтверждают доступность; и то, и другое обычно выражается в виде правил или инфраструктуры как кода. Моделирование ребра означает моделирование этого намерения рядом с наблюдением.
Большинство вопросов, которые стоит задавать об инфраструктуре, именно такие. Какой объект, выходящий в интернет, имеет доступ к базе данных? Какие учетные записи действительно могут взаимодействовать друг с другом, независимо от того, что указано на схеме сети?
Граница аудита — это набор учетных записей, охватываемых областью соответствия требованиям, например SOC 2, которая обычно уже, чем набор учетных записей, принадлежащих компании. Учетная запись внутри этой границы находится в области действия, а вне ее — нет. Инфраструктура здесь включает приобретенную учетную запись, которая так и не была включена в границы аудита, и именно там запросы на доступность и сегментацию находят свои результаты.
В этой статье создается граф, который отвечает на эти вопросы, а затем передается агенту. Агент, настроенный на него, каждую ночь заново делает те же выводы, поэтому в последнем разделе мы добавим ему память.
Сбор инвентаризации
API, с которого вы начинаете, имеет значение. Resource Explorer 2 возвращает ARN, тип, регион, учетную запись и теги. Это список узлов без свойств, поэтому он не дает вам никаких ребер. Каждое ребро ниже берется из данных конфигурации, включая доступность из правил групп безопасности и маршрутов, привязку ролей из определений задач и шифрование из настроек бакетов.
AWS Config предоставляет именно это, поэтому сделайте его своим основным источником. Каждый элемент конфигурации содержит блок конфигурации и связей, а select-aggregate-resource-config работает по всей организации через агрегатор.
Resource Explorer — это дешевая независимая перепись, с которой вы сверяете Config. Она бесплатна, охватывает учетные записи и регионы за один вызов и отвечает на вопрос: «что существует, чего не записывает Config?». Для настройки требуется индекс для каждой учетной записи, индекс агрегатора и доверенный доступ Organizations.
Cloud Control — это то, к чему обращаются в первую очередь и в последнюю. Его унифицированная схема CloudFormation-registry означает, что один парсер обрабатывает каждый тип, но list-resources возвращает идентификаторы, а не свойства, поэтому для каждого ресурса требуется дополнительный вызов get-resource к самому ограниченному по частоте из трех API. Оставьте его для типов, которые Config не записывает. Не все из 2082 публичных типов в us-east-1 можно перечислить, а у AWS::ECS::TaskSet вообще нет обработчика списка:
Плоский граф ресурсов не может ответить на эти вопросы
Дамп Config очень естественно отображается на один тип узла и один тип связи. Это ошибка моделирования, которая усложняет все последующие действия:
Он загружается быстро и даже выглядит как граф, но он почти бесполезен, потому что RELATED_TO между подсетью и экземпляром означает не то же самое, что RELATED_TO между политикой и бакетом, поэтому каждый запрос должен восстанавливать это различие из свойств.
Типизированные узлы сохраняют этот смысл в графе. Двенадцать собранных типов узлов, плюс один производный, в формате, который использует :
entity_types: - label: AWSAccount pole_type: ORGANIZATION properties: - {name: accountId, type: string, required: true, unique: true} - {name: alias, type: string} - {name: complianceScopes, type: list}
- label: Workload pole_type: OBJECT subtype: IT_ASSET properties: - {name: arn, type: string, required: true, unique: true} - {name: type, type: string, enum: [ec2_instance, ecs_service, lambda, eks_pod, alb, nlb, api_gateway]} - {name: internetFacing, type: boolean}
- label: DataStore pole_type: OBJECT subtype: DATA_ASSET properties: - {name: arn, type: string, required: true, unique: true} - {name: dataClass, type: string, enum: [customer-pii, secret, operational, public, none]} - {name: encrypted, type: boolean}
# Производный от расширенных операторов политики, не собранный из AWS. - label: Permission pole_type: OBJECT properties: - {name: action, type: string, required: true, unique: true}
Остальные следуют тому же шаблону, включая Region, VPC, Subnet, Principal, Policy, Control, Finding, ResourceState и EvidenceSnapshot. Каждая связь имеет определенный тип. Этот список представлен в виде обычного текста, поскольку чередование меток, таких как (:Workload|:DataStore), недопустимо в шаблоне узла:
Некоторые части этой схемы выполняют специфическую работу. complianceScopes содержит список, поскольку одна учетная запись может одновременно находиться в области действия SOC 2, внутренней политики и контракта с клиентом, с разными владельцами и циклами проверки, и каждая из этих областей должна оставаться доступной для адресации сама по себе.
EvidenceSnapshot указывает на узлы ResourceState, каждый из которых неизменяем после записи. Ночной запуск изменяет ресурсы на месте, поэтому привязка наблюдения к фиксированному состоянию — это то, что позволяет снимку прошлого месяца описывать прошлый месяц.
Control -[:COVERS]-> делает отсутствие проверяемым. Полезный ответ на вопрос о покрытии — это то, какие рабочие нагрузки не имеют ребра GuardDuty, а модель RELATED_TO не может этого создать, не обучив каждый запрос тому, как выглядит мониторинг.
Загрузка
Общая метка AWSResource содержит ограничение ARN, поэтому уникальность объявляется один раз для каждого типа. Динамические метки затем позволяют одному оператору обрабатывать загрузку.
Затем узлы загружаются пакетами, по одному оператору для каждого типа. Метка поступает как данные в каждой строке, поэтому один оператор охватывает все двенадцать типов:
SET n:$(row.label) принимает метку как значение, поэтому в текст запроса ничего не вставляется. Динамические метки сканируют и фильтруют там, где статическая метка использовала бы индекс, что удерживает это в загрузчике и вне пути горячего чтения.
Ребра загружаются таким же образом, как обычный пакет. Оба конечных узла сопоставляются по ARN, поэтому узлы должны быть на месте до запуска этого процесса:
Инфраструктуре такого размера больше ничего не нужно. При объемах, где запись ребер начинает конфликтовать, тот же блок помещается внутрь CALL { … } IN CONCURRENT TRANSACTIONS с ON ERROR RETRY, потому что каждая запись ребра затрагивает два узла, и параллельные пакеты сталкиваются.
Запросы, которые открывает модель
Все, что ниже, выполняется на синтетической инфраструктуре. Halyard — это B2B-платформа для биллинга подписок с публичным шлюзом и API подписок в halyard-edge, экспортом биллинга и отчетов в halyard-billing и стеком резиденции в ЕС в halyard-eu. Девять месяцев назад она приобрела Pinemark Analytics, чей стек до сих пор работает в pinemark-legacy. Эта учетная запись никогда не проходила аудит, а ее VPC объединена с биллингом для незавершенной миграции.
Стек EU стоит отдельно, и нет ни одного ребра, соединяющего его с остальными. Остальные три учетные записи образуют единый кластер, поэтому они менее изолированы, чем предполагает граница учетной записи, и проверка сегментации превращает это в находку.
Пробелы в покрытии
Вопрос о GuardDuty из вступления, представленный в виде запроса. Он спрашивает об отсутствии связи, которую ребро COVERS делает непосредственно доступной для запроса.
account, type, uncovered"halyard-eu", "ecs_service", "arn:aws:ecs:eu-west-1:444444444444:service/prod/eu-invoice-worker""halyard-eu", "alb", "arn:aws:elasticloadbalancing:eu-west-1:444444444444:loadbalancer/app/eu-gateway/123456""halyard-billing", "lambda", "arn:aws:lambda:us-east-1:222222222222:function:statement-export"
Ничто не отслеживает эти три рабочие нагрузки. Lambda была добавлена после того, как покрытие проверялось в последний раз. Обе рабочие нагрузки в EU отсутствуют, потому что GuardDuty является региональным сервисом, а этот детектор находится в us-east-1, поэтому он ничего не покрывает в eu-west-1. Поучетный просмотр того, включен ли GuardDuty, не показывает этот пробел, так как регион должен быть частью проверки.
Ограниченная достижимость
Какие ресурсы, доступные из интернета, могут получить доступ к данным клиентов и насколько напрямую. Это проходит по ребрам REACHES до четырех переходов.
entry, hops, via, reached"instance/i-123456", 1, "service/prod/invoice-worker", "billing-primary""loadbalancer/app/eu-gateway/123456", 1, "service/prod/eu-invoice-worker","eu-billing-primary""service/migration/migration-worker", 1, "statement-export", "halyard-statements""instance/i-123456", 2, "service/prod/invoice-worker", "billing-primary""instance/i-123456", 2, "statement-export", "halyard-statements""loadbalancer/app/gateway/123456", 2, "service/prod/invoice-worker", "billing-primary"
Первые две строки ожидаемы. Обе точки входа — это собственные шлюзы Halyard, достигающие биллинговых данных внутри границы аудита. Третья строка отличается, потому что ее точка входа находится в pinemark-legacy, приобретенной учетной записи за пределами границы, и она находится в одном переходе от выписок клиентов.
ACYCLIC останавливает путь при повторном посещении узла. Графы достижимости полны взаимных правил групп безопасности, где две рабочие нагрузки разрешают доступ друг другу, и без этого ключевого слова такие пары создают зацикленные пути, которые заполняют результат бесполезными данными.
К каждому результату выше относится одно предостережение. Эти ребра REACHES происходят из правил групп безопасности, маршрутов и конфигурации прослушивателей, поэтому они описывают сетевую экспозицию. Авторизация — это отдельный уровень, и доказательство того, что путь является эксплуатируемым, означает оценку политики IAM с приоритетом Deny и блоками условий, чего данная схема не пытается делать и что само по себе является значительным объемом работы. Рассматривайте каждую строку как кандидата для проверки. Сегментированы ли учетные записи вообще — это отдельный вопрос, и алгоритм отвечает на него лучше, чем обход.
Алгоритм графа: сегментация
Запрос достижимости отвечает на вопросы о конкретных путях. Сегментация — это вопрос о всей инфраструктуре. Он спрашивает, какие рабочие нагрузки находятся в одной связанной группе и выходит ли какая-либо из этих групп за пределы границы аудита. Кластеризация отвечает на это за один проход по всему графу. Проекции здесь выполняются на Aura Graph Analytics, бессерверной форме GDS, поэтому они содержат ключ памяти.
Слабосвязанные компоненты (Weakly Connected Components) — это дешевый способ получить такие группы. Он рассматривает каждое ребро как ненаправленное и помещает две рабочие нагрузки в один компонент, если их соединяет любой путь. Игнорирование направления приводит к ошибке в сторону преувеличения связи, поэтому результат никогда не сообщает о двух вещах как о разделенных, если это не так. Спроектируйте ребра достижимости, а затем ищите компонент, который смешивает вещи, которые не должны иметь возможности общаться.
CALL gds.wcc.stream('netseg')YIELD nodeId, componentIdWITH componentId, gds.util.asNode(nodeId) AS wMATCH (w)-[:RUNS_IN]->(:Subnet)-[:IN_VPC]->(:VPC)-[:IN_ACCOUNT]->(a:AWSAccount)WITH componentId, collect(DISTINCT a.alias) AS accounts, collect(DISTINCT CASE WHEN 'soc2' IN a.complianceScopes THEN 'in-scope' ELSE 'out-of-scope' END) AS scopesWHERE size(scopes) > 1RETURN componentId, accounts, scopes;
componentId, accounts, scopes0, ["halyard-edge","halyard-billing","pinemark-legacy"], ["in-scope","out-of-scope"]
Учетная запись за пределами области аудита разделяет компонент достижимости с данными клиентов. Причина — pcx-123456, пиринг, открытый для миграции, плюс правило группы безопасности, позволяющее миграционному воркеру общаться с функцией экспорта.
Многие инфраструктуры должны быть плоскими, поэтому единый компонент является сбоем только в контексте. Здесь это сбой, потому что pinemark-legacy находится за пределами границы аудита. Рабочие нагрузки EU образуют свой собственный отдельный компонент, который фильтр size(scopes) > 1 отбрасывает, потому что каждая учетная запись в нем имеет одинаковую область действия.
Для мониторинга сравнивайте членство в компонентах между снимками. Само по себе количество неоднозначно, потому что уменьшающееся число может означать, что две группы соединились, или просто то, что компонент был удален. Рассматривайте любое изменение как повод для расследования.
Семантический поиск по графу
Тегирование никогда не бывает таким последовательным, как хотелось бы, поэтому неклассифицированный бакет невидим для запроса, который фильтрует по dataClass. Векторный поиск вместо этого дает ранжированный шорт-лист кандидатов.
Индекс хранит одно вложение (embedding) на ресурс. Любое свойство, по которому вы хотите фильтровать во время поиска, должно быть указано в списке WITH при создании индекса, поэтому n.type появляется там ниже.
Установите vector.dimensions в соответствии с тем, что выдает ваша модель вложений. Вложите что-то описательное для каждого ресурса, а затем выполните поиск с вложением вопроса, здесь «исторические экспортные счета клиентов»:
bucket, dataClass, score"halyard-invoice-archive-2023", "none", 0.9472"halyard-statements", "customer-pii", 0.875"pinemark-events-raw", "none", 0.6021"halyard-build-artifacts", "none", 0.5"halyard-app-logs", "operational", 0.5"halyard-usage-staging", "none", 0.5
Прочитайте первые две строки вместе. halyard-statements уже классифицирован как данные клиентов, поэтому то, что он оказался на втором месте, является контролем, который говорит вам, что поиск работает. Бакет над ним вообще никак не классифицирован, и он содержит историю счетов.
Это отвечает на вопрос, который поднял запрос достижимости, а именно: откуда берется dataClass. AWS не сообщает об этом, и ничто не выводит это автоматически. Коллектор объединяет два независимых источника. Один — это тег классификации, который устанавливают ваши сотрудники. Другой — Amazon Macie, сервис AWS, который сканирует объекты S3 на наличие конфиденциальных данных, таких как номера карт и учетные данные. Macie не записывает свои собственные теги. Он выдает находки, в которых classificationDetails.result называет категории, которые он обнаружил, и коллектор считывает эти находки.
Два источника могут противоречить друг другу. Бакет с тегом internal, который Macie помечает как содержащий номера карт, сам по себе является находкой, поэтому сохраняйте информацию о том, какой источник установил это значение, и храните оба. Все, что не охвачено ни тем, ни другим, является «none», что означает «неклассифицировано», а не «пусто», для чего и предназначен этот поиск. Человек подтверждает шорт-лист, и проверенное значение возвращается для запросов доказательств. Относитесь к ранжированию как к сортировке, потому что аудитор не примет аргумент «так сказала нейросеть».
Дрейф и снимки
AWS остается источником истины, поэтому важно изменение между сбором данных прошлой ночью и сегодняшней. Каждый запуск записывает один неизменяемый ResourceState для каждого ресурса и EvidenceSnapshot, указывающий на этот набор, а сравнение двух снимков дает новые, измененные и исчезнувшие отпечатки.
resource, change"...:loadbalancer/app/gateway/123456", "modified""arn:aws:s3:::halyard-build-artifacts", "deleted""arn:aws:lambda:us-east-1:222222222222:function:statement-export", "created"
API подписки присутствует в обоих снимках и отсутствует в результате. Его отпечаток не изменился, поэтому он не доходит до сравнения (diff).
Вторая ветка UNION фиксирует создания. Привязка к предыдущему снимку позволяет найти то, что изменилось или исчезло, но ресурс, существующий только в сегодняшнем запуске, не имеет точки привязки.
Вычисляйте отпечаток в сборщике, а не в Cypher. Сборщик хранит необработанный JSON конфигурации и может хешировать его с отсортированными ключами, тогда как хеширование карты свойств внутри базы данных означает повторную сериализацию того, для чего Neo4j не дает гарантий порядка.
Агент, который помнит
Агент, который каждую ночь заново приходит к одному и тому же выводу, тратит время впустую и не сообщает ничего нового. Состояние, которое стоит хранить, — это то, что изменилось, что было исследовано и что кто-то принял в качестве исключения; ничто из этого не должно находиться в графе, перестраиваемом каждую ночь. Neo4j’s Agent Memory Service — это размещенное, нативное для графов место для хранения этих данных, и оба хранилища четко разделены.
Связь осуществляется только по ссылке. Запомненное исключение содержит resourceArn и snapshotId, которые разрешаются в Neo4j, но в памяти являются обычными строками, поэтому данные об инфраструктуре не дублируются. Разрешение, основанное на ARN, объединяет голый идентификатор из одного сеанса с более полным из другого.
NAMS хранит этот материал между ночными перестройками. Одна часть — это вердикт, который в данном случае заключается в том, что пиринг миграции был поднят, помечен как SEC-1042 и принят до окончания миграции. Путь к этому вердикту — другая часть, поскольку запросы, которые подтвердили находку, и те, которые ее очистили, несут больше информации, чем один лишь вывод. В течение более длительного периода повторяющиеся флаги конденсируются в паттерн.
Когда пиринг был впервые поднят, кто-то выполнил запрос доступности в один переход, проверил, содержит ли цель данные клиентов, и прочитал тег классификации перед принятием решения. Запись этих шагов превращает вердикт в сценарий (playbook), которому следующий запуск может следовать при анализе сегодняшнего снимка. Та же форма на другом ресурсе затем начинается с этого записанного пути.
Конденсация — это отдельная задача, выполняемая рабочим процессом, который сворачивает повторяющиеся наблюдения в отражение, заменяющее те, которые он суммирует. Сорок семь идентичных ночных флагов превращаются в одну строку, говорящую, что этот путь всегда принимается. Эта строка записывает, как путь обрабатывался, что является иным утверждением, чем вопрос о том, является ли он эксплуатируемым.
Сохраненное исключение само по себе ничего не подавляет. Ваша собственная логика проверяет expiresOn относительно сегодняшнего дня, а отпечаток, который изменился с момента принятого снимка, открывает находку заново, вместо того чтобы наследовать ее вердикт. NAMS хранит суждение и процесс работы, не создавая ни того, ни другого. Интерактивные агенты обращаются к нему через MCP, а автоматические задания отправляют данные в тот же REST API.
Чего это не делает
Эти ограничения перечислены примерно в порядке того, насколько часто они имеют значение. Запросы определяют кандидатов для проверки, поскольку REACHES описывает сетевую экспозицию, а GRANTS — то, что говорит документ политики. Подтверждение того, что путь пригоден для использования, требует дополнительной оценки политики IAM.
Это происходит ночью, поэтому между сборами данных вы смотрите на историю, и все, что создано и уничтожено внутри одного окна, невидимо. Роль сборщика — это отдельная забота, потому что чтение всей организации по всем аккаунтам — это высокоценная цель, находящаяся в графе, который она защищает.
Не делайте выводов о доступности функций по строке версии Aura. Экземпляр, сообщающий о ядре 5.27-aura, по-прежнему запускает uuid(), интерполяцию строк, SEARCH, ACYCLIC и нативный тип VECTOR. Проверьте шпаргалку для вашего тарифа.
Что делать дальше
Сузьте окно устаревания. Config отправляет изменения элементов конфигурации в EventBridge, поэтому измененные ресурсы могут обновляться по мере их изменения, в то время как ночной запуск все еще записывает снимки. Цепочка доказательств остается нетронутой, и граф перестает отставать на день.
Смоделируйте большую часть сети. Транзитные шлюзы, конечные точки VPC, PrivateLink и межрегиональный пиринг — все это перемещает трафик, который эта схема не видит. Каждый добавленный вами элемент должен увеличивать количество компонентов, и если этого не происходит, значит, либо ваша инфраструктура действительно плоская, либо ваш сборщик теряет ребра. Отслеживайте это количество для каждого снимка, наряду с необнаруженными рабочими нагрузками и неклассифицированными хранилищами.
Назначьте владельца для находок. Свяжите каждый ресурс с модулем Terraform, который его создал. ARN дает вам объект для исследования, а путь к модулю — команду и pull request, что обычно и решает, будет ли находка исправлена.
Auditing AWS infrastructure with Neo4j: questions inventory cannot answer была первоначально опубликована в Neo4j Developer Blog на Medium, где люди продолжают обсуждать и реагировать на эту историю.










