Initial commit, squashed
This commit is contained in:
		
						commit
						158e8740b8
					
				
					 83 changed files with 2718 additions and 0 deletions
				
			
		
							
								
								
									
										2
									
								
								roles/firewall/files/conntrackd.conf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/firewall/files/conntrackd.conf
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| # The init script for conntrackd wants this, not sure about conntrackd itself. | ||||
| net.netfilter.nf_conntrack_tcp_be_liberal = 1 | ||||
							
								
								
									
										15
									
								
								roles/firewall/files/sshd_config.friwall
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								roles/firewall/files/sshd_config.friwall
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| # This is used by sshd in default VRF to receive configuration updates. Lock | ||||
| # down to only allow executing the update script. | ||||
| 
 | ||||
| # Only allow pubkey auth. | ||||
| KbdInteractiveAuthentication no | ||||
| PasswordAuthentication no | ||||
| PermitRootLogin prohibit-password | ||||
| 
 | ||||
| # Disable what we can. | ||||
| AllowTcpForwarding no | ||||
| GatewayPorts no | ||||
| X11Forwarding no | ||||
| 
 | ||||
| # And then disable everything else. | ||||
| ForceCommand /usr/local/bin/update | ||||
							
								
								
									
										34
									
								
								roles/firewall/files/update
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								roles/firewall/files/update
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| #!/bin/sh | ||||
| 
 | ||||
| apply() { | ||||
| 	cp -R /opt/config/etc/nftables.d /etc || return 1 | ||||
| 	nft -I /etc/nftables.d -f /etc/nftables.nft || return 2 | ||||
| 	cp -R /opt/config/etc/wireguard /etc || return 3 | ||||
| 	wg syncconf wg /etc/wireguard/wg.conf || return 4 | ||||
| } | ||||
| 
 | ||||
| cleanup() { | ||||
| 	rm -fr /opt/config | ||||
| } | ||||
| 
 | ||||
| # clean now and on exit | ||||
| cleanup | ||||
| trap cleanup EXIT | ||||
| 
 | ||||
| mkdir -p /opt/config | ||||
| tar xz -C /opt/config --warning=no-timestamp | ||||
| 
 | ||||
| current="$(cat /opt/version 2>/dev/null || echo -1)" | ||||
| next="$(cat /opt/config/version 2>/dev/null || echo -1)" | ||||
| echo "Updating config from v${current} to v${next}" | ||||
| if [ "${next:-0}" -ne "${current:-0}" ] ; then | ||||
| 	echo "Applying config v${next}" | ||||
| 	if apply ; then | ||||
| 		echo "${next}" > /opt/version | ||||
| 		echo "Applied config v${next}" | ||||
| 	else | ||||
| 		error="$?" | ||||
| 		echo "Could not apply config v${next}, error ${error}" | ||||
| 		exit "${error}" | ||||
| 	fi | ||||
| fi | ||||
							
								
								
									
										41
									
								
								roles/firewall/handlers/main.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								roles/firewall/handlers/main.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| - name: enable interfaces | ||||
|   command: ifup --auto | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: mkinitfs | ||||
|   command: mkinitfs | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: reload frr | ||||
|   command: /usr/lib/frr/frr-reload.py --reload /etc/frr/frr.conf | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: reload nftables | ||||
|   service: | ||||
|     name: nftables | ||||
|     state: reloaded | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: restart conntrackd | ||||
|   service: | ||||
|     name: conntrackd | ||||
|     state: restarted | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: restart frr | ||||
|   service: | ||||
|     name: frr | ||||
|     state: restarted | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: reload sshd.friwall | ||||
|   service: | ||||
|     name: sshd.friwall | ||||
|     state: reloaded | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
| 
 | ||||
| - name: restart sshd.friwall | ||||
|   service: | ||||
|     name: sshd.friwall | ||||
|     state: restarted | ||||
|   when: "'handler' not in ansible_skip_tags" | ||||
							
								
								
									
										59
									
								
								roles/firewall/tasks/config.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								roles/firewall/tasks/config.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| - name: Install packages for config updates | ||||
