Кто хоть раз писал политику фильрации firewall знает, что это дело не простое и сопряжено с кучей ошибок, когда колличество сетевых зон больше 2-х. В этой сутации вам поможет скрипт из этой статьи.
Под сетевой зоной я подразумеваю совокупность интерфейсов или IP адресов, для которых применяется правило фильтрации. В моем скрипте IP адреса зоны закреплены за интерфейсом. То есть, если мы ожидаем «доверенные» IP адреса из локальной сети, то будет странно, если соответствующие соединения прилетят к нам от провайдера.
Все вертится вокруг описания зон, где мы задаем как они между собой взаимодействуют.
В моей конфигурации, если не заданы другие правила, весь трафик будет сброшен с правилом DROP, поэтому мы будем задавать правила ACCEPT и REJECT.
Далее скрипт в цикле проходит по всем описаниям зон и создает:
IP зона назначения трафика от интерфейсной отличается тем, что её имени нет в разделе для интерфейсов. Таким образом скрипт и понимает, где у нас назначение ip, а где интерфейс.
Само описание разделено на два блока: интерфейсы и ip адреса. Для интерфейсов есть специальные переменные:
Так-же есть две специальные зоны: rt, это сам роутер, и all, как не трудно догадаться «любое направление», all не может быть указан в качестве источника трафика.
Основное действие скрипта, это создание серии правил jump с фильтрацией по источнику и назначению трафика, которые терминируются правилом с ACCEPT или REJECT.
Описание зоны может быть редуцированно, как в примере для ISP и rt (должно быть всегда).
Если вы хотите терминировать цепочку своим правилом, то в описание зоны просто вместо accept или reject впишите custom (или что угодно), тогда цепочка jump-ов не будет завершена никаким правилом, а вы сможете создать его самостоятельно.
Задано 4-е зоны для интерфейсов, одна из которых, обязательная rt:
B 3-и ip зоны:
Данный скрипт мне помог сильно облегчить настройку сложных политик, однако, ошибки в нем все-же могут быть. Перед применением проконсультируйтесь со специалистом. В случае обнаружения побочных явлений, пишите, будем исправлять.
Введение
Под сетевой зоной я подразумеваю совокупность интерфейсов или IP адресов, для которых применяется правило фильтрации. В моем скрипте IP адреса зоны закреплены за интерфейсом. То есть, если мы ожидаем «доверенные» IP адреса из локальной сети, то будет странно, если соответствующие соединения прилетят к нам от провайдера.
Как оно работает
Все вертится вокруг описания зон, где мы задаем как они между собой взаимодействуют.
В моей конфигурации, если не заданы другие правила, весь трафик будет сброшен с правилом DROP, поэтому мы будем задавать правила ACCEPT и REJECT.
Далее скрипт в цикле проходит по всем описаниям зон и создает:
- Списки интерфейсов IF-<имя зоны>
- Списки адресов IP-<имя зоны>
- Задает дефолтовую и немного мной дополненую конфигурацию filter, mangle и raw
IP зона назначения трафика от интерфейсной отличается тем, что её имени нет в разделе для интерфейсов. Таким образом скрипт и понимает, где у нас назначение ip, а где интерфейс.
Переменные
- gping — Разрешить ping во всех направлениях и на пересылке. Иными словами, пинговать можно будет даже то, что было закрыто другими правилами в filter. Включать по желанию ;)
- debug — Ставит в самое начало корневых цепочек ACCEPT правило, что делает все остальные правила бесполезными. Очень полезно на этапе отладки правил. Делаете политику как вы хотите, включаете Safe Mode, а потом эти правила отключаете. Если роутер стал недоступен, то уже через 10 сек правила будут включены опять и вы сможете подумать, а в чем вы ошиблись?
Описание зон
Само описание разделено на два блока: интерфейсы и ip адреса. Для интерфейсов есть специальные переменные:
- is_wan: добавляет правила для обработки тунелей и DNS
- do_masq: добавляет правила для маскардинга трафика в эту зону
- is_lan: добавляет правила для ответа на DHCP и DNS
- mss: добавляет правила корректировки mss для туннельных интерфейсов, если встроенные средства вас не устраивают
Так-же есть две специальные зоны: rt, это сам роутер, и all, как не трудно догадаться «любое направление», all не может быть указан в качестве источника трафика.
Основное действие скрипта, это создание серии правил jump с фильтрацией по источнику и назначению трафика, которые терминируются правилом с ACCEPT или REJECT.
Описание зоны может быть редуцированно, как в примере для ISP и rt (должно быть всегда).
Если вы хотите терминировать цепочку своим правилом, то в описание зоны просто вместо accept или reject впишите custom (или что угодно), тогда цепочка jump-ов не будет завершена никаким правилом, а вы сможете создать его самостоятельно.
Пояснение правил из скрипта
Зоны
:local gping 1
:local debug 0
:local zones {
"if"={
"ISP"={
"is_wan"=1;
};
"LAN"={
"is_lan"=1;
policy={
"all"="accept";
};
};
"TUN"={
mss=1400;
};
"rt"={};
};
"ip"={
"rt"={
policy={
"all"="accept"
};
};
"TUN"={
"Staff"={
"Server"="accept";
};
"Manager"={
"all"="accept";
};
};
"ISP"={
"Trusted"={
"rt"="accept";
};
};
};
}
Задано 4-е зоны для интерфейсов, одна из которых, обязательная rt:
- ISP — для провайдера, поэтому указано создавать дополнительные правила для некоторых протоколов.
- LAN — для локальной сети, ей заданы правила для lan и есть общее разрешающие действие на любые направления.
- TUN — для собственно тунелей, указана корректировка MSS.
B 3-и ip зоны:
- Staff и Manager на интерфейсах TUN:
- Staff — имеет доступ только к ip зоне Server
- Manager — могут куда угодно
- Trusted на интерфейсе ISP имеет доступ к роутеру
gen-filter
# may/01/2020 10:00:00 by RouterOS 6.46.6
# RoS filter generator v 0.9.4
:local gping 0
:local debug 1
:local zones {
"if"={
"ISP"={
"is_wan"=1;
};
"LAN"={
"is_lan"=1;
policy={
"all"="accept";
};
};
"TUN"={
mss=1400;
};
"rt"={};
};
"ip"={
"rt"={
policy={
"all"="accept"
};
};
"TUN"={
"Staff"={
"Server"="accept";
};
"Manager"={
"all"="accept";
};
};
"ISP"={
"Trusted"={
"rt"="accept";
};
};
};
}
/ip firewall raw
add action=notrack chain=prerouting ipsec-policy=in,ipsec comment="Notrack ipsec"
add action=notrack chain=prerouting dst-address-type=multicast comment="Notrack multicast"
/ip firewall filter
:if ( ($debug)=0 ) do={
add action=accept chain=forward comment=DEBUG!!! disabled=yes
add action=accept chain=input comment=DEBUG!!! disabled=yes
add action=accept chain=output comment=DEBUG!!! disabled=yes
} else={
add action=accept chain=forward comment=DEBUG!!!
add action=accept chain=input comment=DEBUG!!!
add action=accept chain=output comment=DEBUG!!!
}
add action=accept chain=input comment="defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1 dst-port=5246,5247 protocol=udp src-address-type=local
add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
add action=jump chain=input comment="defconf: new input" connection-state=new jump-target=in-new
add action=jump chain=input comment="defconf: notrack input" jump-target=in-notrack
add action=drop chain=input comment="defconf: drop all not allowed"
add action=accept chain=output comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=jump chain=output comment="defconf: new output" connection-state=new jump-target=out-new
add action=jump chain=output comment="defconf: notrack output" jump-target=out-notrack
add action=drop chain=output comment="defconf: drop all not allowed"
add action=accept chain=forward comment="defconf: accept in ipsec policy" ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-mark=no-mark connection-state=established,related
add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
add action=jump chain=forward comment="defconf: new forward" connection-state=new jump-target=fw-new
add action=jump chain=forward comment="defconf: notrack forward" jump-target=fw-notrack
add action=accept chain=forward comment="defconf: Accept all forward DSTNATed" connection-nat-state=dstnat
add action=drop chain=forward comment="defconf: drop all not allowed for forward"
:if ( ($gping)=1 ) do={
add action=accept chain=WAN2RT-STD-PROTO comment=ICMP protocol=icmp disabled=yes
} else={
add action=accept chain=WAN2RT-STD-PROTO comment=ICMP protocol=icmp
}
add action=accept chain=WAN2RT-STD-PROTO comment=GRE ipsec-policy=in,ipsec protocol=gre
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-esp
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-ah
add action=accept chain=WAN2RT-STD-PROTO comment="IPSec encapsulated" dst-port=500,4500 protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=L2TP dst-port=1701 ipsec-policy=in,ipsec protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=PPtP dst-port=1723 protocol=tcp
add action=accept chain=LAN2RT-STD-PROTO comment=DNS dst-port=53 protocol=tcp
add action=accept chain=LAN2RT-STD-PROTO comment=NTP,DNS,DHCP dst-port=53,123,67-68 protocol=udp
add action=accept chain=LAN2RT-STD-PROTO comment=DHCP dst-port=67-68 protocol=udp
add action=accept chain=RT2WAN-STD-PROTO comment=DNS dst-port=53 protocol=tcp
add action=accept chain=RT2WAN-STD-PROTO comment=NTP,DNS dst-port=53,123 protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-esp
add action=accept chain=RT2WAN-STD-PROTO comment=IPSec protocol=ipsec-ah
add action=accept chain=RT2WAN-STD-PROTO comment="IPSec encapsulated" dst-port=500,4500 protocol=udp
add action=reject chain=RT2WAN-STD-PROTO comment=GRE ipsec-policy=out,none protocol=gre reject-with=icmp-admin-prohibited
add action=reject chain=RT2WAN-STD-PROTO comment=L2TP dst-port=1701 ipsec-policy=out,none protocol=udp reject-with=icmp-admin-prohibited
:if ( ($gping)=1 ) do={
add action=accept chain=fw-new comment=ICMP protocol=icmp
add action=accept chain=in-new comment=ICMP protocol=icmp
add action=accept chain=out-new comment=ICMP protocol=icmp
} else={
add action=accept chain=fw-new comment=ICMP protocol=icmp disabled=yes
add action=accept chain=in-new comment=ICMP protocol=icmp disabled=yes
add action=accept chain=out-new comment=ICMP protocol=icmp disabled=yes
}
/ip firewall filter
:foreach zone,conf in=($zones->"if") do={
:if ( ($zone)!="rt" ) do={
:if ( [/interface list print count-only where name=("IF-".$zone)] = 0) do={
/interface list add name=("IF-".$zone)
}
add action=jump chain=fw-new in-interface-list=("IF-".$zone) comment=("Fwd plc from if ".$zone) jump-target=("fw-plc-s:".$zone)
add action=jump chain=in-new in-interface-list=("IF-".$zone) comment=("In plc for if ".$zone) jump-target=("in-plc-s:".$zone)
} else={
add action=jump chain=out-new comment=("Out plc for rt") jump-target="out-plc-s:rt"
}
}
:foreach zone,conf in=($zones->"if") do={
:if ( ($zone)!="rt" && ($zone)!="all") do={
:if ( ($conf->"is_lan")=1 || ($conf->"is_wan")=1 ) do={
add action=jump chain=in-notrack comment=("In Allow plc for STD LAN PROTO from ".$zone) in-interface-list=("IF-".$zone) jump-target=LAN2RT-STD-PROTO
}
:if ( ($conf->"is_wan")=1) do={
add action=jump chain=in-notrack comment=("In Allow plc for STD WAN PROTO from ".$zone) in-interface-list=("IF-".$zone) jump-target=WAN2RT-STD-PROTO
add action=jump chain=out-notrack out-interface-list=("IF-".$zone) comment=("Out Allow plc for STD WAN PROTO to ".$zone) jump-target=RT2WAN-STD-PROTO
}
:if ( ($conf->"do_masq")=1) do={
/ip firewall nat add action=masquerade chain=srcnat out-interface-list=("IF-".$zone) comment=("Masquerade traffic going to ".$zone)
}
:if ( [:len ($conf->"mss")]!=0 ) do={
/ip firewall mangle add action=change-mss chain=forward in-interface-list=("IF-".$zone) new-mss=($conf->"mss") passthrough=yes protocol=tcp tcp-flags=syn tcp-mss=(($conf->"mss"+1)."-65535") comment=("Fix mss on tunel ".$zone)
/ip firewall mangle add action=change-mss chain=forward out-interface-list=("IF-".$zone) new-mss=($conf->"mss") passthrough=yes protocol=tcp tcp-flags=syn tcp-mss=(($conf->"mss"+1)."-65535") comment=("Fix mss on tunel ".$zone)
}
:foreach src,val in=(($zones->"ip")->$"zone") do={
:foreach tgt,policy in=$val do={
:if ( ($tgt)!="rt" && ($tgt)!="all") do={
:if ( [:len (($zones->"if")->$"tgt")]=0 ) do={
add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) dst-address-list=("IP-".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." to ".$tgt) jump-target=("fw-plc-s:".$zone."&".$src.">".$tgt)
} else={
add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) out-interface-list=("IF-".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." to ".$tgt) jump-target=("fw-plc-s:".$zone."&".$src.">".$tgt)
}
:if ( ($policy)="accept") do={
add action=accept chain=("fw-plc-s:".$zone."&".$src.">".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." Accept to ".$tgt)
}
:if ( ($policy)="reject") do={
add action=reject chain=("fw-plc-s:".$zone."&".$src.">".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." Reject to ".$tgt)
}
}
:if ( ($tgt)="rt" ) do={
add action=jump chain=("in-plc-s:".$zone) src-address-list=("IP-".$src) comment=("In plc for if ".$zone." & ip ".$src." to rt") jump-target=("in-plc-s:".$zone."&".$src.">rt")
:if ( ($policy)="accept") do={
add action=accept chain=("in-plc-s:".$zone."&".$src.">rt") comment=("In plc for if ".$zone." & ip ".$src." Accept to rt")
}
:if ( ($policy)="reject") do={
add action=reject chain=("in-plc-s:".$zone."&".$src.">rt") comment=("In plc for if ".$zone." & ip ".$src." Accept to rt")
}
}
:if ( ($tgt)="all" ) do={
add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) comment=("Fwd plc from if ".$zone." & ip ".$src." to All") jump-target=("fw-plc-s:".$zone."&".$src.">all")
add action=jump chain=("in-plc-s:".$zone) src-address-list=("IP-".$src) comment=("In plc for if ".$zone." & ip ".$src." to All") jump-target=("in-plc-s:".$zone."&".$src.">all")
:if ( ($policy)="accept") do={
add action=accept chain=("fw-plc-s:".$zone."&".$src.">all") comment=("Fwd plc from if ".$zone." & ip ".$src." Accept to All")
add action=accept chain=("in-plc-s:".$zone."&".$src.">all") comment=("In plc from if ".$zone." & ip ".$src." Accept to All")
}
:if ( ($policy)="reject") do={
add action=reject chain=("fw-plc-s:".$zone."&".$src.">all") comment=("Fwd plc from if ".$zone." & ip ".$src." Reject to All")
add action=reject chain=("in-plc-s:".$zone."&".$src.">all") comment=("In plc from if ".$zone." & ip ".$src." Reject to All")
}
}
}
}
:foreach tgt,policy in=($conf->"policy") do={
:if ( ($tgt)!="rt" && ($tgt)!="all") do={
:if ( [:len (($zones->"if")->$"tgt")]=0 ) do={
add action=jump chain=("fw-plc-s:".$zone) dst-address-list=("IP-".$tgt) comment=("Fwd plc from if ".$zone." to ".$tgt) jump-target=("fw-plc-s:".$zone.">".$tgt)
} else={
add action=jump chain=("fw-plc-s:".$zone) out-interface-list=("IF-".$tgt) comment=("Fwd plc from if ".$zone." to ".$tgt) jump-target=("fw-plc-s:".$zone.">".$tgt)
}
:if ( ($policy)="accept") do={
add action=accept chain=("fw-plc-s:".$zone.">".$tgt) comment=("Fwd plc from if ".$zone." Accept to ".$tgt)
}
:if ( ($policy)="reject") do={
add action=reject chain=("fw-plc-s:".$zone.">".$tgt) comment=("Fwd plc from if ".$zone." Reject to ".$tgt)
}
}
:if ( ($tgt)="rt" ) do={
:if ( ($conf->"is_lan")!=1 && ($conf->"is_wan")!=1 ) do={
add action=jump chain=("in-plc-s:".$zone) comment=("In plc for if ".$zone." to rt") jump-target=("in-plc-s:".$zone.">rt")
}
:if ( ($policy)="accept") do={
add action=accept chain=("in-plc-s:".$zone.">rt") comment=("In plc for if ".$zone." Accept to rt")
}
:if ( ($policy)="reject") do={
add action=reject chain=("in-plc-s:".$zone.">rt") comment=("In plc for if ".$zone." Reject to rt")
}
}
:if ( ($tgt)="all" ) do={
add action=jump chain=("fw-plc-s:".$zone) comment=("Fwd plc from if ".$zone." to All") jump-target=("fw-plc-s:".$zone.">all")
add action=jump chain=("in-plc-s:".$zone) comment=("In plc for if ".$zone." to All") jump-target=("in-plc-s:".$zone.">all")
:if ( ($policy)="accept") do={
add action=accept chain=("fw-plc-s:".$zone.">all") comment=("Fwd plc from if ".$zone." Accept to All")
add action=accept chain=("in-plc-s:".$zone.">all") comment=("In plc from if ".$zone." Accept to All")
}
:if ( ($policy)="reject") do={
add action=reject chain=("fw-plc-s:".$zone.">all") comment=("Fwd plc from if ".$zone." Reject to All")
add action=reject chain=("in-plc-s:".$zone.">all") comment=("In plc from if ".$zone." Reject to All")
}
}
}
}
}
Заключение
Данный скрипт мне помог сильно облегчить настройку сложных политик, однако, ошибки в нем все-же могут быть. Перед применением проконсультируйтесь со специалистом. В случае обнаружения побочных явлений, пишите, будем исправлять.
Grey4ip
Беглый просмотр скрипта выявил:
В нескольких местах, условие на reject, действие accept. Почему action=accept?
Было бы хорошо добавить в статью результат (экспорт добавленных правил) выполнения скрипта с несколькими зонами, включая гостевую зону (в/из которой запрещены соединения из/в LAN зону)
MagicGTS Автор
Да, подлая copy-paste ошибка при рефакторинге.
Предложите вариант конфигурации, я её сгенерирую, ниже для текущей конфигурации.
Grey4ip
Что-то я не заметил в конце цепочек, в которые происходит jump, чтобы было правило с дропом всего, что не соответствует ни одному правилу цепочки.
В конце любой цепочки должен быть дроп или возврат (если нужен возврат в основную цепочку).
MagicGTS Автор
Это происходит как «возврат» в корневую, там и происходит DROP.
Grey4ip
Может я что-то путаю, но я не вижу return.
MagicGTS Автор
Обработка правил идет до тех пор, пока пакет не терминируется на любом правиле типа ACCEPT, REJECT, DROP. И если в вашем фильтре нет терминирующего корневого правила, к примеру в цепочке forwad (для транзитного трафика), то действие для него будет accept. Для любой не корневой цепочки, если в ней нет больше правил для обработки, и пакет не подошел, срабатывает действие return, и обработка продолжается со следующего правила от jump.
На скрине пояснение для forward:
17 — прыгаем в дебри фильтрации, если пакет там не «застрял», то он всплывет и попадет на 19 — ое правило.
Grey4ip
Понятно. Не знал, что в пользовательской цепочке действие по завершению — return. Но я бы добавил в конце пользовательских цепочек drop (если нет необходимости в обработке другими правилами в базовых цепочках)
MagicGTS Автор
А тут вопрос. Скрипт делает каркас, в любое место которого можно вставить свою цепочку или правило. Если все дропать как вы предлагаете, то это не решит ничего кроме добавления большого числа правил, которые по сути не нужны и создаст лишню работу при кастомизации.
Grey4ip
Правило для маскарадинга (srcnat) вручную добавляете для out-interface-list=IF-ISP?
MagicGTS Автор
Да, маскардинг данным скриптом не создается совсем.
Когда я об этом думал, я так и не решил, стоит ли его делать, потому и не сделал.
Если можете хорошо обосновать, нужно ли это делать, то я добавлю в скрипт, благо это не сложно.
Grey4ip
Спасибо, что поделились скриптом.
Раз определена зона isp и указано «is_wan»=1, то можно и правило маскарадинга добавлять для неё, чтобы сразу был доступен интернет для lan зоны. Или определить ещё переменную типа do_msqrd.
MagicGTS Автор
Вот я не смог прийти к однозначному мнению на это счёт.
Скрипт может быть адаптирован под IPv6, да и не у всех на IPv4 должен быть NAT (хотя сколько таких осталось)?
И если уж добавлять то, что не связано с фильтрацией (уже есть немного), то что ещё добавить?
И конечно объем тестирования скрипта пока довольно мал, будьте внимательны при использовании ;)
MagicGTS Автор
Развернутое тестирование помогло, не без боли, выявить ряд проблем в генерируемых политиках и спрятанных ошибках. Актуальная версия 0.9.4.
Добавил генерацию маскардинга.
!!! Если вы используете рекурсивные проверки маршрутов, к примеру для dualwan, то обязательно разрешите пинги от руотера в интернет. Без этого у вас интернет быстро пропадет.