Сетевая архитектура стенда. Источник: Авторская схема (Excalidraw)
Сетевая архитектура стенда. Источник: Авторская схема (Excalidraw)

❯ Вступление

В этой статье будет разбираться построение многосегментного Linux-маршрутизатора на базе Ubuntu 24.04, Netplan и nftables на основе моего лабораторного стенда. Стенд состоит из 7 виртуальных машин и нескольких изолированных сетевых сегментов: LAN, DMZ и MGMT, а также имитирует провайдерский маршрутизатор и внешнего клиента. Все виртуальные машины разворачиваются с помощью Vagrant и VirtualBox, в качестве альтернативы, если вам хочется, такую схему также можно развернуть на облачных серверах. Для этого подойдет хостинг Timeweb Cloud, в таком случае Vagrantfile писать будет не нужно.

Сам стенд можно посмотреть в моем GitHub. В папке docs/ есть команды с краткими комментариями по шагам, однако всё самое важное будет объясняться здесь.

Всего будет 12 этапов. Для каждого этапа я подготовил пошаговый разбор конфигураций, правил файрвола и команд диагностики. Мы с нуля настроим адресацию, включим IP forwarding, разберем работу stateful-фильтрации через conntrack, организуем SNAT/DNAT, развернем локальный DNS и изолируем критические сегменты и многое другое.

❯ Сегменты

Сеть разделяется на сегменты, каждый сегмент имеет свою подсеть и шлюз. Сначала пройдемся по терминам.

Сегмент сети — отдельная часть общей сети. Устройства внутри одного сегмента могут общаться напрямую, а трафик между разными сегментами проходит через маршрутизатор (какая-то конкретная точка, общая у сегментов, через которую идет трафик) и может контролироваться файрволом.

Сегментация (разделение сети) нужна, чтобы изолировать друг от друга разные по уровню доверия зоны: пользовательскую сеть, публичные сервисы, сеть управления и внешний мир.

Подсеть — это диапазон IP-адресов, объединённых общей маской. Например, запись 10.10.10.0/24 означает сеть с маской 255.255.255.0: в ней 256 адресов, из которых 254 можно назначить устройствам. Все узлы одной подсети видят друг друга напрямую, без маршрутизации.

Шлюз — это IP-адрес маршрутизатора в данной подсети. Когда устройство хочет отправить пакет в другую подсеть или во внешнюю сеть, оно отправляет его на шлюз. Шлюз решает, куда передать пакет дальше. В нашем стенде роль шлюзов выполняют linux-gateway и upstream-router.

Для конкретного этого стенда можно построить такую таблицу:

Сегмент

Подсеть

Шлюз

lab-internet

198.51.100.0/24

198.51.100.1

lab-wan

172.16.0.0/24

172.16.0.1

lab-lan

10.10.10.0/24

10.10.10.1

lab-dmz

10.10.20.0/24

10.10.20.1

lab-mgmt

10.10.30.0/24

10.10.30.1

  1. WAN (172.16.0.0/24): Внешний канал. Связывает наш шлюз с upstream-router.

  2. LAN (10.10.10.0/24): Внутренняя пользовательская сеть (lan-workstation и lan-dns-server).

  3. DMZ (10.10.20.0/24): Демилитаризованная зона для публичных сервисов (dmz-web-server). Это сегмент сети для публичных сервисов, к которым нужен доступ извне. Его специально держат отдельно от внутренней сети, чтобы если сервер взломают, злоумышленник не попал сразу в LAN.

  4. MGMT (10.10.30.0/24): Защищённая сеть управления (mgmt-workstation). Защищённый сегмент для администраторов и управления оборудованием. В MGMT стоит mgmt-workstation, и только из неё разрешён SSH/ICMP к шлюзу, LAN и DMZ

❯ Узлы

Узел — это отдельное устройство в сети: компьютер, сервер и т.д.

В нашем стенде каждый узел — это отдельная виртуальная машина (VM), созданная в VirtualBox. У каждой VM есть имя (hostname), набор сетевых интерфейсов и своя роль

Для конкретного этого стенда можно построить такую таблицу:

VM

Роль

upstream-router

Имитация провайдера

linux-gateway

Основной шлюз + firewall

lan-workstation

Рабочая станция в LAN

lan-dns-server

DNS-сервер в LAN

dmz-web-server

Nginx в DMZ

mgmt-workstation

Админская станция

external-client

Внешний клиент

❯ Этап 1: Подготовка Vagrant и интерфейсов

Источник

Кратко про то, что такое Vagrant и про некоторые особенные для этого стенда настройки.

Vagrant — инструмент для управления виртуальными машинами через конфигурационный файл. Вместо ручного создания VM в VirtualBox мы описываем весь стенд в Vagrantfile: базовый образ, ресурсы и сети.

virtualbox__intnet — создаёт изолированную внутреннюю сеть VirtualBox. Она соединяет только виртуальные машины между собой и не выходит в реальную сеть. Разные имена (lab-lan, lab-dmz) — разные сегменты.

auto_config: false — запрещает Vagrant автоматически настраивать этот интерфейс. То есть интерфейсы будут созданы, но они будут без привязки к какому-то адресу. IP-адреса мы назначим сами через Netplan на следующем этапе (обычно выключать не нужно, но для настройки «с нуля» решил сделать так).

