From db397cb2b169fede4d5f99dc324e645298153f88 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 10 Apr 2024 14:03:50 +0200 Subject: [PATCH] exit: store VLAN interface addresses in NetBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … instead of generating them from prefixes. A NetBox script can be used to create and configure all necessary data for a new VLAN. Instead of VLAN roles “inside" and “outside” we now create separate VRFs for inside VLANs to match the actual exit/firewall configuration. The “outside” VRF is for all VLANs that are directly accessible from the internet. --- group_vars/all/vars.yml | 2 +- roles/exit/templates/frr.conf.j2 | 24 ++++++++--------- roles/exit/templates/isc-dhcp-relay.j2 | 5 ++-- roles/exit/templates/networks.intf.j2 | 36 +++++--------------------- roles/exit/templates/radvd.conf.j2 | 5 ++-- roles/fabric/templates/switch.intf.j2 | 7 ++++- roles/firewall/templates/frr.conf.j2 | 4 ++- 7 files changed, 32 insertions(+), 51 deletions(-) diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml index 76826ea..0b73a21 100644 --- a/group_vars/all/vars.yml +++ b/group_vars/all/vars.yml @@ -1 +1 @@ -vlans: "{{ query('netbox.netbox.nb_lookup', 'vlans', api_filter='group=new-net', raw_data=true) | selectattr('role') | sort(attribute='vid') }}" +vlans: "{{ query('netbox.netbox.nb_lookup', 'vlans', api_filter='group=new-net', raw_data=true) | sort(attribute='vid') }}" diff --git a/roles/exit/templates/frr.conf.j2 b/roles/exit/templates/frr.conf.j2 index e4e638f..cd18add 100644 --- a/roles/exit/templates/frr.conf.j2 +++ b/roles/exit/templates/frr.conf.j2 @@ -2,10 +2,8 @@ | map(attribute='ip_addresses') | first | selectattr('role') | selectattr('role.value', '==', 'loopback') | map(attribute='address') %} -{% set my_index = inventory_hostname.split('-')[1]|int %} -{% set bridge = interfaces | selectattr('type') | selectattr('type.value', '==', 'bridge') | first %} -{% set bridge_vlans = vlans | selectattr('vid', 'in', bridge.tagged_vlans | map(attribute='vid')) -%} -{% set inside_vlans = bridge_vlans | selectattr('role.slug', '==', 'inside') -%} +{% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') + | selectattr('vrf') | map(attribute='vrf') | rejectattr('name', '==', 'outside') -%} frr defaults datacenter log syslog informational @@ -159,8 +157,8 @@ router bgp {{ asn.asn }} vrf inside {% endfor %} redistribute connected route-map loopback-inside -{% for vlan in inside_vlans %} - import vrf {{ vlan.name }} +{% for vrf in inside_vrfs %} + import vrf {{ vrf.name }} {% endfor %} import vrf default import vrf route-map inside-import @@ -181,17 +179,17 @@ router bgp {{ asn.asn }} vrf inside {% endfor %} redistribute connected route-map loopback-inside -{% for vlan in inside_vlans %} - import vrf {{ vlan.name }} +{% for vrf in inside_vrfs %} + import vrf {{ vrf.name }} {% endfor %} import vrf default import vrf route-map inside-import exit-address-family -{% for vlan in inside_vlans %} -# VRF for L2 network {{ vlan.name }}. Imports gateway from inside VRF. -router bgp {{ asn.asn }} vrf {{ vlan.name }} +{% for vrf in inside_vrfs %} +# VRF for L2 network {{ vrf.name }}. Imports gateway from inside VRF. +router bgp {{ asn.asn }} vrf {{ vrf.name }} bgp bestpath as-path multipath-relax address-family ipv4 unicast @@ -215,8 +213,8 @@ ipv6 prefix-list default permit ::/0 ip prefix-list fabric permit 10.34.0.0/24 ge 32 ipv6 prefix-list fabric permit 2001:1470:fffd:3400::/64 ge 128 -{% for vlan in inside_vlans %} -{% set prefixes = query('netbox.netbox.nb_lookup', 'prefixes', api_filter='vlan_id='~vlan.id, raw_data=true) +{% for vrf in inside_vrfs %} +{% set prefixes = query('netbox.netbox.nb_lookup', 'prefixes', api_filter='vrf_id='~vrf.id, raw_data=true) | sort(attribute='family.value') %} {% for prefix in prefixes %} {% if prefix.family.value == 4 %} diff --git a/roles/exit/templates/isc-dhcp-relay.j2 b/roles/exit/templates/isc-dhcp-relay.j2 index 1d86ef4..8e8c1fd 100644 --- a/roles/exit/templates/isc-dhcp-relay.j2 +++ b/roles/exit/templates/isc-dhcp-relay.j2 @@ -1,4 +1,3 @@ -{% set bridge = interfaces | selectattr('type') | selectattr('type.value', '==', 'bridge') | first %} {% set dhcp_networks = query('netbox.netbox.nb_lookup', 'prefixes', api_filter='role=dhcp-pool', raw_data=true) | selectattr('vlan') | map(attribute='vlan.vid') | sort -%} @@ -10,7 +9,9 @@ SERVERS="{{ dhcp }}" # This variable requires a -i for each interface configured above. # This will be used in the actual dhcrelay command # For example, "-i eth0 -i eth1" -INTF_CMD="{{ bridge.tagged_vlans | map(attribute='vid') | intersect(dhcp_networks) | sort | map('regex_replace', '^', '-id bridge.') | join(' ') }} -iu {{ iface_uplink }} -iu peerlink.4" +INTF_CMD="{{ interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') + | selectattr('untagged_vlan') | selectattr('untagged_vlan.vid', 'in', dhcp_networks) + | map(attribute='name') | sort | map('regex_replace', '^', '-id ') | join(' ') }} -iu {{ iface_uplink }} -iu peerlink.4" # Additional options that are passed to the DHCP relay daemon? OPTIONS="-U outside" diff --git a/roles/exit/templates/networks.intf.j2 b/roles/exit/templates/networks.intf.j2 index 28619ad..a9f3857 100644 --- a/roles/exit/templates/networks.intf.j2 +++ b/roles/exit/templates/networks.intf.j2 @@ -1,35 +1,11 @@ -{# Note that there must be exactly one VLAN-aware bridge. #} -{% set bridge = interfaces | selectattr('type') | selectattr('type.value', '==', 'bridge') | first %} -{% set bridge_vlans = vlans | selectattr('vid', 'in', bridge.tagged_vlans | map(attribute='vid')) -%} +{# VRF outside is special, all others are inside but also firewalled from each other. #} +{% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') + | selectattr('vrf') | map(attribute='vrf') | rejectattr('name', '==', 'outside') -%} # A separate VRF for each inside network so we can firewall between them. -{% for vlan in bridge_vlans | selectattr('role.slug', '==', 'inside') %} -auto {{ vlan.name }} -iface {{ vlan.name }} +{% for vrf in inside_vrfs %} +auto {{ vrf.name }} +iface {{ vrf.name }} vrf-table auto {% endfor %} - -# Interfaces. -{% for vlan in bridge_vlans %} -{% set prefixes = query('netbox.netbox.nb_lookup', 'prefixes', api_filter='vlan_id='~vlan.id, raw_data=true) - | sort(attribute='family.value') | map(attribute='prefix') %} -auto {{ bridge.name }}.{{ vlan.vid }} -iface {{ bridge.name }}.{{ vlan.vid }} - vrf {% if vlan.role.slug == 'outside' %}outside{% else %}{{ vlan.name }}{% endif +%} - mtu 9216 -{% if peer is defined %} -{% set my_index = inventory_hostname.split('-')[1]|int %} -{% for prefix in prefixes %} - address {{ prefix | ipaddr(1 + my_index) }} -{% endfor %} -{% if prefixes %} - address-virtual 00:00:5e:00:01:01 {{ prefixes | ipaddr(1) | join(' ') }} -{% endif %} -{% else %} -{% for prefix in prefixes %} - address {{ prefix }} -{% endfor %} -{% endif %} - -{% endfor %} diff --git a/roles/exit/templates/radvd.conf.j2 b/roles/exit/templates/radvd.conf.j2 index f383924..ba92fe3 100644 --- a/roles/exit/templates/radvd.conf.j2 +++ b/roles/exit/templates/radvd.conf.j2 @@ -1,6 +1,5 @@ -{# Note that there must be exactly one VLAN-aware bridge. #} -{% set bridge = interfaces | selectattr('type') | selectattr('type.value', '==', 'bridge') | first %} -{% set my_vlans = bridge.tagged_vlans | sort(attribute='vid') -%} +{% set my_vlans = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') + | selectattr('untagged_vlan') | map(attribute='untagged_vlan') -%} # Send IPv6 RAs from virtual router IP for each network. Also set DNS options. # Both exits announce the same gateway, so don’t revoke it if we go down. diff --git a/roles/fabric/templates/switch.intf.j2 b/roles/fabric/templates/switch.intf.j2 index 66b8528..dfe633c 100644 --- a/roles/fabric/templates/switch.intf.j2 +++ b/roles/fabric/templates/switch.intf.j2 @@ -37,8 +37,13 @@ iface {{ iface.name }} {% endif %} {#- Addresses. #} -{% for addr in iface.ip_addresses %} +{% for addr in iface.ip_addresses | rejectattr('role') %} address {{ addr.address }} {% endfor %} +{% set anycast = iface.ip_addresses | selectattr('role') | selectattr('role.value', '==', 'anycast') + | map(attribute='address') %} +{% if anycast %} + address-virtual 00:00:5e:00:01:01 {{ anycast | ipaddr(1) | join(' ') }} +{% endif %} {% endfor %} diff --git a/roles/firewall/templates/frr.conf.j2 b/roles/firewall/templates/frr.conf.j2 index c1ab7ee..1329092 100644 --- a/roles/firewall/templates/frr.conf.j2 +++ b/roles/firewall/templates/frr.conf.j2 @@ -82,13 +82,15 @@ ipv6 prefix-list default permit ::/0 ip prefix-list fabric permit 10.34.0.0/24 ge 32 -{% for vlan in vlans | selectattr('role.slug', '==', 'inside') %} +{% for vlan in vlans %} {% for prefix in query('netbox.netbox.nb_lookup', 'prefixes', api_filter='vlan_id='~vlan.id, raw_data=true) %} +{% if prefix.vrf and prefix.vrf.name != 'outside' %} {% if prefix.family.value == 4 %} ip prefix-list office permit {{ prefix.prefix }} ge 24 {% elif prefix.family.value == 6 %} ipv6 prefix-list office permit {{ prefix.prefix }} ge 64 {% endif %} +{% endif %} {% endfor %} {% endfor %}