|   package: | ||||
|     name: tar | ||||
| 
 | ||||
| - name: Limit SSH for config updates | ||||
|   copy: | ||||
|     dest: /etc/ssh/ | ||||
|     src: sshd_config.friwall | ||||
|   notify: reload sshd.friwall | ||||
| 
 | ||||
| - name: Create SSH service for config updates | ||||
|   file: | ||||
|     path: /etc/init.d/sshd.friwall | ||||
|     src: /etc/init.d/sshd | ||||
|     state: link | ||||
| 
 | ||||
| - name: Configure SSH service for config updates | ||||
|   copy: | ||||
|     dest: /etc/conf.d/sshd.friwall | ||||
|     content: | | ||||
|       cfgfile="/etc/ssh/sshd_config.friwall" | ||||
|       vrf="default" | ||||
|   notify: restart sshd.friwall | ||||
| 
 | ||||
| - name: Enable SSH service for config updates | ||||
|   service: | ||||
|     name: sshd.friwall | ||||
|     enabled: yes | ||||
|     state: started | ||||
| 
 | ||||
| - name: Install config updater | ||||
|   copy: | ||||
|     dest: /usr/local/bin/ | ||||
|     src: update | ||||
|     mode: 0700 | ||||
| 
 | ||||
| - name: Get master SSH key | ||||
|   delegate_to: '{{ master }}' | ||||
|   command: "cat ~friwall/.ssh/id_ed25519.pub" | ||||
|   register: master_key | ||||
|   changed_when: false | ||||
| 
 | ||||
| - name: Deploy master key on node | ||||
|   authorized_key: "user=root key={{ master_key.stdout }}" | ||||
| 
 | ||||
| - name: Get my host SSH key | ||||
|   command: cat /etc/ssh/ssh_host_ed25519_key.pub | ||||
|   register: node_key | ||||
|   changed_when: false | ||||
| 
 | ||||
| - name: Introduce myself to master | ||||
|   delegate_to: '{{ master }}' | ||||
|   become: yes | ||||
|   become_user: friwall | ||||
|   become_method: su | ||||
|   become_flags: "-s /bin/sh" # no login shell for user | ||||
|   known_hosts: | ||||
|     name: "{{ inventory_hostname }}" | ||||
|     key: "{{ inventory_hostname }},{{ interfaces | selectattr('name', '==', 'lo') | map(attribute='ip_addresses') | first | selectattr('role') | selectattr('role.value', '==', 'loopback') | map(attribute='address') | ipv4 | first | ipaddr('address') }} {{ node_key.stdout }}" # TODO make IP retrieval less terrifying | ||||
							
								
								
									
										36
									
								
								roles/firewall/tasks/conntrackd.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								roles/firewall/tasks/conntrackd.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| - name: Install conntrack-tools | ||||
|   package: | ||||
|     name: conntrack-tools | ||||
| 
 | ||||
| # Ensure the module is loaded before setting sysctl values. | ||||
| - name: Autoload nf_conntrack | ||||
|   lineinfile: | ||||
|     dest: /etc/modules-load.d/netfilter.conf | ||||
|     line: nf_conntrack | ||||
|     create: yes | ||||
| 
 | ||||
| # Set required sysctl values. | ||||
| - name: Set sysctl values for conntrackd | ||||
|   copy: | ||||
|     dest: /etc/sysctl.d/ | ||||
|     src: conntrackd.conf | ||||
| 
 | ||||
| - name: Set up conntrackd | ||||
|   template: | ||||
|     dest: /etc/conntrackd/conntrackd.conf | ||||
|     src: conntrackd.conf.j2 | ||||
|     mode: 0644 | ||||
|   notify: restart conntrackd | ||||
| 
 | ||||
| - name: Run conntrackd in default VRF | ||||
|   lineinfile: | ||||
|     dest: /etc/conf.d/conntrackd | ||||
|     line: 'vrf="default"' | ||||
|     regexp: '^vrf=' | ||||
|   notify: restart conntrackd | ||||
| 
 | ||||
