AWS: VPC Flow Logs – описание и пример с CloudWatch Logs Insights

Автор: | 09/06/2022

AWS VPC Flow Logs позволяет логгировать инфомрацию о трафике между сетевыми интерфейсами в VPC. Далее, эти логи могут быть сохранены в AWS S3 или отправлены в AWS CloudWatch Logs, при этом включение логгирования трафика никак не виляет на производительность работы сетевого интерфейса.

Кратко рассмотрим основные понятия, доступные настройки и настроим Flow Logs для VPC с передачей данных для анализа в CloduWatch Logs.

Описание VPC Flow Logs

Логи могут быть включены для целой VPC, для подсети, или конкретного интерфейса. При включении для всей VPC – логгирование будет включено для всех сетевых интерфейсов.

Сервисы, для которых можно использовать Flow Logs:

  • Elastic Load Balancing
  • Amazon RDS
  • Amazon ElastiCache
  • Amazon Redshift
  • Amazon WorkSpaces
  • NAT gateways
  • Transit gateways

Данные записываются как flow log records и представляют собой запись с заданными полями.

Use Cases – примеры использования

Что можно отслеживать с помощью Flow logs?

  • срабатывания правил SecuirtyGroup/Network Access List – заблокированные запросы будут отмечены как REJECTED
  • то, ради чего логи внедряем мы – получить картину трафика между VPC и сервисами, что бы понять кто больше всего трафика потребляет, где и сколько cross-AZ трафика и так далее
  • мониторинг удалённых логинов в систему – следить за портами 22 (SSH), 3389 (RDP)
  • отслеживание сканирования портов

Flow Log record – поля

Каждая запись в логе представляет собой данные об IP-трафике, полученном за aggregation interval и представляет собой строку с полями, разделённые пробелами, где каждое поле содержит информацию о передаче данных, например – Source IP, Destination IP и протокол.

По-умолчанию используется следующий формат:

${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}

См. таблицу Available fields в документации, всё в колонке Version 2 включено в default format.

При создании Flow Logs, мы можем использоваться дефолтный формат, или создать свой – рассмотрим его чуть ниже:

Ограничения VPC Flow Logs

  • нельзя использовать с инстансами EC2-Classic
  • нельзя создать логи для VPC-пирингов, если они ведут к VPC  вдругом аккаунте
  • после создания лога – нельзя изменить его конфигурацию или формат записей
  • ?! если у интерфейса несколько IPv4-адресов, и траифк отправляется к одному из secondary-адресов, то в поле dstaddr будет отображён primary-адрес; что бы получить оригинальный адрес – используйте поле pkt-dstaddr
  • ?! если трафик отправлен с или на сетевой интерфейс, поля srcaddr и dstaddr будут содержать его primary private IPv4 адрес; что бы получить оригинальный адрес – используйте поле pkt-dstaddr и pkt-dstaddr

Также, учитывайте, что:

  • не логгируются записи к DNS Amazon, но пишутся, если используется свой DNS
  • не логгируется трафик на и с адреса 169.254.169.254 для получения метаданных EC2-инстанса
  • не логгируется трафик между сетевым интерфейсом EC2 и интерфейсом AWS Network Load Balancer

См. все ограничения в Flow log limitations.

Создание VPC Flow Log

Для создания нам требуется указать:

  • ресурс, логи которого будем писать – VPC, подсеть или конкретный сетевой интерфейс
  • тип трафика, который логгируем (accepted traffic, rejected traffic или all traffic)
  • и куда будем писать данные – в корзину, или CloudWtch Logs

Пока посмотрим, что получится с CloudWatch Logs, а в следующий раз – попробуем визуализировать в Kibana.

CloudWatch Logs Log Group

Создаём Log Group:

Создание IAM Policy и Role

Для того, что бы сервис Flow Logs мог писать в наш CloudWatch – настраиваем ему права доступа.

Переходим в AWS IAM, создаём IAM Policy и IAM Role.

Начинаем с Policy:

Добавляем саму политику:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

Сохраняем:

Создаём роль.

Переходим в IAM Roles, создаём новую, выбираем тип EC2:

Находим созданную выше политику, подключаем:

Задаём имя, сохраняем:

Переходим к Role Trust relationshitp (см. AWS: IAM AssumeRole – описание, примеры), редактируем – меняем значение поля Service на vpc-flow-logs.amazonaws.com:

Указываем:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "vpc-flow-logs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Сохраняем:

VPC – включение Flow Logs

И наконец-то переходим к включению логов – находим нужную VPC, кликаем Flow Logs > Create:

Задаём имя, Filter, Interval:

В Destination выбираем CloudWatch Logs, указываем созданные ранее Log Group и IAM Role:

Format – оставляем Default.

Проверяем Status:

И через несколько минут – данные пошли:

В лог-группе появился первый стрим с именем равным имени Elastic Network Interface, с которого снимаются данные:

CloudWatch Logs Insights

Быстренько посмотрим, что нам доступно в Logs Insights.

Клиаем Queries для получения подсказки по синтаксису запросов:

Например, топ-15 хостов по количеству пакетов:

Или по объёму отправленных данных:

stats sum(bytes) as BytesSent by srcAddr, dstAddr
| sort BytesSent desc

Окей, а что с другими форматами?

К примеру, хочется увидеть направление запроса (egress/ingress) и значение поля pkt-dstaddr.

VPC Flow Log – Custom format

См. примеры на странице Flow log record examples.

Используем такой формат:

region vpc-id az-id subnet-id instance-id interface-id flow-direction srcaddr dstaddr srcport dstport pkt-srcaddr pkt-dstaddr pkt-src-aws-service pkt-dst-aws-service traffic-path packets bytes action

