Initial commit, squashed
This commit is contained in:
commit
158e8740b8
83 changed files with 2718 additions and 0 deletions
50
roles/firewall/templates/conntrackd.conf.j2
Normal file
50
roles/firewall/templates/conntrackd.conf.j2
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% set fw = inventory_hostname.split('-')[1]|int -%}
|
||||
|
||||
Sync {
|
||||
Mode FTFW {
|
||||
# Add received rules immediately so we don’t need a
|
||||
# signal on failover.
|
||||
DisableExternalCache On
|
||||
}
|
||||
|
||||
UDP {
|
||||
Interface {{ iface_sync }}
|
||||
IPv6_address fe80::{{ fw }}
|
||||
IPv6_Destination_Address fe80::{{ 2 if fw == 1 else 1 }}
|
||||
Port 3780
|
||||
|
||||
# Recommended by manual.
|
||||
Checksum on
|
||||
RcvSocketBuffer 1249280
|
||||
SndSocketBuffer 1249280
|
||||
}
|
||||
|
||||
#Options {
|
||||
# TCPWindowTracking Off
|
||||
#}
|
||||
}
|
||||
|
||||
General {
|
||||
UNIX {
|
||||
Path /var/run/conntrackd.ctl
|
||||
}
|
||||
Syslog on
|
||||
|
||||
# Recommended by manual.
|
||||
HashLimit 524288
|
||||
NetlinkBufferSize 2097152
|
||||
NetlinkBufferSizeMaxGrowth 8388608
|
||||
|
||||
Filter From Kernelspace {
|
||||
# Don’t replicate rules for traffic from/to firewall.
|
||||
Address Ignore {
|
||||
IPv4_address 127.0.0.1/8
|
||||
IPv6_address ::1/128
|
||||
IPv6_address fe80::/64 # link-local addresses
|
||||
IPv4_address {{ wg_ip }}
|
||||
{% for address in interfaces | map(attribute='ip_addresses') | flatten | sort(attribute='address') %}
|
||||
IPv{{ address.family.value }}_address {{ address.address }}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
}
|
12
roles/firewall/templates/fabric.intf.j2
Normal file
12
roles/firewall/templates/fabric.intf.j2
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% for iface in interfaces | selectattr('name', 'match', '^lan') | map(attribute='name') %}
|
||||
auto {{ iface }}
|
||||
iface {{ iface }}
|
||||
mtu 9216
|
||||
|
||||
auto {{ iface }}.2
|
||||
iface {{ iface }}.2
|
||||
|
||||
auto {{ iface }}.4
|
||||
iface {{ iface }}.4
|
||||
|
||||
{% endfor %}
|
141
roles/firewall/templates/frr.conf.j2
Normal file
141
roles/firewall/templates/frr.conf.j2
Normal file
|
@ -0,0 +1,141 @@
|
|||
{% set addrs = interfaces | selectattr('name', '==', 'lo') |
|
||||
map(attribute='ip_addresses') | first | selectattr('role') %}
|
||||
{% set loopback = addrs | selectattr('role.value', '==', 'loopback') | map(attribute='address') -%}
|
||||
|
||||
frr defaults datacenter
|
||||
service integrated-vtysh-config
|
||||
log syslog
|
||||
|
||||
# Without this frr and kernel ECMP routes sometimes get desynced when a link is
|
||||
# lost and found. Maybe related to https://github.com/FRRouting/frr/issues/12239.
|
||||
zebra nexthop-group keep 1
|
||||
|
||||
router-id {{ loopback | ipv4 | first | ipaddr('address') }}
|
||||
|
||||
# Don’t announce anything at start until we get routes from all our peers.
|
||||
# Without this packets might get dropped until all routes are synced.
|
||||
bgp update-delay 10
|
||||
|
||||
bfd
|
||||
profile fast
|
||||
receive-interval 150
|
||||
transmit-interval 150
|
||||
|
||||
# Default VRF has two connections to each exit, one for inside and one
|
||||
# for outside networks. The efault route is received from the outside
|
||||
# peers and distributed back to inside peers. Routes to office
|
||||
# networks and NAT IPs are distributed to outside peers.
|
||||
router bgp {{ asn.asn }}
|
||||
# Allow multipathing through different ASs with equal path length.
|
||||
bgp bestpath as-path multipath-relax
|
||||
# NAT IPs are not on any interface so disable checking for it.
|
||||
no bgp network import-check
|
||||
|
||||
{% for group in ['inside', 'outside'] %}
|
||||
neighbor {{ group }} peer-group
|
||||
neighbor {{ group }} remote-as external
|
||||
neighbor {{ group }} capability extended-nexthop
|
||||
{% endfor %}
|
||||
|
||||
{% for iface in interfaces | selectattr('name', 'match', '^lan') %}
|
||||
neighbor {{ iface.name }}.2 interface peer-group inside
|
||||
neighbor {{ iface.name }}.2 bfd profile fast
|
||||
neighbor {{ iface.name }}.4 interface peer-group outside
|
||||
neighbor {{ iface.name }}.4 bfd profile fast
|
||||
{% endfor %}
|
||||
|
||||
address-family ipv4 unicast
|
||||
{% for network in nat %}
|
||||
network {{ network }}
|
||||
{% endfor %}
|
||||
|
||||
redistribute connected route-map loopback
|
||||
maximum-paths 16
|
||||
|
||||
neighbor outside soft-reconfiguration inbound
|
||||
neighbor outside route-map outside->default in
|
||||
neighbor outside route-map default->outside out
|
||||
|
||||
neighbor inside allowas-in origin
|
||||
neighbor inside default-originate
|
||||
neighbor inside soft-reconfiguration inbound
|
||||
neighbor inside route-map inside->default in
|
||||
neighbor inside route-map default->inside out
|
||||
exit-address-family
|
||||
|
||||
address-family ipv6 unicast
|
||||
redistribute connected route-map loopback
|
||||
maximum-paths 16
|
||||
|
||||
neighbor outside activate
|
||||
neighbor outside soft-reconfiguration inbound
|
||||
neighbor outside route-map outside->default in
|
||||
neighbor outside route-map default->outside out
|
||||
|
||||
neighbor inside activate
|
||||
neighbor inside allowas-in origin
|
||||
neighbor inside default-originate
|
||||
neighbor inside soft-reconfiguration inbound
|
||||
neighbor inside route-map inside->default in
|
||||
neighbor inside route-map default->inside out
|
||||
exit-address-family
|
||||
|
||||
# Prefix lists.
|
||||
ip prefix-list default permit 0.0.0.0/0
|
||||
ipv6 prefix-list default permit ::/0
|
||||
|
||||
ip prefix-list fabric permit 10.34.0.0/24 ge 32
|
||||
|
||||
{% for vlan in vlans %}
|
||||
{% for prefix in query('netbox.netbox.nb_lookup', 'prefixes', api_filter='vlan_id='~vlan.id, raw_data=true) %}
|
||||
{% if prefix.family.value == 4 %}
|
||||
ip prefix-list office permit {{ prefix.prefix }} ge 24
|
||||
{% else %}
|
||||
ipv6 prefix-list office permit {{ prefix.prefix }} ge 64
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
ip prefix-list vpn permit {{ wg_net | ipaddr('subnet') }}
|
||||
|
||||
{% for network in nat %}
|
||||
ip prefix-list nat permit {{ network }}
|
||||
{% endfor %}
|
||||
{# TODO WG endpoint should probably be in a separate prefix-list. #}
|
||||
ip prefix-list nat permit {{ wg_ip }}
|
||||
|
||||
route-map loopback permit 1
|
||||
match interface lo
|
||||
|
||||
# Get routes to offices and VPN users on other firewalls from inside peers.
|
||||
route-map inside->default permit 10
|
||||
match ip address prefix-list fabric
|
||||
route-map inside->default permit 20
|
||||
match ip address prefix-list office
|
||||
route-map inside->default permit 21
|
||||
match ipv6 address prefix-list office
|
||||
|
||||
# Send default route and VPN network to inside peers.
|
||||
route-map default->inside permit 1
|
||||
match interface lo
|
||||
route-map default->inside permit 20
|
||||
match ip address prefix-list default
|
||||
route-map default->inside permit 21
|
||||
match ipv6 address prefix-list default
|
||||
route-map default->inside permit 30
|
||||
match ip address prefix-list vpn
|
||||
|
||||
# Get default route from outside peers.
|
||||
route-map outside->default permit 10
|
||||
match ip address prefix-list default
|
||||
route-map outside->default permit 11
|
||||
match ipv6 address prefix-list default
|
||||
|
||||
# Send IPv6 office addresses and IPv4 NAT addresses to outside peers
|
||||
# so inbound packets go through the firewall.
|
||||
route-map default->outside permit 1
|
||||
match interface lo
|
||||
route-map default->outside permit 11
|
||||
match ipv6 address prefix-list office
|
||||
route-map default->outside permit 20
|
||||
match ip address prefix-list nat
|
10
roles/firewall/templates/interfaces.j2
Normal file
10
roles/firewall/templates/interfaces.j2
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% set addrs = interfaces | selectattr('name', '==', 'lo') | map(attribute='ip_addresses') | first -%}
|
||||
|
||||
source-directory /etc/network/interfaces.d
|
||||
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
address {{ wg_ip }}
|
||||
{% for address in addrs %}
|
||||
address {{ address.address }}
|
||||
{% endfor %}
|
10
roles/firewall/templates/interfaces.nft.j2
Normal file
10
roles/firewall/templates/interfaces.nft.j2
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% set ifaces_fabric = interfaces | selectattr('name', 'match', '^lan') | map(attribute='name') %}
|
||||
set inside {
|
||||
type iface_index
|
||||
elements = { {{ ifaces_fabric | product(['2']) | map('join', '.') | join(', ') }}, wg }
|
||||
}
|
||||
|
||||
set outside {
|
||||
type iface_index
|
||||
elements = { {{ ifaces_fabric | product(['4']) | map('join', '.') | join(', ') }} }
|
||||
}
|
3
roles/firewall/templates/mactab.j2
Normal file
3
roles/firewall/templates/mactab.j2
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% for iface in interfaces | iface_real %}
|
||||
{{ iface.name }} {{ iface.mac_address | lower }}
|
||||
{% endfor %}
|
24
roles/firewall/templates/mgmt.intf.j2
Normal file
24
roles/firewall/templates/mgmt.intf.j2
Normal file
|
@ -0,0 +1,24 @@
|
|||
auto mgmt
|
||||
iface mgmt
|
||||
pre-up ip link add $IFACE type vrf table 100
|
||||
up ip link set dev $IFACE up
|
||||
post-down ip link del $IFACE
|
||||
|
||||
{% for iface in interfaces | selectattr('name', 'match', '^mgmt') %}
|
||||
auto {{ iface.name }}
|
||||
iface {{ iface.name }}
|
||||
{% if iface.vrf %}
|
||||
requires {{ iface.vrf.name }}
|
||||
pre-up ip link set $IFACE master {{ iface.vrf.name }}
|
||||
{% endif %}
|
||||
{% if iface.mtu %}
|
||||
mtu {{ iface.mtu }}
|
||||
{% endif %}
|
||||
{% for addr in iface.ip_addresses %}
|
||||
address {{ addr.address }}
|
||||
{% endfor %}
|
||||
{% if iface.custom_fields.gateway %}
|
||||
up ip route add default via {{ iface.custom_fields.gateway.address | ipaddr('address') }}{% if iface.vrf %} vrf {{ iface.vrf.name }}{% endif %}
|
||||
{% endif +%}
|
||||
|
||||
{% endfor %}
|
117
roles/firewall/templates/nftables.nft.j2
Normal file
117
roles/firewall/templates/nftables.nft.j2
Normal file
|
@ -0,0 +1,117 @@
|
|||
#!/usr/sbin/nft -f
|
||||
{% set ifaces_fabric = interfaces | selectattr('name', 'match', '^lan') | map(attribute='name') %}
|
||||
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
include "/etc/nftables.d/interfaces.nft"
|
||||
include "/etc/nftables.d/sets.nft*"
|
||||
|
||||
set link {
|
||||
type iface_index
|
||||
elements = { {{ ifaces_fabric | product(['2', '4']) | map('join', '.') | join(', ') }} }
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop
|
||||
|
||||
ct state vmap { established : accept, related : accept, invalid : drop } \
|
||||
comment "Accept established streams and drop invalid connections"
|
||||
|
||||
iif lo accept \
|
||||
comment "Accept any localhost traffic"
|
||||
|
||||
iif mgmt tcp dport ssh accept \
|
||||
comment "Accept SSH from management VRF"
|
||||
|
||||
tcp dport ssh ip saddr {{ hostvars[master]['ansible_host'] }} accept \
|
||||
comment "Accept SSH from firewall master"
|
||||
|
||||
iif @link tcp dport bgp ip6 saddr fe80::/10 accept \
|
||||
comment "Accept link-local BGP on fabric links"
|
||||
|
||||
iif @link udp dport 3784 ip6 saddr fe80::/10 accept \
|
||||
comment "Accept link-local BFD on fabric links"
|
||||
|
||||
iif @outside udp dport 51820 accept \
|
||||
comment "Accept WireGuard from outside"
|
||||
|
||||
iif {{ iface_sync }} ip6 saddr fe80::/10 udp dport 3780 accept \
|
||||
comment "Accept connection tracking sync data"
|
||||
|
||||
tcp dport auth reject with icmpx type port-unreachable \
|
||||
comment "Reject AUTH to make it fail fast"
|
||||
|
||||
# ICMPv4
|
||||
ip protocol icmp icmp type {
|
||||
echo-request, echo-reply, destination-unreachable,
|
||||
parameter-problem, time-exceeded,
|
||||
} accept \
|
||||
comment "Accept ICMP"
|
||||
|
||||
# ICMPv6
|
||||
ip6 nexthdr icmpv6 icmpv6 type {
|
||||
echo-request, echo-reply, destination-unreachable,
|
||||
packet-too-big, parameter-problem, time-exceeded,
|
||||
} accept \
|
||||
comment "Accept basic IPv6 functionality"
|
||||
|
||||
ip6 nexthdr icmpv6 icmpv6 type {
|
||||
nd-neighbor-solicit, nd-neighbor-advert,
|
||||
nd-router-solicit, nd-router-advert,
|
||||
} ip6 hoplimit 255 accept \
|
||||
comment "Allow IPv6 neighbor discovery"
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop
|
||||
|
||||
ct state { established, related } accept \
|
||||
comment "Forward all established and related traffic"
|
||||
|
||||
ct status dnat accept \
|
||||
comment "Forward DNAT traffic for servers and suchlike"
|
||||
|
||||
# Forward IPv4 to/from VPN users in the same network.
|
||||
{% for vlan in vlans %}
|
||||
iif @inside ip saddr @{{ vlan.name }} ip daddr @{{ vlan.name }} accept
|
||||
{% endfor %}
|
||||
|
||||
# Forward IPv6 to/from VPN users in the same network.
|
||||
{% for vlan in vlans %}
|
||||
iif @inside ip6 saddr @{{ vlan.name }}/6 ip6 daddr @{{ vlan.name }}/6 accept
|
||||
{% endfor %}
|
||||
|
||||
include "/etc/nftables.d/forward.nft*"
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 0; policy accept
|
||||
}
|
||||
}
|
||||
|
||||
table ip nat {
|
||||
include "/etc/nftables.d/interfaces.nft"
|
||||
include "/etc/nftables.d/sets.nft*"
|
||||
include "/etc/nftables.d/netmap.nft*"
|
||||
|
||||
# Ensure these maps exist even if empty.
|
||||
map netmap-in { type ipv4_addr : interval ipv4_addr; flags interval; }
|
||||
map netmap-out { type ipv4_addr : interval ipv4_addr; flags interval; }
|
||||
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat
|
||||
|
||||
iif @inside oif @outside snat ip prefix to ip saddr map @netmap-out \
|
||||
comment "Static source NAT for 1:1 mapped addresses"
|
||||
|
||||
include "/etc/nftables.d/nat.nft*"
|
||||
}
|
||||
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat
|
||||
|
||||
dnat ip prefix to ip daddr map @netmap-in \
|
||||
comment "Static destination NAT for 1:1 mapped addresses"
|
||||
}
|
||||
}
|
19
roles/firewall/templates/sysctl.conf.j2
Normal file
19
roles/firewall/templates/sysctl.conf.j2
Normal file
|
@ -0,0 +1,19 @@
|
|||
# We are router.
|
||||
net.ipv4.ip_forward = 1
|
||||
net.ipv6.conf.all.forwarding = 1
|
||||
|
||||
# But not for management interfaces.
|
||||
{% for iface in interfaces | selectattr('name', 'match', '^mgmt') %}
|
||||
net.ipv4.conf.{{ iface.name }}.forwarding = 0
|
||||
net.ipv6.conf.{{ iface.name }}.forwarding = 0
|
||||
{% endfor %}
|
||||
|
||||
# Zebra docs recommend these.
|
||||
net.ipv6.conf.all.keep_addr_on_down = 1
|
||||
net.ipv6.route.skip_notify_on_dev_down = 1
|
||||
|
||||
# Do not send ICMP redirects. Happens because firewall sees all office
|
||||
# networks coming from the same routers, and gets confused as to why
|
||||
# firewall is routing packets between them.
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
4
roles/firewall/templates/wg.intf.j2
Normal file
4
roles/firewall/templates/wg.intf.j2
Normal file
|
@ -0,0 +1,4 @@
|
|||
auto wg
|
||||
iface wg inet static
|
||||
use wireguard
|
||||
address {{ wg_net }}
|
Loading…
Add table
Add a link
Reference in a new issue