| - name: Enable conntrackd | ||||
|   service: | ||||
|     name: conntrackd | ||||
|     enabled: yes | ||||
|     state: started | ||||
							
								
								
									
										48
									
								
								roles/firewall/tasks/frr.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								roles/firewall/tasks/frr.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| - name: Enable sysctl service | ||||
|   service: | ||||
|     name: sysctl | ||||
|     enabled: yes | ||||
|     runlevel: boot | ||||
|     state: started | ||||
| 
 | ||||
| - name: Enable community package repo | ||||
|   lineinfile: | ||||
|     path: /etc/apk/repositories | ||||
|     regexp: '^# *(http.*/v[^/]*/community)' | ||||
|     line: '\1' | ||||
|     backrefs: yes | ||||
| 
 | ||||
| - name: Install FRR | ||||
|   package: | ||||
|     name: frr,frr-pythontools | ||||
|     state: latest | ||||
| 
 | ||||
| - name: Set datacenter defaults | ||||
|   lineinfile: | ||||
|     path: /etc/frr/daemons | ||||
|     regexp: '^frr_profile=' | ||||
|     line: 'frr_profile="datacenter"' | ||||
|   notify: restart frr | ||||
| 
 | ||||
| - name: Enable BGP and BFD | ||||
|   lineinfile: | ||||
|     path: /etc/frr/daemons | ||||
|     regexp: "^{{ item }}=" | ||||
|     line: "{{ item }}=yes" | ||||
|   loop: | ||||
|     - bfdd | ||||
|     - bgpd | ||||
|   notify: restart frr | ||||
| 
 | ||||
| - name: Enable FRR service | ||||
|   service: | ||||
|     name: frr | ||||
|     enabled: yes | ||||
|     state: started | ||||
| 
 | ||||
| - name: Copy FRR config | ||||
|   template: | ||||
|     dest: /etc/frr/frr.conf | ||||
|     src: frr.conf.j2 | ||||
|     mode: 0644 | ||||
|   notify: reload frr | ||||
							
								
								
									
										64
									
								
								roles/firewall/tasks/main.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								roles/firewall/tasks/main.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| - name: Update package cache | ||||
|   package: | ||||
|     update_cache: yes | ||||
| 
 | ||||
| - name: Install packages | ||||
|   package: | ||||
|     name: bash,bonding,iproute2 | ||||
|     state: latest | ||||
| 
 | ||||
| - name: Tell mdev to rename network interfaces | ||||
|   lineinfile: | ||||
|     path: /etc/mdev.conf | ||||
|     line: '-net/.* root:root 600 @/sbin/nameif -s' | ||||
|     insertafter: '^# net devices' | ||||
|   notify: mkinitfs | ||||
| 
 | ||||
| - name: Configure interface names | ||||
|   template: | ||||
|     dest: /etc/mactab | ||||
|     src: mactab.j2 | ||||
|     mode: 0644 | ||||
| 
 | ||||
| - name: Create /etc/network/interfaces.d | ||||
|   file: | ||||
|     path: /etc/network/interfaces.d | ||||
|     state: directory | ||||
|     mode: 0755 | ||||
| 
 | ||||
| - name: Set up interfaces | ||||
|   template: | ||||
|     dest: /etc/network/interfaces | ||||
|     src: interfaces.j2 | ||||
|     mode: 0644 | ||||
|   notify: enable interfaces | ||||
| 
 | ||||
| - name: Set up management interfaces | ||||
|   import_tasks: mgmt.yml | ||||
| 
 | ||||
| - name: Set up data interfaces | ||||
|   template: | ||||
|     dest: /etc/network/interfaces.d/fabric.intf | ||||
|     src: fabric.intf.j2 | ||||
|     mode: 0644 | ||||
|   notify: enable interfaces | ||||
| 
 | ||||