В CloudWatch Logs создаём новый Log group, назовём его bttrm-eks-dev-1-21-vpc-fl-custom, не забываем про retention, что бы данные не лежали вечно:

Возвращаемся к VPC, находим нужную сеть, создаём новый Flow Log, назовём его bttrm-eks-dev-1-21-vpc-fl-custom:

Выбираем Custom Format и поля, которые хотим записывать. При этом учитывайте, что очередность полей в логе будет та, которую вы используете при выборе полей тут.

Т.е. если первым кликнуть на “region” – то и в логе он будет задан первым:

Получается так:

${region} ${vpc-id} ${az-id} ${subnet-id} ${instance-id} ${interface-id} ${flow-direction} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${pkt-srcaddr} ${pkt-dstaddr} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${traffic-path} ${packets} ${bytes} ${action}

Flow Log Custom format и CloudWatch Logs Insights

Но если мы зайдём в Logs Insights, и попробуем любой из предыдущих запросов – получим совсем не те поля, которые хотели:

Т.е. данные мы видим, но как разбить поля по колонкам?

Мы вряд ли будем плотно пользоваться CloudWatch Logs, скорее всего данные пойдут в S3 и потом в ELK (logz.io), поэтому подробно тут останавливаться не буду, но принцип работы посмотрим – пригодится позже для работы с ELK.

CloudWatch Logs по-умолчанию создаёт несколько мета-полей, которые мы можем использовать в запросах:

  • @message: “сырые” данные – всё сообщение в text
  • @timestamp: время события
  • @logStream: имя стрима

Для Custom format, что бы сформировать поля, используем команду parse, которой передаём поле @message со всем содержимым, а затем парсим его по полям, которые разделены пробелами:

parse @message "* * * * * * * * * * * * * * * * * * *" 
| as region, vpc_id, az_id, subnet_id, instance_id, interface_id, 
| flow_direction, srcaddr, dstaddr, srcport, dstport, 
| pkt_srcaddr, pkt_dstaddr, pkt_src_aws_service, pkt_dst_aws_service, 
| traffic_path, packets, bytes, action 
| sort start desc

Тут количество “*” в @message должно быть равным количеству имён полей, которые мы задаём – ${vpc-id} и т.д.

Кроме того, имена полей не должны содержать тире. Т.е. оригинальное имя поля ${vpc-id} для вывода в имени колонки указываем как vpc_id (или vpcID – кому какой формат больше нравится).

Проверяем:

Другое дело!

Кроме parse, мы можем использовать такие команды как filter, display, stats. См. все в CloudWatch Logs Insights query syntax.

Примеры Logs Insights

Ну и попробуем что-то изобразить, например – получить все заблокированные запросы SecuirtyGroup/Network Access List – они будут отмечены как REJECTED.

К нашему запросу:

parse @message "* * * * * * * * * * * * * * * * * * * * * *" 
| as start, end, region, vpc_id, az_id, subnet_id, instance_id, interface_id, 
| flow_direction, srcaddr, dstaddr, srcport, dstport, protocol, 
| pkt_srcaddr, pkt_dstaddr, pkt_src_aws_service, pkt_dst_aws_service, 
| traffic_path, packets, bytes, action

Добавим:

  • filter action="REJECT"
  • stats count(action) as redjects by srcaddr
  • sort redjects desc

Тут:

  • фильтруем по действию над пакетом – выбираем все REJECTED
  • считаем количество записей по полю action, выбирая по IP-адресу источника, выводим в колонке redjects
  • и сортируем по колонке redjects

Т.е. полностью запрос сейчас будет:

parse @message "* * * * * * * * * * * * * * * * * * *" 
| as region, vpc_id, az_id, subnet_id, instance_id, interface_id, 
| flow_direction, srcaddr, dstaddr, srcport, dstport, 
| pkt_srcaddr, pkt_dstaddr, pkt_src_aws_service, pkt_dst_aws_service, 
| traffic_path, packets, bytes, action 
| filter action="REJECT" 
| stats count(action) as redjects by srcaddr 
| sort redjects desc

Получаем:

Мы можем также использовать негативные фильтры и комбинировать условия фильтра с операторами and/or.

К примеру, убрать из вывода все IP, начинающиеся с 162.142.125 – добавляем filter (srcaddr not like "162.142.125."):

...
| filter action="REJECT"
| filter (srcaddr not like "162.142.125.")
| stats count(action) as redjects by srcaddr
| sort redjects desc

См. Sample queries.

И добавим фильтр на выборку только входящих запросов – flow_direction == ingress:

...
| filter action="REJECT"
| filter (srcaddr not like "162.142.125.") and (flow_direction like "ingress")
| stats count(action) as redjects by flow_direction, srcaddr, dstaddr, pkt_srcaddr, pkt_dstaddr
| sort redjects desc

Получаем топ отброшенных запросов – сработало правило SecurityGroup или VPC Network Access List.

И смотрим – что за IP в dstaddr – кому шёл пакет, который был заблокирован?

Переходим в EC2 > Network Interfaces, ищем по Private IP:

Находим “Elastic IP address owner“:

Балансировщику.

Если адрес не находится в AWS – возможно, это какой-то ендпоинт в Kubernetes – ищем например так:

[simterm]

$ kubectl get endpoints --all-namespaces | grep 10.1.55.140
dev-ios-check-translation-ns                     ios-check-translation-backend-svc                    10.1.55.140:3000                                                     58d
dev-ios-check-translation-ns                     ios-check-translation-frontend-svc                   10.1.55.140:80                                                       58d

[/simterm]

В целом на это всё.

В следующей части – настроим сбор логов в AWS S3, затем будем их оттуда собирать в ELK, и там попробуем сделать визуализацию и алерты.

Ссылки по теме

VPC Flow Logs

CloudWatch Logs