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

Введение


Под сетевой зоной я подразумеваю совокупность интерфейсов или 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")
				}
			}
		}
	}
}


Заключение


Данный скрипт мне помог сильно облегчить настройку сложных политик, однако, ошибки в нем все-же могут быть. Перед применением проконсультируйтесь со специалистом. В случае обнаружения побочных явлений, пишите, будем исправлять.