Здесь сам Vagrantfile
Vagrant.configure("2") do |config|

  # Базовый образ Ubuntu 24.04 для всех виртуальных машин.
  # box — имя образа, box_version — конкретная зафиксированная версия,
  # чтобы стенд воспроизводился одинаково при каждом запуске.
  config.vm.box = "cloud-image/ubuntu-24.04"
  config.vm.box_version = "20260814.0.0"

  # Внешний маршрутизатор: имитирует провайдера и соединяет WAN с внешней сетью
  config.vm.define "upstream-router" do |vm|
    vm.vm.hostname = "upstream"

    # Сеть между upstream и gateway (сегмент lab-wan)
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-wan",
      auto_config: false

    # Внешняя сеть, имитирующая Интернет (сегмент lab-internet)
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-internet",
      auto_config: false

    # Ресурсы и настройки VirtualBox
    vm.vm.provider "virtualbox" do |vb|
      vb.name = "upstream"
      vb.cpus = 2
      vb.memory = 1024
      # По идее настройка должна отключать gui, от нее был бы смысл, если бы вместо vboxvga вообще не было контроллера.
    
      vb.gui = false
      # Используется графический контроллер vboxvga
      # Если не указывается, то используется vmsvga, 
      # но с vagrant у меня почему-то виртуалки в таком случае не запускаются
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # Главный Linux-маршрутизатор лаборатории: 4 лабораторных интерфейса
  config.vm.define "linux-gateway" do |vm|
    vm.vm.hostname = "gateway"

    # WAN: связь с upstream
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-wan",
      auto_config: false

    # LAN: внутренняя пользовательская сеть
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-lan",
      auto_config: false

    # DMZ: сеть публичных сервисов
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-dmz",
      auto_config: false

    # MGMT: отдельная сеть для администрирования
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-mgmt",
      auto_config: false

    # Ресурсы и настройки VirtualBox
    vm.vm.provider "virtualbox" do |vb|
      vb.name = "gateway"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # Рабочая станция пользователя в LAN
  config.vm.define "lan-workstation" do |vm|
    vm.vm.hostname = "workstation"

    # Подключение только к LAN
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-lan",
      auto_config: false

    vm.vm.provider "virtualbox" do |vb|
      vb.name = "workstation"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # DNS-сервер внутренней сети
  config.vm.define "lan-dns-server" do |vm|
    vm.vm.hostname = "dns"

    # Подключение только к LAN
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-lan",
      auto_config: false

    vm.vm.provider "virtualbox" do |vb|
      vb.name = "dns"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # Веб-сервер в DMZ
  config.vm.define "dmz-web-server" do |vm|
    vm.vm.hostname = "web"

    # Подключение только к DMZ
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-dmz",
      auto_config: false

    vm.vm.provider "virtualbox" do |vb|
      vb.name = "web"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # Административная рабочая станция
  config.vm.define "mgmt-workstation" do |vm|
    vm.vm.hostname = "admin"

    # Подключение только к MGMT
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-mgmt",
      auto_config: false

    vm.vm.provider "virtualbox" do |vb|
      vb.name = "admin"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

  # Внешний клиент, имитирующий пользователя из Интернета
  config.vm.define "external-client" do |vm|
    vm.vm.hostname = "external-client"

    # Подключение только к внешней сети
    vm.vm.network "private_network",
      virtualbox__intnet: "lab-internet",
      auto_config: false

    vm.vm.provider "virtualbox" do |vb|
      vb.name = "external-client"
      vb.cpus = 2
      vb.memory = 1024
      vb.gui = false
      vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"]
    end
  end

end

Теперь с помощью vagrant up поднимаем машины.

Всё запустилось, теперь напишем цикл на bash, чтобы зайти на каждую машину и получить список интерфейсов.

# Перебираем все виртуальные машины стенда по очереди.
# Список соответствует именам VM из Vagrantfile.
for vm in upstream-router linux-gateway lan-workstation lan-dns-server \
          dmz-web-server mgmt-workstation external-client; do

    # Печатаем заголовок, чтобы понимать, к какой машине относится вывод.
    echo "===== $vm ====="

    # Заходим на VM по SSH и выполняем команду:
    # ip -br link — краткий список сетевых интерфейсов и их состояний (UP/DOWN).
    vagrant ssh "$vm" -c 'ip -br link'
done

Получим такую карту интерфейсов:

VM

enp0s3

enp0s8

enp0s9

enp0s10

enp0s16

upstream-router

Vagrant NAT

lab-wan

lab-internet

—

—

linux-gateway

Vagrant NAT

lab-wan

lab-lan

lab-dmz

lab-mgmt

lan-workstation

Vagrant NAT

lab-lan

—

—

—

lan-dns-server

Vagrant NAT

lab-lan

—

—

—

dmz-web-server

Vagrant NAT

lab-dmz

—

—

—

mgmt-workstation

Vagrant NAT

lab-mgmt

—

—

—

external-client

Vagrant NAT

lab-internet

—

—

—

На всех виртуальных машинах есть интерфейс enp0s3. Он обязателен для Vagrant, так как без него vagrant потеряет доступ к узлам. На него внимание не обращаем, он в сети участвовать не будет.

Также небольшая справка по поводу названий интерфейсов. Имена интерфейсов в Linux зависят от PCI-слота, в который VirtualBox подключила сетевую карту. Порядок сетей в Vagrantfile не связан с порядком имён внутри VM: четвёртый интерфейс может получить имя enp0s16, а не enp0s11. Поэтому мы смотрим список интерфейсов на каждой машине отдельно командой ip -br link.

❯ Этап 2: Назначение IP-адресов через Netplan

Схема прохождения сетевых настроек от Vagrant до IP-адресов. Источник: Авторская схема (Excalidraw)
Схема прохождения сетевых настроек от Vagrant до IP-адресов. Источник: Авторская схема (Excalidraw)

На этом этапе зададим IP-адреса интерфейсов. Его можно было бы пропустить, если бы в Vagrantfile не было auto_config: false настройки, но для наглядности, мне кажется, стоит этот этап тоже сделать ручками.

Конкретно в моем случае в /etc/netplan везде стандартный файл назывался 50-cloud-init.yaml. Он управляет только enp0s3. Чтобы ничего там не сломать, буду создавать отдельный 60-lab.yaml.

Для каждого сервера повторяется один общий блок кода команд. Отличается только имя при подключении по ssh и сам конфиг.

# Подключаемся к виртуальной машине linux-gateway по SSH
vagrant ssh linux-gateway

# Открываем файл конфигурации сети в редакторе nano (с правами root)
sudo nano /etc/netplan/60-lab.yaml

# Устанавливаем права доступа 600 (только владелец может читать и писать) —
# netplan требует такие права для файлов конфигурации из соображений безопасности
sudo chmod 600 /etc/netplan/60-lab.yaml

# Генерируем конфигурацию бэкендов (проверка синтаксиса YAML без применения)
sudo netplan generate

# Применяем сетевые настройки из конфигурации
sudo netplan apply

# Кратко показать интерфейсы и назначенные им IP-адреса
ip -br addr

# Показать таблицу маршрутизации: куда уходят пакеты и какой шлюз по умолчанию
ip route

Ниже конфиги netplan для каждого отдельного сервера с пояснениями. Они все примерно одинаковые, с небольшими отличиями в назначенных IP-адресах.

Linux Gateway
network:
  version: 2
  ethernets:
    enp0s8:  # lab-wan (канал к upstream-router)
      addresses:
        - 172.16.0.2/24
    enp0s9:  # lab-lan (локальная сеть)
      addresses:
        - 10.10.10.1/24
    enp0s10: # lab-dmz (демилитаризованная зона)
      addresses:
        - 10.10.20.1/24
    enp0s16: # lab-mgmt (сеть управления)
      addresses:
        - 10.10.30.1/24

После применения конфигурации интерфейсы получат IP-адреса 172.16.0.2, 10.10.10.1, 10.10.20.1, 10.10.30.1.

Upstream Router
network:
  version: 2
  ethernets:
    enp0s8:  # lab-wan
      addresses:
        - 172.16.0.1/24
    enp0s9:  # lab-internet
      addresses:
        - 198.51.100.1/24
LAN Workstation
network:
  version: 2
  ethernets:
    enp0s8:  # lab-lan
      addresses:
        - 10.10.10.10/24
