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/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..fa1c71a 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 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/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/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(' ') }}" } 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") }}'