| - name: Set up sysctls | ||||
|   template: | ||||
|     dest: /etc/sysctl.d/firewall.conf | ||||
|     src: sysctl.conf.j2 | ||||
| 
 | ||||
| - name: Set up FRR | ||||
|   import_tasks: frr.yml | ||||
| 
 | ||||
| - name: Set up wireguard | ||||
|   import_tasks: wireguard.yml | ||||
| 
 | ||||
| - name: Set up nftables | ||||
|   import_tasks: nftables.yml | ||||
| 
 | ||||
| - name: Set up conntrackd | ||||
|   import_tasks: conntrackd.yml | ||||
| 
 | ||||
| - name: Set up configuration channel | ||||
|   import_tasks: config.yml | ||||
							
								
								
									
										25
									
								
								roles/firewall/tasks/mgmt.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								roles/firewall/tasks/mgmt.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| - name: Set up management interfaces | ||||
|   template: | ||||
|     dest: /etc/network/interfaces.d/mgmt.intf | ||||
|     src: mgmt.intf.j2 | ||||
|     mode: 0644 | ||||
|   register: task_mgmt_interface | ||||
| 
 | ||||
| - name: Run SSH in management VRF | ||||
|   lineinfile: | ||||
|     path: /etc/conf.d/sshd | ||||
|     line: "vrf=\"mgmt\"" | ||||
|   register: task_ssh_vrf | ||||
| 
 | ||||
| - name: Reboot for new VRF | ||||
|   reboot: | ||||
|   when: task_mgmt_interface.changed or task_ssh_vrf.changed | ||||
|   register: task_reboot | ||||
| 
 | ||||
| - name: Reset the connection | ||||
|   meta: reset_connection | ||||
| 
 | ||||
| - name: Wait for the network device to reload | ||||
|   wait_for_connection: | ||||
|     delay: 10 | ||||
|   when: task_reboot.changed | ||||
							
								
								
									
										25
									
								
								roles/firewall/tasks/nftables.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								roles/firewall/tasks/nftables.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| - name: Install nftables | ||||
|   package: | ||||
|     name: nftables | ||||
| 
 | ||||
| - name: Copy nftables config | ||||
|   template: | ||||
|     dest: /etc/nftables.nft | ||||
|     src: nftables.nft.j2 | ||||
|     mode: 0644 | ||||
|   notify: reload nftables | ||||
| 
 | ||||
| - name: Copy static nftables includes | ||||
|   template: | ||||
|     dest: '/etc/nftables.d/{{ item }}' | ||||
|     src: '{{ item }}.j2' | ||||
|     mode: 0644 | ||||
|   loop: | ||||
|     - interfaces.nft | ||||
|   notify: reload nftables | ||||
| 
 | ||||
| - name: Enable nftables service | ||||
|   service: | ||||
|     name: nftables | ||||
|     enabled: yes | ||||
|     state: started | ||||
							
								
								
									
										26
									
								
								roles/firewall/tasks/wireguard.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								roles/firewall/tasks/wireguard.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| # All firewall nodes share one external IP for wireguard connections. | ||||
| # Private key and peer configuration is the same for all nodes. Peers | ||||
| # connected to each node are installed in the routing table and | ||||
| # distributed into fabric. | ||||
| 
 | ||||
| - name: Install wireguard tools | ||||
|   package: | ||||
|     name: wireguard-tools | ||||
| 
 | ||||
| - name: Create wireguard directory | ||||
|   file: | ||||
|     path: /etc/wireguard | ||||
|     state: directory | ||||
| 
 | ||||
| - name: Touch wireguard config | ||||
|   file: | ||||
|     path: /etc/wireguard/wg.conf | ||||
|     state: touch | ||||
|     access_time: preserve | ||||
|     modification_time: preserve | ||||
| 
 | ||||
| - name: Add wireguard interface | ||||
|   template: | ||||
|     dest: /etc/network/interfaces.d/wg.intf | ||||
|     src: wg.intf.j2 | ||||
|   notify: enable interfaces | ||||
							
								
								
									
										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