LAN DNS Server
network:
  version: 2
  ethernets:
    enp0s8:  # lab-lan
      addresses:
        - 10.10.10.53/24
DMZ Web Server
network:
  version: 2
  ethernets:
    enp0s8:  # lab-dmz
      addresses:
        - 10.10.20.10/24
MGMT Workstation
network:
  version: 2
  ethernets:
    enp0s8:  # lab-mgmt
      addresses:
        - 10.10.30.10/24
External Client
network:
  version: 2
  ethernets:
    enp0s8:  # lab-internet
      addresses:
        - 198.51.100.10/24

После завершения настройки Netplan на всех узлах адресация в стенде распределилась следующим образом:

Узел

Назначенные IP-адреса

linux-gateway

172.16.0.2, 10.10.10.1, 10.10.20.1, 10.10.30.1

upstream-router

172.16.0.1, 198.51.100.1

lan-workstation

10.10.10.10

lan-dns-server

10.10.10.53

dmz-web-server

10.10.20.10

mgmt-workstation

10.10.30.10

external-client

198.51.100.10

❯ Этап 3: Маршрутизация

На этом шаге мы прописываем на всех виртуальных машинах маршруты и шлюзы по умолчанию. Пересылку пакетов (IP forwarding) и файрвол (nftables) пока не включаем — сначала нужно, чтобы каждый узел правильно понимал, куда отправлять трафик.

Нюанс работы с Vagrant NAT

Как я уже упоминал ранее, на всех виртуальных машинах по умолчанию присутствует default-маршрут через интерфейс enp0s3. Проблема в том, что в данный момент этот шлюз имеет приоритет, поэтому нужно сделать так, чтобы у него этого приоритета не было, и трафик через него не шел.

В netplan есть такая настройка metric. Чем меньше в ней число — тем выше приоритет, в enp0s3 значение равняется 100, поэтому, чтобы решить нашу проблему нужно поставить metric: 50 (к примеру). Эта настройка будет нужна на всех серверах, так как на них нужно будет прописать default-маршрут к шлюзу, кроме Linux Gateway и Upstream Router.

Теперь для каждого сервера пишем новый netplan конфиг, с дополнительными параметрами. Для удобного обновления файла /etc/netplan/60-lab.yaml используем утилиту tee:

Linux Gateway
# Подключаемся к главному шлюзу по SSH
vagrant ssh linux-gateway

# Создаем и записываем конфигурацию в /etc/netplan/60-lab.yaml
# Экранирование 'EOF' предотвращает подстановку переменных bash внутри документа
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата конфигурации Netplan
  renderer: networkd # Использование бэкенда systemd-networkd для управления сетью
  ethernets:
    enp0s8: # Сетевой интерфейс сегмента lab-wan
      addresses:
        - 172.16.0.2/24 # IP-адрес linux-gateway в сегменте WAN
      routes:
        # Статический маршрут к внешней сети через upstream-router
        - to: 198.51.100.0/24 # Целевая внешняя подсеть
          via: 172.16.0.1     # IP-адрес следующего перехода (next-hop)
    enp0s9: # Сетевой интерфейс сегмента lab-lan
      addresses:
        - 10.10.10.1/24 # IP-адрес шлюза для подсети LAN
    enp0s10: # Сетевой интерфейс сегмента lab-dmz
      addresses:
        - 10.10.20.1/24 # IP-адрес шлюза для подсети DMZ
    enp0s16: # Сетевой интерфейс сегмента lab-mgmt
      addresses:
        - 10.10.30.1/24 # IP-адрес шлюза для подсети MGMT
EOF

# Генерируем конфигурационные файлы и применяем настройки сети
sudo netplan generate && sudo netplan apply

В таблице маршрутизации (ip route) должны появиться маршруты к сетям LAN, DMZ, MGMT, WAN и статический маршрут 198.51.100.0/24 via 172.16.0.1.

Upstream Router
# Подключаемся к провайдерскому маршрутизатору по SSH
vagrant ssh upstream-router

# Записываем конфигурационный файл Netplan
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия спецификации Netplan
  ethernets:
    enp0s8: # Сетевой интерфейс сегмента lab-wan
      addresses:
        - 172.16.0.1/24 # IP-адрес внешнего маршрутизатора
      routes:
        # Статические маршруты к внутренним сетям лаборатории за linux-gateway
        - to: 10.10.10.0/24 # Подсеть LAN
          via: 172.16.0.2   # Адрес linux-gateway в сегменте WAN
        - to: 10.10.20.0/24 # Подсеть DMZ
          via: 172.16.0.2   # Адрес linux-gateway в сегменте WAN
        - to: 10.10.30.0/24 # Подсеть MGMT
          via: 172.16.0.2   # Адрес linux-gateway в сегменте WAN
    enp0s9: # Сетевой интерфейс сегмента lab-internet
      addresses:
        - 198.51.100.1/24 # IP-адрес в симулируемом Интернете
EOF

# Проверяем синтаксис и активируем настройки
sudo netplan generate && sudo netplan apply

Upstream Router должен знать, что все внутренние подсети стенда (10.10.10.0/24, 10.10.20.0/24, 10.10.30.0/24) находятся за главным шлюзом linux-gateway (172.16.0.2).

LAN Workstation
# Подключаемся к пользовательской рабочей станции
vagrant ssh lan-workstation

# Формируем файл конфигурации сети
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата Netplan
  ethernets:
    enp0s8: # Интерфейс подключения к сегменту lab-lan
      addresses:
        - 10.10.10.10/24 # IP-адрес хоста
      routes:
        # Маршрут по умолчанию через linux-gateway
        - to: default     # Обозначает любой назначенный трафик (0.0.0.0/0)
          via: 10.10.10.1 # IP-адрес linux-gateway в сети LAN
          metric: 50      # Высший приоритет по сравнению с DHCP (100)
EOF

# Генерация и активация новой сетевой конфигурации
sudo netplan generate && sudo netplan apply
LAN DNS Server
# Подключаемся к DNS-серверу локальной сети
vagrant ssh lan-dns-server

# Записываем конфигурацию с маршрутом по умолчанию
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата Netplan
  ethernets:
    enp0s8: # Интерфейс подключения к сегменту lab-lan
      addresses:
        - 10.10.10.53/24 # IP-адрес DNS-сервера
      routes:
        # Маршрут по умолчанию через linux-gateway
        - to: default     # Весь внешне ориентированный трафик
          via: 10.10.10.1 # IP-адрес linux-gateway в сети LAN
          metric: 50      # Метрика для приоритета перед NAT-интерфейсом
EOF

# Генерация и активация сетевой конфигурации
sudo netplan generate && sudo netplan apply
DMZ Web Server
# Подключаемся к веб-серверу DMZ
vagrant ssh dmz-web-server

