Задача - в целях защиты периметра от внешних атак необходимо на границе сети фильтровать входящий трафик с известных (враждебных) префиксов. Префиксов таких многие тысячи, поэтому фильтровать с помощью ACL не вариант. Емкость ACL на маршрутизаторах ограничена. Кроме того любая масштабная ручная настройка неудобна и чревата ошибками. Особенно когда маршрутизаторов больше одного.
Решение - использовать технику защиты от ДДоС под названием: Remote Triggered Black Hole Filtering with Unicast Reverse Path Forwarding (uRPF) или S/RTBH
Эта техника описанна в RFC 5635
Суть техники следующая:
Нам нужен отдельный BGP демон, в RIB которого мы будем помещать нужные префиксы, а префиксы эти он будет анонсировать граничным маршрутизаторам с определенным next-hop-ом. На маршрутизаторах необходимо сделать всего две настройки:
- сеть в этот next-hop смаршрутизировать в Null
- на внешних интерфейсах включить uRPF в режиме loose. Таким образом будут отброшены входящие пакеты с враждебных префиксов
В качестве отдельного BGP демона выступает open-source продукт goBGP
Это надежный быстрый легковесный демон с развитой функциональностью. А главное - есть пограммный интерфейс (через grpc, в том числе и для python) и он не сказать что просто, но при должном усилии интегрируется в любой пограммный продукт. Собственно такой продукт здесь и представлен - это веб-приложение с рабочим названием SDBGP.
Приложение состоит из бекенда на python с использованием библиотеки FastAPI и базы данных Mongo, а так же фронтенда написанного на React. Такой стек достаточно популярен и даже имеет собственный акроним FARM (FastApi + React + Mongo)
Стоит сказать, что ровно в такую же схему решения вписывается более подвинутая техника защиты от ДДоС под названием BGP Flowspec rfc5575. И конечно же goBGP эту продвинутую технологию поддерживает и поэтому Flowspec тоже реализован в приложении
При этом в goBGP видим такую картину
$ docker exec -it sdbgp_gobgp_1 gobgp global rib | grep 101.7
*> 101.71.121.196/32 0.0.0.0 05:31:34 [{Origin: ?}]
*> 101.74.239.6/32 0.0.0.0 05:31:34 [{Origin: ?}]
*> 101.75.120.22/32 0.0.0.0 05:31:34 [{Origin: ?}]
и в маршрутизаторе
c4331-test#sh ip bgp | i 101.7
*>i 101.71.121.196/32
*>i 101.74.239.6/32 172.20.0.2 100 0 ?
*>i 101.75.120.22/32 172.20.0.2 100 0 ?
Заметили, что next-hop вместо 0.0.0.0 стал 172.20.0.2?
Об этом отдельная заметка в примечании
Сверху две вкладки:
- BGP Unicast - вариант защиты S/RTBH
- BGP Flowspec - продвинутый вариант защиты FlowSpec
Название вкладок происходит от используемых Address Family (AFI). При желании можно будет дописать сюда поддержку и других AFI например ipv6-uniсast и ipv6-flowspec-unicast
Дальше идет поле фильтрации префиксов.
фильтрация происходит в вашем браузере и когда префиксов становится несколько тысяч это становится заметно. поэтому поле имеет ограничение в 5000 префиксов после которого поле становится неактивным. ограничение можно задать переменонй окружения SEARCH_FIELD_LIMIT
Затем как видно идет таблица префиксов с кнопками редактирования и удаления для каждого префикса. Ну в общем тут все должно быть понятно.
Далее два ряда кнопок
- Кнопки управления состоянием в приложении
- Кнопки управления состоянием в RIB BGP демона
Проще развернуть и потренироваться чтоб понять что к чему
Приложение использует следующие компоненты:
- GoBGP - собственно bgp демон
- MongoDB - промежуточное хранение префиксов
- Python3 - бекенд приложения
- React - фронтенд приложения
GoBGP должен быть установлен и доведен до рабочего сотстояния.
Есть в стандартных линукс дистрибутивах, на гитхабе, докерхабе итд.
Для ubuntu достаточно такого
$ sudo apt install gobgpd
$ cat /etc/gobgpd.conf
[global.config]
as = 65100
router-id = "192.168.255.1"
$ sudo systemctl start gobgpd
Приложение использует protobuf файлы, которые могут отличаться в разных версиях goBgp
В репозитории уже есть эти файлы для версии goBgp 2.12.0 (версия по цмолчанию Ubuntu 20.4 LTS)
Но если ваша версия goBgp отличается, то возможно приложение не зарабоает как надо и будет необходимо получить протобуфы именно для вашей версии
Делается это следующим образом
-
выяснить текущую установленную версию goBgp
$ gobgp --version
gobgp version 2.12.0 -
Зайти в gobgp репозиторий найти там подходящий релиз и получить хеш релиза (a4b688a)
-
склонировать репозитарий
$ git clone https://github.com/osrg/gobgp.git gobgp_repo
-
перейти в репозитарий и получить слепок релиза
$ cd gobgp_repo
$ git checkout a4b688a -
скопировать *proto файлы в рабочий каталог
$ cd ..
$ cp gobgp_repo/api/*proto . -
из полученных protobuf файлов сгенерировать python-библиотеки
$ python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. *.proto
-
убедиться что появились/обновились файлы *.py соответствующие по названиям файлам *.proto:
gobgp_pb2.py, gobgp_grpc_pb2.py, attribute_pb2.py, attribute_pb2_grpc.py *.proto файлы нам больше не понадобятся
> apt install mongo //или как то так
Я не сильно знаком с монго, возможно, нужно заранее зайти и обозначить используемую в проекте базу данных sdbgp
> $ mongo
> use sdbgp
> exit
Бекенд реализован на FastAPI, и использует ряд модулей для работы с mongo и grpc
Окружение создается как обычно:
> $ cd backend
> $ python3 -m venv venv
> $ source venv/bin/activate
> $ pip3 install -r requirements.txt
Далее необходимо скопировать config.py.example в config.py
и наверное имеет смысл заглянуть внутрь config.py и возможно подправить некоторые параметры
> cp config.py.example config.py
теперь можно запускать
> $ uvicorn routes:app
> $ cd frontend
> $ npm install
тут так же как и в случае с backend есть свой файл конфигурации src/config.js
но на данном этапе скорей всего его править не придется
> $ npm start
На этом шаге через некоторое время должен стартовать браузер и в отдельной его вкладке загрузиться приложение. Теперь мы можем добавлять свои префиксы или политики, сохранять их в базу, а из базы отправлять из в RIB демона goBGP
Для того чтобы увидеть состояние RIB пригодится пара CLI команд:
> gobgp global rib
> gobgp global rib -a ipv4-flowspec
Так же из командной строки можно инжектить префикcы или политики flowspec сразу в RIB:
> gobgp global rib add 1.2.3.56/32 nexthop 0.0.0.0 > gobgp global rib -a ipv4-flowspec add match source 3.3.3.3/30 destination 5.5.5.5/32 protocol '==tcp&==udp' destination-port '80,443' then discard
подробней в мануале goBGP
Описанный здесь процесс развернет приложение на localhost. Это удобно для разработки, но скорей всего не подойдет для прода.
Не знаю как в других дистрибуливах, но в Убунту нет возможности получить актуальную версию goBGP. Для версии Ubuntu 20.04LTS в пакетах идет достаточно старая 2.12, а для 18.04LTS идет совсем древняя версия 1.хх.
Поэтому возникают некоторые сложности на не самых свежих системах.
Здесь на помощь приходит докер.
Для моих экспериментов я выбрал эту сборку https://hub.docker.com/r/jauderho/gobgp
Причем так как я изначально в этом проекте начинал работать с версией goBGP 2.12, то не стал переходить на 3-ю ветку и взял последнюю версию из 2-й ветки - 2.34.
И версия поновее и протобуфы оказались совместимы
Изначально я не планировал докеризировать все приложение, а начинал использовать докер для того чтобы собрать виртуальную лабу с виртуальным же роутером.
И об этой лабе есть отдельная статья:
Пример использования приложения в среде docker
Затем, когда приложение более менее выросло, стало понятно, что разворачивать его на проде та еще задача.
Заводить приложение на выделенный для него сервер как-то расточительно, а на существующий продовый сервер придется затащить кучу каких то библиотек, модулей, зависимостей. Под публикацию приложения надо будет перенастраивать существующий веб-сервер. А при удалении приложения хорошо было бы все вернуть обратно. И стабильность продового сервера в этот момент под вопросом. Работа достаточно скрупулезная, и нужна она ровно один раз потому что каждый следующий деплой скорее всего будет не похож на предыдущий.
Вот для таких сценариев Docker, а точнее docker-compose подходят как нельзя лучше.
Пришлось немного (пару дней) повозиться, но докеризацию осилил и теперь считаю этот метод деплоя предпочтительным. О том как разворачивать приложение с помощью docker-compose в отдельной статье Deploy with Docker
конфигурация gobgp хранится в gobgp_config/gobgp.toml
[global.config]
as = 65100
router-id = "1.1.1.1"
[[neighbors]]
[neighbors.config]
neighbor-address = "10.2.0.123"
peer-as = 65100
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-flowspec"
[[neighbors.afi-safis]]
[neighbors.afi-safis.config]
afi-safi-name = "ipv4-unicast"
[[defined-sets.prefix-sets]]
prefix-set-name = "px-all"
[[defined-sets.prefix-sets.prefix-list]]
ip-prefix = "0.0.0.0/0"
masklength-range = "1..32"
[[defined-sets.neighbor-sets]]
neighbor-set-name = "ext-neigh"
neighbor-info-list = ["10.0.0.0/8"]
[[policy-definitions]]
name = "policy-reject-all-from-ext-neigh"
[[policy-definitions.statements]]
name = "statement1"
[policy-definitions.statements.conditions.match-prefix-set]
prefix-set = "px-all"
match-set-options = "any"
[policy-definitions.statements.conditions.match-neighbor-set]
neighbor-set = "ext-neigh"
match-set-options = "any"
[policy-definitions.statements.actions]
route-disposition = "reject-route"
[global.apply-policy.config]
import-policy-list = ["policy-reject-all-from-ext-neigh"]
default-import-policy = "accept-route"
default-export-policy = "accept-route"
основной конфиг содержится в первых 14 строках сюда входит параметры global-config и прописывание neighbors и две address-family (AFI)
А начиная с 15-й строчки идет простая по сути, но многословная по форме policy, которая запрещает получать префиксы от BGP соседей. Полиси необходима из-за тогоу что в проде соседями у нас выступают реальные интернет бордеры. А в RIB каждого такого бордера находится под миллион префиксов (FullView). И нам нет никакого смысла принимать все эти маршруты
В стандартном network типа bridge в docker при соединении с внешними сетями происходит трансляция в адрес интерфейса хоста. При этом конечно внутри BGP сессии параметры никак не видоизменяются и атрибут next-hop приходит с внутренним адресом контейнера.
Но для нашей задачи это даже несет некоторое преимущество. Мы можем оставить для наших анонсируемых префиксов значение next-hop по умолчанию (0.0.0.0) и нам не нужно заботиться, о том, что трафик на эти префиксы польется в сторону демона. Более того мы зная адресацию docker network (172.20.0.0/16) можем заранее заблекхолить эту сеть на маршрутизаторах и сразу получить желаемое поведение
- сделать авторизацию (генерация и проверка jwt на беке готова)
- сделать логи аудита - кто (авторизация) когда чего нажал. поместить в отдельную вкладку
- сгенерировать help.md загрузить в вкладку help