From 99aef435743c16bb9c201d7e8a2366b50a8b0f90 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Sat, 13 Jul 2024 18:08:31 +0200 Subject: [PATCH 1/2] exit: add DHCP relay for new server Really quite shoddy as it is right now. Should get better once the old server is retired. --- inventory.yml | 1 + roles/exit/files/keepalive-service | 18 ----------------- roles/exit/tasks/dhcp.yml | 17 ++++++++-------- roles/exit/templates/isc-dhcp-relay.j2 | 27 ++++++++++--------------- roles/exit/templates/keepalived.conf.j2 | 7 ++++++- 5 files changed, 27 insertions(+), 43 deletions(-) delete mode 100644 roles/exit/files/keepalive-service diff --git a/inventory.yml b/inventory.yml index 7edb45c..da6ea32 100644 --- a/inventory.yml +++ b/inventory.yml @@ -13,3 +13,4 @@ query_filters: - role: 'compute-node' - role: 'firewall' - role: 'switch' + - role: 'server' diff --git a/roles/exit/files/keepalive-service b/roles/exit/files/keepalive-service deleted file mode 100644 index c1f895f..0000000 --- a/roles/exit/files/keepalive-service +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -class="${1}" -name="${2}" -state="${3}" - -case "${state}" in -"MASTER" | "FAULT") - systemctl start "${name}" - ;; -"BACKUP" | "STOP") - systemctl stop "${name}" - ;; -*) - logger "keepalived unknown state for ${name}: ${state}" - exit 1 - ;; -esac diff --git a/roles/exit/tasks/dhcp.yml b/roles/exit/tasks/dhcp.yml index f714088..37aec1a 100644 --- a/roles/exit/tasks/dhcp.yml +++ b/roles/exit/tasks/dhcp.yml @@ -1,16 +1,17 @@ +# TODO rewrite task and templates into something sane once the old DHCP server is retired +- set_fact: + dhcp_servers: '{{ prefixes | selectattr("custom_fields.dhcp_server") | groupby(attribute="custom_fields.dhcp_server.address") }}' + - name: Install keepalived import_tasks: keepalived.yml -- name: Create keepalive notify script for systemd services - copy: - dest: /usr/local/bin/ - src: keepalive-service - mode: 0755 - -- name: Configure DHCP relays +- name: Configure relay for old DHCP server template: - dest: "/etc/default/isc-dhcp-relay" + dest: '/etc/default/isc-dhcp-relay-{{ prefixes | selectattr("prefix", "==", item.0 | ipaddr("network/prefix")) | map(attribute="vrf.name") | first }}' src: isc-dhcp-relay.j2 + loop: '{{ dhcp_servers }}' + loop_control: + label: "{{ item.0 }}" notify: restart keepalived - name: Set up keepalived diff --git a/roles/exit/templates/isc-dhcp-relay.j2 b/roles/exit/templates/isc-dhcp-relay.j2 index 21cb999..ae42667 100644 --- a/roles/exit/templates/isc-dhcp-relay.j2 +++ b/roles/exit/templates/isc-dhcp-relay.j2 @@ -1,17 +1,12 @@ -{% set dhcp_vlans = vrf_prefixes | selectattr('custom_fields.dhcp_ranges') - | map(attribute='vlan.vid') | sort -%} +{% set my_server = item.0 %} +{% set my_vlans = item.1 | map(attribute='vlan.vid') | sort %} +{% set my_prefix = prefixes | selectattr("prefix", "==", my_server | ipaddr("network/prefix")) | first -%} -# What servers should the DHCP relay forward requests to? -SERVERS="{{ dhcp }}" - -# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests? -# Always include the interface towards the DHCP server. -# 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="{{ interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') - | selectattr('untagged_vlan') | selectattr('untagged_vlan.vid', 'in', dhcp_vlans) - | 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" +SERVERS="{{ my_server | ipaddr('address') }}" +{% if my_prefix.vrf.name == 'outside' %} +INTF_CMD="{{ my_vlans | map('regex_replace', '^', '-id bridge.') | join(' ') }} -iu {{ iface_uplink }} -iu peerlink.4 -U {{ my_prefix.vrf.name }}" +OPTIONS="" +{% else %} +INTF_CMD="{{ my_vlans | map('regex_replace', '^', '-id bridge.') | join(' ') }} -U bridge.{{ my_prefix.vlan.vid }}" +OPTIONS="--giaddr-src" +{% endif %} diff --git a/roles/exit/templates/keepalived.conf.j2 b/roles/exit/templates/keepalived.conf.j2 index 67f9325..7a7fb95 100644 --- a/roles/exit/templates/keepalived.conf.j2 +++ b/roles/exit/templates/keepalived.conf.j2 @@ -1,3 +1,5 @@ +{% set dhcrelays = prefixes | selectattr('prefix', 'in', dhcp_servers | map('first') | ipaddr("network/prefix")) + | map(attribute="vrf.name") | map('regex_replace', '^', 'dhcrelay@') %} {% set exits = [inventory_hostname, peer]|sort -%} global_defs { @@ -18,5 +20,8 @@ vrrp_instance dhcrelay { @^{{ exit }} {{ "169.254.1.0/24" | ipaddr(loop.index + 1) | ipaddr('address') }} {% endfor %} } - notify /usr/local/bin/keepalive-service + notify_master "systemctl start {{ dhcrelays | join(' ') }}" + notify_fault "systemctl start {{ dhcrelays | join(' ') }}" + notify_backup "systemctl stop {{ dhcrelays | join(' ') }}" + notify_stop "systemctl stop {{ dhcrelays | join(' ') }}" } From 82b10e8133ccf4887ea038d0bec6ba77796dbbdc Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Mon, 15 Jul 2024 11:28:33 +0200 Subject: [PATCH 2/2] exit: support custom VRF imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten minutes to set up and ten hours to convince Ansible to not be quite so retarded. The list2dict filter seems to be the (or another) missing piece. Now let’s rewrite everything else using it. Or not. --- filter_plugins/util.py | 14 +++++++++ roles/exit/tasks/dhcp.yml | 2 +- roles/exit/templates/frr.conf.j2 | 50 ++++++++++++++++++++++++++++---- roles/facts/tasks/main.yml | 1 + 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 filter_plugins/util.py diff --git a/filter_plugins/util.py b/filter_plugins/util.py new file mode 100644 index 0000000..50c2c60 --- /dev/null +++ b/filter_plugins/util.py @@ -0,0 +1,14 @@ +#!/usr/bin/python + +class FilterModule(object): + '''Helper filters to make Ansible less unpleasant''' + def filters(self): + return { + 'list2dict': self.list2dict, + } + + def list2dict(self, items, key): + ''' + Like items2dict but keep entire dictionaries as values. + ''' + return {item[key]: item for item in items} diff --git a/roles/exit/tasks/dhcp.yml b/roles/exit/tasks/dhcp.yml index 37aec1a..fa1c71a 100644 --- a/roles/exit/tasks/dhcp.yml +++ b/roles/exit/tasks/dhcp.yml @@ -5,7 +5,7 @@ - name: Install keepalived import_tasks: keepalived.yml -- name: Configure relay for old DHCP server +- name: Configure DHCP relays template: dest: '/etc/default/isc-dhcp-relay-{{ prefixes | selectattr("prefix", "==", item.0 | ipaddr("network/prefix")) | map(attribute="vrf.name") | first }}' src: isc-dhcp-relay.j2 diff --git a/roles/exit/templates/frr.conf.j2 b/roles/exit/templates/frr.conf.j2 index 556bd4e..d4f6bfe 100644 --- a/roles/exit/templates/frr.conf.j2 +++ b/roles/exit/templates/frr.conf.j2 @@ -3,7 +3,7 @@ | selectattr('role') | selectattr('role.value', '==', 'loopback') | map(attribute='address') %} {% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') - | selectattr('vrf') | map(attribute='vrf') | rejectattr('name', '==', 'outside') -%} + | selectattr('vrf') | map(attribute='vrf.name') | reject('==', 'outside') | sort %} frr defaults datacenter log syslog informational @@ -158,7 +158,7 @@ router bgp {{ asn.asn }} vrf inside redistribute connected route-map loopback-inside {% for vrf in inside_vrfs %} - import vrf {{ vrf.name }} + import vrf {{ vrf }} {% endfor %} import vrf default import vrf route-map inside-import @@ -180,14 +180,14 @@ router bgp {{ asn.asn }} vrf inside redistribute connected route-map loopback-inside {% for vrf in inside_vrfs %} - import vrf {{ vrf.name }} + import vrf {{ vrf }} {% endfor %} import vrf default import vrf route-map inside-import exit-address-family -{% for vrf in inside_vrfs %} +{% for vrf in vrfs.values() | selectattr('name', '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 @@ -195,13 +195,27 @@ router bgp {{ asn.asn }} vrf {{ vrf.name }} address-family ipv4 unicast redistribute connected import vrf inside +{% if vrf.custom_fields.imports %} +{% for import in vrf.custom_fields.imports %} + import vrf {{ import.name }} +{% endfor %} + import vrf route-map {{ vrf.name }}-import +{% else %} import vrf route-map office-import +{% endif %} exit-address-family address-family ipv6 unicast redistribute connected import vrf inside +{% if vrf.custom_fields.imports %} +{% for import in vrf.custom_fields.imports %} + import vrf {{ import.name }} +{% endfor %} + import vrf route-map {{ vrf.name }}-import +{% else %} import vrf route-map office-import +{% endif %} exit-address-family {% endfor %} @@ -213,8 +227,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 prefix in vrf_prefixes - | selectattr('vrf.id', 'in', inside_vrfs|map(attribute='id')) +# common prefix list for all inside networks +{% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs) | sort(attribute='family.value') | sort(attribute='vlan.vid') %} {% if prefix.family.value == 4 %} ip prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} @@ -223,6 +237,16 @@ ipv6 prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr( {% endif %} {% endfor %} +# individual prefix lists for each inside network +{% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs) + | sort(attribute='family.value') | sort(attribute='vlan.vid') %} +{% if prefix.family.value == 4 %} +ip prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} +{% else %} +ipv6 prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} +{% endif %} +{% endfor %} + {% if wg_net is defined %} ip prefix-list vpn permit {{ wg_net | ipaddr('subnet') }} {% endif %} @@ -278,6 +302,20 @@ route-map inside-import permit 20 route-map inside-import permit 21 match ipv6 address prefix-list office +{% for vrf in vrfs.values() | selectattr('custom_fields.imports') %} +route-map {{ vrf.name }}-import permit 10 + match ip address prefix-list default +route-map {{ vrf.name }}-import permit 11 + match ipv6 address prefix-list default +{% for import in vrf.custom_fields.imports %} +route-map {{ vrf.name }}-import permit {{ 100 + 10*loop.index0 }} + match ip address prefix-list {{ import.name }} +route-map {{ vrf.name }}-import permit {{ 101 + 10*loop.index0 }} + match ipv6 address prefix-list {{ import.name }} +{% endfor %} + +{% endfor %} + # Route maps for advertised and received routes. # Default VRF ↔ fabric. route-map default->fabric permit 10 diff --git a/roles/facts/tasks/main.yml b/roles/facts/tasks/main.yml index 568fe41..5f7ce96 100644 --- a/roles/facts/tasks/main.yml +++ b/roles/facts/tasks/main.yml @@ -3,6 +3,7 @@ set_fact: vlans: '{{ query("netbox.netbox.nb_lookup", "vlans", api_filter="group=new-net", raw_data=true) | sort(attribute="vid") }}' + vrfs: '{{ query("netbox.netbox.nb_lookup", "vrfs", raw_data=true) | list2dict("name") }}' prefixes: '{{ query("netbox.netbox.nb_lookup", "prefixes", raw_data=true) | sort(attribute="prefix") | sort(attribute="family.value") }}'