# Формируем файл конфигурации Netplan
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата Netplan
  ethernets:
    enp0s8: # Интерфейс подключения к сегменту lab-dmz
      addresses:
        - 10.10.20.10/24 # IP-адрес веб-сервера
      routes:
        # Маршрут по умолчанию через linux-gateway
        - to: default     # Направление неуточненного трафика
          via: 10.10.20.1 # IP-адрес linux-gateway в сети DMZ
          metric: 50      # Приоритетная метрика
EOF

# Применение новых параметров сети
sudo netplan generate && sudo netplan apply
MGMT Workstation
# Подключаемся к административной станции
vagrant ssh mgmt-workstation

# Записываем конфигурацию сети
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата Netplan
  ethernets:
    enp0s8: # Интерфейс подключения к сегменту lab-mgmt
      addresses:
        - 10.10.30.10/24 # IP-адрес рабочей станции
      routes:
        # Маршрут по умолчанию через linux-gateway
        - to: default     # Направление трафика по умолчанию
          via: 10.10.30.1 # IP-адрес linux-gateway в сети MGMT
          metric: 50      # Приоритетная метрика
EOF

# Генерация и активация сетевой конфигурации
sudo netplan generate && sudo netplan apply
External Client
# Подключаемся к внешнему клиенту
vagrant ssh external-client

# Записываем конфигурацию сети
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата Netplan
  ethernets:
    enp0s8: # Интерфейс подключения к сегменту lab-internet
      addresses:
        - 198.51.100.10/24 # IP-адрес клиента
      routes:
        # Маршрут по умолчанию через upstream-router
        - to: default        # Весь исходящий трафик
          via: 198.51.100.1  # IP-адрес upstream-router в интернет-сегменте
          metric: 50         # Приоритетная метрика
EOF

# Применение настроек сети
sudo netplan generate && sudo netplan apply

Теперь все узлы стенда имеют настроенный маршрут по умолчанию.

Linux Gateway знает путь к внешней сети 198.51.100.0/24 через Upstream Router.

Upstream Router знает пути ко всем внутренним сегментам (10.10.10.0/24, 10.10.20.0/24, 10.10.30.0/24) через Linux Gateway.

Пересылка пакетов (IP forwarding) и правила сетевого экрана (nftables) ещё не активированы, поэтому трафик между сегментами пока не передаётся.

❯ Этап 4: IP forwarding

Включение пересылки пакетов (IP forwarding) является обязательным условием для превращения узла Linux в маршрутизатор. Без этой функции ядро Linux отбрасывает пакеты, адресованные другим узлам, из-за чего трафик не может пройти дальше одного хопа.

Хоп — это один переход пакета от одного сетевого устройства к другому по пути к цели. Каждый маршрутизатор, через который проходит пакет, — это отдельный хоп.

На нашем стенде IP forwarding активируется на двух ключевых машинах:

  1. linux-gateway — передаёт пакеты между внешним каналом WAN и внутренними сегментами (LAN, DMZ, MGMT).

  2. upstream-router — передаёт пакеты между внешним сегментом (lab-internet) и каналом связи со шлюзом (lab-wan).

Файервол на данном этапе сознательно не настраивается. Это позволяет проверить связность и убедиться в правильности маршрутизации перед созданием ограничений безопасности.

Linux Gateway
# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Записываем параметр включения IP forwarding в постоянный конфигурационный файл sysctl
sudo tee /etc/sysctl.d/99-ipforward.conf > /dev/null <<'EOF'
# Активация пересылки IPv4-пакетов между сетевыми интерфейсами на уровне ядра
net.ipv4.ip_forward=1
EOF

# Перезагружаем и применяем настройки sysctl из всех конфигурационных файлов в системе
sudo sysctl --system

# Проверяем, что параметр пересылки пакетов активирован (ожидается: net.ipv4.ip_forward = 1)
sysctl net.ipv4.ip_forward
Upstream Router
# Подключаемся по SSH к провайдерскому маршрутизатору
vagrant ssh upstream-router

# Записываем параметр включения IP forwarding в конфигурацию sysctl
sudo tee /etc/sysctl.d/99-ipforward.conf > /dev/null <<'EOF'
# Разрешаем ядру транзитировать IPv4-пакеты между интерфейсами enp0s8 и enp0s9
net.ipv4.ip_forward=1
EOF

# Применяем измененные параметры ядра
sudo sysctl --system

# Проверяем текущий статус функции пересылки пакетов
sysctl net.ipv4.ip_forward

После включения пересылки проверяем доступность подключенных шлюзов с соседних виртуальных машин:

# Проверка доступности провайдера (upstream) с внешнего клиента
vagrant ssh external-client -c 'ping -c 3 198.51.100.1'

# Проверка доступности главного шлюза (linux-gateway) с провайдерского маршрутизатора
vagrant ssh upstream-router -c 'ping -c 3 172.16.0.2'

# Проверка доступности всех внутренних узлов и внешнего маршрутизатора с linux-gateway
vagrant ssh linux-gateway -c \
  'ping -c 3 10.10.10.10 && \
   ping -c 3 10.10.20.10 && \
   ping -c 3 10.10.30.10 && \
   ping -c 3 172.16.0.1'

Так как сетевой экран ещё не активирован, пакеты должны проходить между любыми сегментами:

# Проверка доступности DMZ из внешней сети (External Client -> DMZ Web Server)
vagrant ssh external-client -c 'ping -c 3 10.10.20.10'

# Проверка доступности DMZ из локальной сети (LAN Workstation -> DMZ Web Server)
vagrant ssh lan-workstation -c 'ping -c 3 10.10.20.10'

# Проверка доступности LAN и MGMT из демилитаризованной зоны (DMZ Web Server -> LAN / MGMT)
vagrant ssh dmz-web-server -c \
  'ping -c 3 10.10.10.10 && ping -c 3 10.10.30.10'

Все ICMP-запросы должны успешно проходить. Это подтверждает правильность настройки таблицы маршрутизации и включения IP forwarding на обоих роутерах.

❯ Этап 5: веб-сервис в DMZ

Источник

На данном этапе разворачивается веб-сервер (Nginx) в зоне dmz-web-server и проводятся дополнительные проверки до включения межсетевого экрана.

Подключаемся к узлу, обновляем индекс пакетов и устанавливаем Nginx:

# Подключаемся по SSH к серверу демилитаризованной зоны
vagrant ssh dmz-web-server

# Обновляем списки пакетов apt в системе
sudo apt update

# Устанавливаем веб-сервер Nginx в автоматическом режиме
sudo apt install -y nginx

# Проверяем текущий статус службы Nginx (без открытия пейджера)
sudo systemctl status nginx --no-pager

# Проверяем, что Nginx успешно прослушивает 80-й порт на всех IPv4/IPv6 интерфейсах
ss -tulpn | grep ':80'

Выполняем HTTP-запросы к веб-серверу из различных сегментов стенда:

# Запрос к веб-серверу DMZ со станции внутренней локальной сети (LAN Workstation -> DMZ Web Server)
vagrant ssh lan-workstation -c 'curl http://10.10.20.10'

# Запрос к веб-серверу DMZ с внешнего клиента (External Client -> DMZ Web Server)
vagrant ssh external-client -c 'curl http://10.10.20.10'

Оба запроса должны вернуть стандартный приветственный HTML от Nginx.

❯ Этап 6: Базовый nftables

На данном этапе разворачивается и настраивается фильтрация пакетов nftables на главном шлюзе linux-gateway. Мы переходим от открытой сети к контролю трафика с политикой по умолчанию DROP.

Чтобы не потерять доступ к виртуальным машинам через vagrant ssh, в цепочке input явно разрешается трафик через технический enp0s3. Также на этом этапе настраивается доступ для административной сети MGMT (SSH и ICMP на сам шлюз).

Формируем конфигурационный файл /etc/nftables.conf на главном шлюзе:

Набор правил Linux Gateway Nftables
# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Обновляем индексы пакетов apt
sudo apt update

# Устанавливаем межсетевой экран nftables
sudo apt install -y nftables

sudo tee /etc/nftables.conf > /dev/null <<'EOF'
# Очищаем текущий набор правил nftables перед загрузкой новых
flush ruleset

# Создаем таблицу для IPv4-пакетов с именем "filter"
table ip filter {

    # Цепочка обработки входящего трафика, адресованного самому шлюзу
    chain input {
        # Подключаем хук input с приоритетом filter и жесткой политикой DROP
        type filter hook input priority filter; policy drop;

        # Разрешаем весь локальный трафик на loopback-интерфейсе
        iifname "lo" accept

        # Stateful-фильтрация: разрешаем входящий трафик для уже установленных и связанных соединений
        ct state established,related accept

        # Разрешаем SSH-подключения (порт 22) через служебный Vagrant NAT для работы vagrant ssh
        iifname "enp0s3" tcp dport 22 accept

        # Разрешаем узлам административной сети (MGMT) SSH-доступ к самому шлюзу
        ip saddr 10.10.30.0/24 tcp dport 22 accept

        # Разрешаем узлам административной сети (MGMT) отправлять ICMP Echo-Request (ping) на шлюз
        ip saddr 10.10.30.0/24 icmp type echo-request accept
    }

    # Цепочка обработки транзитного трафика, проходящего ЧЕРЕЗ шлюз
    chain forward {
        # Подключаем хук forward с приоритетом filter и жесткой политикой DROP
        type filter hook forward priority filter; policy drop;

        # На данном шаге транзитный трафик полностью заблокирован — правила разрешения отсутствуют
    }

    # Цепочка обработки исходящего трафика, генерируемого самим шлюзом
    chain output {
        # Подключаем хук output с приоритетом filter и разрешающей политикой ACCEPT
        type filter hook output priority filter; policy accept;
    }
}
EOF

# Синтаксическая проверка файла конфигурации (флаг -c проверяет синтаксис без применения)
sudo nft -c -f /etc/nftables.conf

# Загрузка правил в ядро
sudo nft -f /etc/nftables.conf

# Вывод текущего активного набора правил nftables
sudo nft list ruleset

# Включаем автозагрузку службы nftables при старте системы и перезапускаем её
sudo systemctl enable nftables
sudo systemctl restart nftables

# Проверяем статус работы службы nftables (ожидаем значение active)
sudo systemctl status nftables --no-pager

Из-за смены политики по умолчанию в цепочке FORWARD на DROP, транзитный трафик между всеми сегментами теперь полностью заблокирован.

Новые входящие подключения к самому шлюзу разрешены только в трёх случаях:

  • Vagrant → gateway:22 — служебное SSH-подключение через enp0s3. Это нужно, чтобы работала команда vagrant ssh linux-gateway. Без этого правила мы потеряли бы управление шлюзом.

  • MGMT → gateway:22 — администратор (10.10.30.0/24) может зайти на шлюз по SSH.

  • MGMT → gateway (ICMP) — с той же админской станции можно пинговать шлюз.

Проверяем блокировку транзитного трафика и сохранение доступности шлюза при подключении напрямую:

# 1. Запросы, которые должны блокироваться (таймаут):

# Запрос со станции внешнего клиента к веб-серверу в DMZ
vagrant ssh external-client -c 'curl --connect-timeout 3 http://10.10.20.10'

# Запрос из локальной сети (LAN) к веб-серверу в DMZ
vagrant ssh lan-workstation -c 'curl --connect-timeout 3 http://10.10.20.10'

# Пинг с рабочей станции LAN до локального шлюза (ICMP не разрешён для LAN в цепочке input)
vagrant ssh lan-workstation -c 'ping -c 3 10.10.10.1'


# 2. Проверки, которые должны проходить:

# Пинг с админской станции MGMT до шлюза в сети управления
vagrant ssh mgmt-workstation -c 'ping -c 3 10.10.30.1'

# Проверка доступности SSH-порта 22 на шлюзе из сети MGMT
vagrant ssh mgmt-workstation -c 'nc -vz 10.10.30.1 22'

❯ Этап 7: Stateful-фильтрация и conntrack

Stateful-фильтрация — это когда файрвол помнит, какие соединения уже открыты. Новое соединение разрешается правилом ct state new, а ответные пакеты пропускаются автоматически правилом ct state established,related accept. За это отвечает подсистема conntrack: она ведёт таблицу всех активных сессий.

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

Нужно добавить stateful-правила в цепочку FORWARD:

# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Добавляем правило для автоматического пропуска обратного трафика уже установленных сессий
sudo nft add rule ip filter forward ct state established,related accept

# Проверяем текущую цепочку forward в таблице filter
sudo nft list chain ip filter forward

В цепочке FORWARD отобразится примерно следующее:

chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
}

Теперь точечно разрешаем пользователям из локальной сети (10.10.10.0/24) инициировать новые HTTP-соединения к веб-серверу в DMZ (10.10.20.10:80):

# Добавляем правило разрешения инициации (ct state new) TCP-трафика на 80 порт веб-сервера
sudo nft add rule ip filter forward \
    ip saddr 10.10.10.0/24 \
    ip daddr 10.10.20.10 \
    tcp dport 80 \
    ct state new \
    accept

# Проверяем итоговый порядок правил в цепочке forward
sudo nft list chain ip filter forward

Правила в цепочке идут в определённом порядке, и это важно:

  1. ct state established,related accept — сначала пропускаются пакеты уже открытых сессий. Это быстро, потому что не нужно проверять каждое новое соединение.

  2. ip saddr 10.10.10.0/24 ip daddr 10.10.20.10 tcp dport 80 ct state new accept — затем проверяются условия для новых HTTP-соединений.

  3. policy drop — всё остальное, что не подошло под эти правила, блокируется.

Наблюдение за таблицей conntrack

Для наглядного отслеживания того, как ядро Linux отслеживает состояния сетевых сессий, установим утилиту conntrack и запустим мониторинг событий в реальном времени:

# Устанавливаем утилиту работы с таблицей соединений на linux-gateway
sudo apt install -y conntrack

# Запускаем отслеживание событий conntrack в режиме реального времени
sudo conntrack -E

При выполнении HTTP-запроса с lan-workstation в окне мониторинга отобразится жизненный цикл TCP-соединения:

[NEW]       tcp      6 120 SYN_SENT src=10.10.10.10 dst=10.10.20.10 sport=... dport=80
[UPDATE]    tcp      6 60 SYN_RECV src=10.10.10.10 dst=10.10.20.10 sport=... dport=80
[UPDATE]    tcp      6 432000 ESTABLISHED src=10.10.10.10 dst=10.10.20.10 sport=... dport=80 [ASSURED]
[UPDATE]    tcp      6 120 FIN_WAIT src=10.10.10.10 dst=10.10.20.10 sport=... dport=80
[UPDATE]    tcp      6 30 LAST_ACK src=10.10.10.10 dst=10.10.20.10 sport=... dport=80
[UPDATE]    tcp      6 120 TIME_WAIT src=10.10.10.10 dst=10.10.20.10 sport=... dport=80

Ответные пакеты от веб-сервера к рабочей станции проходят через шлюз благодаря правилу ct state established,related — отдельно разрешать направление DMZ → LAN не нужно. Stateful-фильтрация работает.

❯ Этап 8: SNAT для LAN → External

Схема SNAT. Источник: Авторская схема (Excalidraw)
Схема SNAT. Источник: Авторская схема (Excalidraw)

На этом этапе мы организуем выход приватной локальной сети (LAN) во внешнюю сеть (lab-internet) через механизмы трансляции сетевых адресов — SNAT (Source Network Address Translation).

Логика работы SNAT

Вся приватная подсеть 10.10.10.0/24 будет выходить во внешний мир, подменяя свой исходный IP-адрес на WAN-адрес шлюза 172.16.0.2 (интерфейс enp0s8 на linux-gateway).

На upstream-router уже есть маршрут обратно в LAN через 172.16.0.2. Формально пакеты могли бы ходить и без SNAT. Но SNAT нужен, чтобы сымитировать реальную схему: вся приватная сеть выходит во внешний сегмент через один адрес шлюза.

Фиксация текущего рабочего ruleset

Перед созданием новых таблиц и цепочек экспортируем текущие активные правила в файл конфигурации /etc/nftables.conf, чтобы сохранить все ранее настроенные блокировки и правила фильтрации:

# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Дамп активного правил с предварительной очисткой во временный файл
sudo sh -c 'echo "flush ruleset"; nft list ruleset' > /tmp/nftables.conf

# Замещаем постоянный файл конфигурации и устанавливаем безопасные права доступа
sudo mv /tmp/nftables.conf /etc/nftables.conf
sudo chmod 600 /etc/nftables.conf

# Проверяем синтаксис обновленного файла конфигурации
sudo nft -c -f /etc/nftables.conf

Разрешение инициации трафика LAN → External в FORWARD

Пакет сначала проходит фильтрацию в цепочке forward, и только потом выполняется SNAT. Разрешаем новые соединения из LAN во внешнюю сеть 198.51.100.0/24

# Разрешаем прохождение новых сессий (ct state new) из подсети LAN в подсеть lab-internet
sudo nft add rule ip filter forward \
    ip saddr 10.10.10.0/24 \
    ip daddr 198.51.100.0/24 \
    ct state new \
    accept

Настройка таблицы NAT и цепочки postrouting

Создаём отдельную таблицу nat для IPv4 и цепочку postrouting, которая перехватывает пакеты перед их отправкой в сетевой интерфейс:

# Создаём таблицу для правил трансляции адресов
sudo nft add table ip nat

# Создаём цепочку postrouting с привязкой к хуку postrouting и приоритетом srcnat (100)
sudo nft 'add chain ip nat postrouting { type nat hook postrouting priority srcnat; policy accept; }'

Добавление правила SNAT

Добавляем правило, которое выполняет подмену исходного IP-адреса для всех пакетов из сети 10.10.10.0/24, уходящих через внешний интерфейс enp0s8:

# Выполняем SNAT (замену IP отправителя на 172.16.0.2) при выходе через интерфейс enp0s8
sudo nft add rule ip nat postrouting \
    oifname "enp0s8" \
    ip saddr 10.10.10.0/24 \
    ip daddr 198.51.100.0/24 \
    snat to 172.16.0.2

# Проверяем сформированные правила в таблице nat
sudo nft list table ip nat

Проверка работы с помощью tcpdump

Для проверки корректности подмены IP-адресов запускаем дамп сетевого трафика на виртуальной машине external-client и отправляем ICMP-пакеты с lan-workstation:

  • На external-client запускаем прослушивание ICMP-трафика:

    # Запускаем перехват ICMP-пакетов на интерфейсе enp0s8 без разрешения DNS-имён
    sudo tcpdump -ni enp0s8 icmp
  • На lan-workstation отправляем тестовый запрос:

    # Отправляем 3 ICMP-пакета на внешний адрес 198.51.100.10
    ping -c 3 198.51.100.10

В консоли external-client отображается, что входящие пакеты поступают с адреса 172.16.0.2 (внешний интерфейс шлюза), а не с реального приватного адреса 10.10.10.10:

11:10:50 IP 172.16.0.2 > 198.51.100.10: ICMP echo request
11:10:50 IP 198.51.100.10 > 172.16.0.2: ICMP echo reply

Сохраняем полную конфигурацию файрвола:

# Записываем полный действующий набор правил в файл /etc/nftables.conf
sudo nft list ruleset | sudo tee /etc/nftables.conf > /dev/null

# Выставляем корректные права доступа на файл
sudo chmod 600 /etc/nftables.conf

# Проверяем синтаксическую целостность итогового файла
sudo nft -c -f /etc/nftables.conf

# Перезапускаем службу для проверки корректности загрузки при старте
sudo systemctl restart nftables

❯ Этап 9: DNAT для External → DMZ

Схема DNAT. Источник: Авторская схема (Excalidraw)
Схема DNAT. Источник: Авторская схема (Excalidraw)

На этом этапе настраиваем DNAT, чтобы внешние клиенты из 198.51.100.0/24 могли обращаться к веб-серверу в DMZ (10.10.20.10:80) через шлюз linux-gateway.

Как пакет проходит через Netfilter:

  • PREROUTING — здесь выполняется DNAT: адрес назначения меняется до того, как ядро решит, куда маршрутизировать пакет.

  • FORWARD — здесь файрвол проверяет, можно ли пропустить пакет дальше. Важно: адрес назначения уже изменён DNAT.

  • POSTROUTING — здесь выполняется SNAT: адрес источника меняется перед отправкой пакета в интерфейс.

Настройка цепочки prerouting в таблице NAT

Подключаемся к главному шлюзу и создаём цепочку prerouting с привязкой к хуку prerouting и приоритетом dstnat (-100):

# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Создаём цепочку prerouting для обработки входящих пакетов до маршрутизации
sudo nft 'add chain ip nat prerouting { type nat hook prerouting priority dstnat; policy accept; }'

# Проверяем структуру таблицы nat
sudo nft list table ip nat

Добавление правила DNAT

Добавляем правило DNAT: входящий TCP-трафик на 80-й порт внешнего интерфейса enp0s8 перенаправляется на веб-сервер в DMZ (10.10.20.10:80).

# Разрешаем прохождение новых сессий (ct state new) с внешнего интерфейса enp0s8 на интерфейс DMZ enp0s10 к веб-серверу
sudo nft add rule ip filter forward \
    iifname "enp0s8" \
    oifname "enp0s10" \
    ip saddr 198.51.100.0/24 \
    ip daddr 10.10.20.10 \
    tcp dport 80 \
    ct state new \
    accept

Проверка работы DNAT и диагностика

С внешнего клиента обращаемся к адресу шлюза в сети lab-wan:

# Запрос с external-client на внешний адрес шлюза. Должна открыться стандартная страница Nginx.
vagrant ssh external-client -c 'curl --connect-timeout 5 http://172.16.0.2'

Убедимся, что адрес назначения действительно меняется. Запускаем tcpdump на двух интерфейсах шлюза в разных терминалах:

  • На внешнем интерфейсе (enp0s8):

    # Перехватываем HTTP-трафик на внешнем интерфейсе enp0s8
    sudo tcpdump -ni enp0s8 tcp port 80
  • На интерфейсе DMZ (enp0s10):

    # Смотрим HTTP-трафик на интерфейсе DMZ
    sudo tcpdump -ni enp0s10 tcp port 80

Отправляем запрос с external-client. В первом окне увидим пакет 198.51.100.10 -> 172.16.0.2:80, во втором — тот же пакет, но уже с адресом 198.51.100.10 -> 10.10.20.10:80. Это подтверждает, что DNAT работает: адрес назначения подменяется на внутренний IP веб-сервера.

Фиксируем набор правил:

# Сохраняем итоговый набор правил nftables в конфигурационный файл /etc/nftables.conf
sudo nft list ruleset | sudo tee /etc/nftables.conf > /dev/null

# Выставляем правильные права доступа на файл
sudo chmod 600 /etc/nftables.conf

# Проверяем синтаксис файла конфигурации
sudo nft -c -f /etc/nftables.conf

# Перезапускаем службу nftables для проверки загрузки правил при старте
sudo systemctl restart nftables

❯ Этап 10: Правила сегмента DMZ

На этом этапе настраиваем правила для трафика из DMZ. Работает принцип наименьших привилегий: разрешаем только то, что действительно нужно.

Что разрешено и что запрещено для DMZ

  • DMZ → LAN — запрещено.

  • DMZ → MGMT — запрещено.

  • DMZ → DNS (порт 53) — разрешено.

  • DMZ → External (порты 80, 443) — разрешено.

Отдельные запрещающие правила для LAN и MGMT писать не нужно: в цепочке FORWARD уже стоит политика drop, поэтому всё, что не разрешено явно, блокируется автоматически.

Разрешение доступа DMZ к локальному DNS-серверу

Веб-серверу в DMZ нужно уметь разрешать доменные имена через внутренний DNS (10.10.10.53). DNS работает и по UDP, и по TCP на 53-м порту, поэтому открываем оба протокола:

# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Разрешаем новые UDP-соединения из подсети DMZ к локальному DNS-серверу
sudo nft add rule ip filter forward \
    iifname "enp0s10" \
    oifname "enp0s9" \
    ip saddr 10.10.20.0/24 \
    ip daddr 10.10.10.53 \
    udp dport 53 \
    ct state new \
    accept

# Разрешаем новые TCP-соединения из подсети DMZ к локальному DNS-серверу
sudo nft add rule ip filter forward \
    iifname "enp0s10" \
    oifname "enp0s9" \
    ip saddr 10.10.20.0/24 \
    ip daddr 10.10.10.53 \
    tcp dport 53 \
    ct state new \
    accept

Разрешение доступа DMZ в внешний мир (HTTP/HTTPS)

Разрешаем серверам из DMZ обращаться во внешнюю сеть (198.51.100.0/24) по протоколам HTTP (порт 80) и HTTPS (порт 443):

# Разрешаем исходящий HTTP-трафик (порт 80) из DMZ во внешнюю сеть
sudo nft add rule ip filter forward \
    iifname "enp0s10" \
    oifname "enp0s8" \
    ip saddr 10.10.20.0/24 \
    ip daddr 198.51.100.0/24 \
    tcp dport 80 \
    ct state new \
    accept

# Разрешаем исходящий HTTPS-трафик (порт 443) из DMZ во внешнюю сеть
sudo nft add rule ip filter forward \
    iifname "enp0s10" \
    oifname "enp0s8" \
    ip saddr 10.10.20.0/24 \
    ip daddr 198.51.100.0/24 \
    tcp dport 443 \
    ct state new \
    accept

Тестирование изоляции и разрешенного доступа

Попытка достучаться из DMZ до рабочей станции в LAN должна приводить к таймауту:

# Проверка пинга из DMZ в LAN
vagrant ssh dmz-web-server -c 'ping -c 3 10.10.10.10'

# Проверка доступности SSH-порта на рабочей станции из DMZ
vagrant ssh dmz-web-server -c 'nc -vz -w 3 10.10.10.10 22'

Проверка разрешенного доступа (DMZ → External:80):

  • На external-client:

    # Запуск простого HTTP-сервера Python на порту 80
    sudo python3 -m http.server 80 --bind 198.51.100.10
  • С dmz-web-server:

    # Запрос к внешнему серверу из зоны DMZ
    curl http://198.51.100.10

Запрос должен успешно завершиться ответом от сервера.

Сохраняем конфигурацию через nft как делали ранее.

❯ Этап 11: Доступ из MGMT-сегмента

На этом этапе настраиваем правила для административной сети (10.10.30.0/24). Из неё должен быть полный доступ к внутренним сегментам, но обратный доступ в MGMT должен быть закрыт.

Что разрешено для MGMT

  • MGMT → Gateway — SSH и ICMP (уже настроено в цепочке INPUT).

  • MGMT → LAN — SSH (порт 22) и ICMP.

  • MGMT → DMZ — SSH (порт 22) и ICMP.

Новые соединения из LAN и DMZ в MGMT запрещены. Ответный трафик в рамках соединений, созданных из MGMT, разрешается через ct state established,related.

Разрешаем MGMT → LAN

Администраторы из 10.10.30.0/24 (интерфейс enp0s16) должны иметь доступ по SSH и ICMP ко всем хостам в LAN (10.10.10.0/24, интерфейс enp0s9):

# Подключаемся по SSH к главному шлюзу
vagrant ssh linux-gateway

# Разрешаем новые SSH-соединения (порт 22) из сети MGMT в подсеть LAN
sudo nft add rule ip filter forward \
    iifname "enp0s16" \
    oifname "enp0s9" \
    ip saddr 10.10.30.0/24 \
    ip daddr 10.10.10.0/24 \
    tcp dport 22 \
    ct state new \
    accept

# Разрешаем отправку ICMP Echo-Request (ping) из сети MGMT в подсеть LAN
sudo nft add rule ip filter forward \
    iifname "enp0s16" \
    oifname "enp0s9" \
    ip saddr 10.10.30.0/24 \
    ip daddr 10.10.10.0/24 \
    icmp type echo-request \
    ct state new \
    accept

Разрешение доступа MGMT → DMZ

Точно так же разрешаем административной станции доступ к серверам в DMZ (10.10.20.0/24, интерфейс enp0s10):

# Разрешаем новые SSH-соединения (порт 22) из сети MGMT в подсеть DMZ
sudo nft add rule ip filter forward \
    iifname "enp0s16" \
    oifname "enp0s10" \
    ip saddr 10.10.30.0/24 \
    ip daddr 10.10.20.0/24 \
    tcp dport 22 \
    ct state new \
    accept

# Разрешаем отправку ICMP Echo-Request (ping) из сети MGMT в подсеть DMZ
sudo nft add rule ip filter forward \
    iifname "enp0s16" \
    oifname "enp0s10" \
    ip saddr 10.10.30.0/24 \
    ip daddr 10.10.20.0/24 \
    icmp type echo-request \
    ct state new \
    accept

Сохраняем nft конфигурацию как это делали ранее.

❯ Этап 12: DNS-сервер в LAN

Лого dnsmasq. Источник
Лого dnsmasq. Источник

На этом этапе поднимаем DNS-сервер на lan-dns-server с помощью dnsmasq. Он будет разрешать локальные имена внутри стенда.

Установка dnsmasq

Подключаемся к выделенному серверу DNS-службы, обновляем списки пакетов и устанавливаем dnsmasq:

# Подключаемся по SSH к серверу внутренней DNS-службы
vagrant ssh lan-dns-server

# Обновляем индексы репозиториев apt
sudo apt update

# Устанавливаем легкий DNS-сервер dnsmasq в автоматическом режиме
sudo apt install -y dnsmasq

Конфигурация dnsmasq

Создаём конфигурационный файл для описания локальной зоны и параметров прослушивания сетевых интерфейсов:

# Создаём и заполняем файл конфигурации /etc/dnsmasq.d/lab.conf
sudo tee /etc/dnsmasq.d/lab.conf > /dev/null <<'EOF'
# Прослушивать запросы только на интерфейсе локальной сети (enp0s8)
interface=enp0s8

# Привязываться строго к указанному IP-адресу DNS-сервера
listen-address=10.10.10.53
bind-interfaces

# Использовать стандартный 53-й порт для DNS-запросов
port=53

# Игнорировать системный файл /etc/resolv.conf (автономный режим без редиректа во внешнюю сеть)
no-resolv

# Статические локальные записи для лаборатории
address=/web.lab/10.10.20.10
address=/gateway.lab/10.10.10.1
EOF

Примечание: Внешние DNS-серверы здесь не указаны специально — зона изолированная и работает только для внутренних нужд стенда.

Запускаем службу и проверяем работу:

# Перезапускаем службу dnsmasq и добавляем её в автозагрузку системы
sudo systemctl restart dnsmasq
sudo systemctl enable dnsmasq

# Проверяем текущий статус работы службы
sudo systemctl status dnsmasq --no-pager

# Проверяем, что служба успешно слушает 53-й порт (UDP/TCP)
sudo ss -lntup | grep ':53'

Выполняем тестовые запросы напрямую к созданному DNS-серверу с помощью dig:

# Запрос A-записи для домена web.lab
dig @10.10.10.53 web.lab +short
# Запрос A-записи для домена gateway.lab
dig @10.10.10.53 gateway.lab +short

Ожидаемые ответы: 10.10.20.10 для web.lab и 10.10.10.1 для gateway.lab.

Настройка клиента: dmz-web-server

Настраиваем веб-сервер в DMZ, чтобы он использовал локальный DNS-сервер (10.10.10.53) как основной:

# Подключаемся по SSH к веб-серверу в DMZ
vagrant ssh dmz-web-server

# Записываем конфигурацию Netplan с указанием nameserver
sudo tee /etc/netplan/60-lab.yaml > /dev/null <<'EOF'
network:
  version: 2 # Версия формата конфигурации Netplan

  ethernets:
    enp0s8:
      addresses:
        - 10.10.20.10/24 # IP-адрес веб-сервера в DMZ

      routes:
        - to: default
          via: 10.10.20.1 # Шлюз по умолчанию в сегменте DMZ
          metric: 50      # Приоритетная метрика перед DHCP

      nameservers:
        addresses:
          - 10.10.10.53 # IP-адрес нашего локального DNS-сервера в LAN
EOF

# Генерация и применение новых сетевых настроек
sudo netplan generate
sudo netplan apply

Проверяем конфигурацию резолвера на клиенте:

# Проверяем статус DNS-клиента systemd-resolved
resolvectl status enp0s8

В выводе параметра должны отображаться назначенные DNS сервера: 10.10.10.53.

Проверка резолвинга на клиенте и сквозных запросов

Проверяем разрешение имён и доступность по доменному имени с dmz-web-server:

# Запросы резолвинга через systemd-resolved
resolvectl query web.lab
resolvectl query gateway.lab

# Прямые запросы к DNS-серверу
dig @10.10.10.53 web.lab +short

# Проверка HTTP-запроса по доменному имени
curl http://web.lab

❯ Заключение

В рамках лабораторного стенда мы настроили статическую маршрутизацию, активировали IP forwarding, внедрили stateful-фильтрацию через conntrack, реализовали SNAT и DNAT, настроили правила безопасности для DMZ и сегмента управления (MGMT), а также развернули локальный DNS-сервер.

Полный исходный код проекта с готовыми конфигурационными файлами и пошаговыми инструкциями доступен в репозитории canntstand/network-tools-practice.

Если вам интересна тема сетевой архитектуры, администрирования Linux и DevOps, делюсь своими практическими заметками и опытом в Telegram-канале My Tech Notes.

Может быть интересно:
Перейти ↩

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram‑канале ↩