diff --git a/filter_plugins/enquote.py b/filter_plugins/enquote.py new file mode 100644 index 0000000..576bf3f --- /dev/null +++ b/filter_plugins/enquote.py @@ -0,0 +1,16 @@ +class FilterModule: + def filters(self): + return { + "enquote": enquote, + } + + +def enquote(string, delimiter='"', escape="\\"): + translation = str.maketrans( + { + delimiter: f"{escape}{delimiter}", + escape: f"{escape}{escape}", + } + ) + escaped = string.translate(translation) + return f"{delimiter}{escaped}{delimiter}" diff --git a/filter_plugins/suffix.py b/filter_plugins/suffix.py new file mode 100644 index 0000000..53c04ac --- /dev/null +++ b/filter_plugins/suffix.py @@ -0,0 +1,9 @@ +class FilterModule: + def filters(self): + return { + "suffix": suffix, + } + + +def suffix(value, suffix): + return value + suffix diff --git a/hosts b/hosts index 1730956..11b7e06 100644 --- a/hosts +++ b/hosts @@ -127,6 +127,10 @@ radius-fleming.adm.auro.re dns-1.int.infra.auro.re isp-1.rtr.infra.auro.re isp-2.rtr.infra.auro.re +edge-1.rtr.infra.auro.re +edge-2.rtr.infra.auro.re +infra-1.rtr.infra.auro.re +infra-2.rtr.infra.auro.re dhcp-1.isp.auro.re dhcp-2.isp.auro.re radius-fleming-backup.adm.auro.re diff --git a/playbooks/bird.yml b/playbooks/bird.yml new file mode 100755 index 0000000..e7991df --- /dev/null +++ b/playbooks/bird.yml @@ -0,0 +1,194 @@ +#!/usr/bin/env ansible-playbook +--- +- hosts: + - isp-1.rtr.infra.auro.re + - isp-2.rtr.infra.auro.re + vars: + bird__router_ids: + isp-1.rtr.infra.auro.re: 10.136.0.1 + isp-2.rtr.infra.auro.re: 10.136.0.2 + bird__router_id: "{{ bird__router_ids[inventory_hostname] }}" + bird__ospf_broadcast_interfaces: + ens20: null + bird__ospf_stub_interfaces: + - client-0 + - client-1 + - client-2 + - client-3 + - client-4 + bird__radv_interfaces: + client-0: + prefix: + - 2a09:6841::/56 + domain_search: + - client-0.isp.auro.re + client-1: + prefix: + - 2a09:6841:0:100::/56 + domain_search: + - client-1.isp.auro.re + client-2: + prefix: + - 2a09:6841:0:200::/56 + domain_search: + - client-2.isp.auro.re + client-3: + prefix: + - 2a09:6841:0:300::/56 + domain_search: + - client-3.isp.auro.re + client-4: + prefix: + - 2a09:6841:0:400::/56 + domain_search: + - client-4.isp.auro.re + bird__radv_dns_servers: + - 2a09:6840:128::127 + roles: + - bird + +- hosts: + - infra-1.rtr.infra.auro.re + - infra-2.rtr.infra.auro.re + vars: + bird__router_ids: + infra-1.rtr.infra.auro.re: 10.203.1.3 + infra-2.rtr.infra.auro.re: 10.203.1.4 + bird__router_id: "{{ bird__router_ids[inventory_hostname] }}" + bird__ospf_broadcast_interfaces: + ens19: null + bird__ospf_stub_interfaces: + - ens20 + - ens21 + - ens22 + - ens23 + - ens1 + - ens2 + - ens1s3 + roles: + - bird + +- hosts: + - edge-1.rtr.infra.auro.re + - edge-2.rtr.infra.auro.re + vars: + bird__router_ids: + edge-1.rtr.infra.auro.re: 10.203.1.1 + edge-2.rtr.infra.auro.re: 10.203.1.2 + bird__asn: + aurore: 43619 + crans: 204515 + zayo: 8218 + viarezo: 212424 + bird__orig_prefixes: + aurore: + - 45.66.108.0/22 + - 2a09:6840::/29 + crans: + - 185.230.76.0/22 + - 2a0c:700::/32 + viarezo: + - 138.195.144.0/20 + - 192.159.121.0/24 + - 2a0c:b641:2f0::/44 + martians: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + - 100.64.0.0/10 + - 127.0.0.0/8 + - 169.254.0.0/16 + - 192.0.0.0/24 + - 192.0.2.0/24 + - 198.18.0.0/15 + - 198.51.100.0/24 + - 203.0.113.0/24 + - 224.0.0.0/4 + - 240.0.0.0/4 + - ::/128 + - ::1/128 + - ::ffff:0:0/96 + - ::/96 + - 100::/64 + - 2001:10::/28 + - 2001:db8::/32 + - fc00::/7 + - fe80::/10 + - fec0::/10 + - ff00::/8 + bird__router_id: "{{ bird__router_ids[inventory_hostname] }}" + bird__ospf_broadcast_interfaces: + ens22: null + bird__ospf_stub_networks: + - 0.0.0.0/0 + - ::/0 + bird__bgp_sessions: + - name: zayo + local: + address: + - 83.167.52.69 + - 2001:1b48:2:103::d7:2 + as: "{{ bird__asn.aurore }}" + remote: + address: + - 83.167.52.68 + - 2001:1b48:2:103::d7:1 + as: "{{ bird__asn.zayo }}" + import: + - accept: true + export: + - prefix: "{{ ['aurore', 'crans', 'viarezo'] + | map('extract', bird__orig_prefixes) + | flatten }}" + sub: true + accept: true + - accept: false + - name: crans + local: + address: + - 185.230.79.254 + - 2a0c:700:28::2 + as: "{{ bird__asn.aurore }}" + remote: + address: + - 185.230.79.253 + - 2a0c:700:28::1 + as: "{{ bird__asn.crans }}" + import: + - prefix: "{{ bird__orig_prefixes.crans }}" + sub: true + accept: true + - accept: false + export: + - accept: true + - name: viarezo + local: + address: + - 192.159.121.134 + - 2a0c:b641:2ff::6 + as: "{{ bird__asn.aurore }}" + remote: + address: + - 192.159.121.133 + - 2a0c:b641:2ff::5 + as: "{{ bird__asn.viarezo }}" + import: + - prefix: "{{ bird__orig_prefixes.martians }}" + accept: false + - prefix: "{{ bird__orig_prefixes.viarezo }}" + sub: true + negate: true + local_pref: 50 + - accept: true + export: + - prefix: "{{ bird__orig_prefixes.aurore }}" + as_prepend: + asn: "{{ bird__asn.aurore }}" + size: 3 + - accept: true + bird__static_unreachable: + - 45.66.108.0/22 + - 2a09:6840::/29 + roles: + - bird +... diff --git a/playbooks/ifupdown2.yml b/playbooks/ifupdown2.yml index fcd816c..716458a 100755 --- a/playbooks/ifupdown2.yml +++ b/playbooks/ifupdown2.yml @@ -7,6 +7,10 @@ - dhcp-2.isp.auro.re - isp-1.rtr.infra.auro.re - isp-2.rtr.infra.auro.re + - edge-1.rtr.infra.auro.re + - edge-2.rtr.infra.auro.re + - infra-1.rtr.infra.auro.re + - infra-2.rtr.infra.auro.re vars: # TODO: netbox ifupdown2__hosts: @@ -115,6 +119,11 @@ - 2a09:6840:128::255/56 - 10.128.0.255/16 ens19: null + ens20: + forward: true + addresses: + - 2a09:6840:136:0:1::1/56 + - 10.136.0.1/16 clients: bridge_vlan_aware: true bridge_ports: @@ -158,6 +167,11 @@ - 2a09:6840:128::158/56 - 10.128.0.158/16 ens19: null + ens20: + forward: true + addresses: + - 2a09:6840:136:0:2::1/56 + - 10.136.0.2/16 clients: bridge_vlan_aware: true bridge_ports: @@ -189,6 +203,88 @@ vlan_id: 1004 vlan_raw_device: clients ipv6_addrgen: false + edge-1.rtr.infra.auro.re: + ens18: + gateways: + - 2a09:6840:128::254 + - 10.128.0.254 + addresses: + - 2a09:6840:128::186/56 + - 10.128.0.186/16 + ens19: null # crans + ens20: null # vr + ens21: null # zayo + ens22: # backbone + addresses: + - 2a09:6840:203:1:1::1/64 + - 10.203.1.1/16 + edge-2.rtr.infra.auro.re: + ens18: + gateways: + - 2a09:6840:128::254 + - 10.128.0.254 + addresses: + - 2a09:6840:128::228/56 + - 10.128.0.228/16 + ens19: null # crans + ens20: null # vr + ens21: null # zayo + ens22: # backbone + addresses: + - 2a09:6840:203:1:2::1/64 + - 10.203.1.2/16 + infra-1.rtr.infra.auro.re: + ens18: + gateways: + - 2a09:6840:128::254 + - 10.128.0.254 + addresses: + - 2a09:6840:128::2:76/56 + - 10.128.2.76/16 + ens19: + addresses: + - 2a09:6840:1:3::1/64 + - 10.203.1.3/16 + ens20: + ipv6_addrgen: false + ens21: + ipv6_addrgen: false + ens22: + ipv6_addrgen: false + ens23: + ipv6_addrgen: false + ens1: + ipv6_addrgen: false + ens2: + ipv6_addrgen: false + enp1s3: + ipv6_addrgen: false + infra-2.rtr.infra.auro.re: + ens18: + gateways: + - 2a09:6840:128::254 + - 10.128.0.254 + addresses: + - 2a09:6840:128::2:27/56 + - 10.128.2.27/16 + ens19: + addresses: + - 2a09:6840:1:4::1/64 + - 10.203.1.4/16 + ens20: + ipv6_addrgen: false + ens21: + ipv6_addrgen: false + ens22: + ipv6_addrgen: false + ens23: + ipv6_addrgen: false + ens1: + ipv6_addrgen: false + ens2: + ipv6_addrgen: false + enp1s3: + ipv6_addrgen: false ifupdown2__interfaces: "{{ ifupdown2__hosts[inventory_hostname] }}" roles: - ifupdown2 @@ -200,6 +296,10 @@ - dhcp-2.isp.auro.re - isp-1.rtr.infra.auro.re - isp-2.rtr.infra.auro.re + - edge-1.rtr.infra.auro.re + - edge-2.rtr.infra.auro.re + - infra-1.rtr.infra.auro.re + - infra-2.rtr.infra.auro.re vars: resolvconf__nameservers: - 2a09:6840:128::127 diff --git a/playbooks/keepalived.yml b/playbooks/keepalived.yml index e8239a1..044b01b 100755 --- a/playbooks/keepalived.yml +++ b/playbooks/keepalived.yml @@ -5,27 +5,87 @@ - isp-2.rtr.infra.auro.re vars: keepalived__virtual_router_id: 80 - keepalived__interface: ens18 + keepalived__interface: ens20 keepalived__virtual_addresses: client-0: - 100.64.0.1/27 - - 2a09:6841::/56 + - 2a09:6841::1/56 - fe80::1/10 client-1: - 100.64.0.33/27 - - 2a09:6841:0:100::/56 + - 2a09:6841:0:100::1/56 - fe80::1/10 client-2: - 100.64.0.65/27 - - 2a09:6841:0:100::/56 + - 2a09:6841:0:200::1/56 - fe80::1/10 client-3: - 100.64.0.97/27 - - 2a09:6841:0:200::/56 + - 2a09:6841:0:300::1/56 - fe80::1/10 client-4: - 100.64.0.129/27 - - 2a09:6841:0:300::/56 + - 2a09:6841:0:400::1/56 + - fe80::1/10 + roles: + - keepalived + +- hosts: + - edge-1.rtr.infra.auro.re + - edge-2.rtr.infra.auro.re + vars: + keepalived__virtual_router_id: 81 + keepalived__interface: ens22 + keepalived__virtual_addresses: + ens19: + - 185.230.79.254/29 + - 2a0c:700:28::2/64 + - fe80::1/10 + ens20: + - 192.159.121.134/30 + - 2a0c:b641:2ff::6/126 + - fe80::1/10 + ens21: + - 83.167.52.69/31 + - 2001:1b48:2:103::d7:2/126 + - fe80::1/10 + roles: + - keepalived + +- hosts: + - infra-1.rtr.infra.auro.re + - infra-2.rtr.infra.auro.re + vars: + keepalived__virtual_router_id: 82 + keepalived__interface: ens19 + keepalived__virtual_addresses: + ens20: + - 10.204.0.1/16 + - 2a09:6840:204::1/64 + - fe80::1/10 + ens21: + - 10.205.0.1/16 + - 2a09:6840:205::1/64 + - fe80::1/10 + ens22: + - 10.206.0.1/16 + - 2a09:6840:206::1/64 + - fe80::1/10 + ens23: + - 10.207.0.1/16 + - 2a09:6840:207::1/64 + - fe80::1/10 + ens1: + - 10.208.0.1/16 + - 2a09:6840:208::1/64 + - fe80::1/10 + ens2: + - 10.209.0.1/16 + - 2a09:6840:209::1/64 + - fe80::1/10 + enp1s3: + - 10.210.0.1/16 + - 2a09:6840:210::1/64 - fe80::1/10 roles: - keepalived diff --git a/roles/bird/defaults/main.yml b/roles/bird/defaults/main.yml new file mode 100644 index 0000000..1c72633 --- /dev/null +++ b/roles/bird/defaults/main.yml @@ -0,0 +1,15 @@ +--- +bird__ospf_stub_interfaces: [] +bird__ospf_stub_networks: [] +bird__ospf_broadcast_interfaces: {} +bird__ospf_hello: 2 +bird__ospf_retransmit: 5 +bird__ospf_wait: 10 +bird__ospf_dead: 20 +bird__radv_interfaces: {} +bird__radv_dns_servers: [] +bird__radv_max_interval: 5 +bird__static_unreachable: [] +bird__bgp_sessions: [] +bird__prometheus_listen_address: 0.0.0.0:9324 +... diff --git a/roles/bird/handlers/main.yml b/roles/bird/handlers/main.yml new file mode 100644 index 0000000..8b3f2a0 --- /dev/null +++ b/roles/bird/handlers/main.yml @@ -0,0 +1,11 @@ +--- +- name: Reload bird + systemd: + name: bird.service + state: reloaded + +- name: Restart prometheus-bird-exporter + systemd: + name: prometheus-bird-exporter.service + state: restarted +... diff --git a/roles/bird/tasks/main.yml b/roles/bird/tasks/main.yml new file mode 100644 index 0000000..0f7bda1 --- /dev/null +++ b/roles/bird/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Install bird + apt: + name: + - bird2 + - prometheus-bird-exporter + +- name: Configure bird + template: + src: bird.conf.j2 + dest: /etc/bird/bird.conf + owner: root + group: bird + mode: u=rw,g=r,o= + notify: + - Reload bird + +- name: Configure prometheus-bird-exporter + template: + src: prometheus-bird-exporter.j2 + dest: /etc/default/prometheus-bird-exporter + owner: root + group: root + mode: u=rw,g=r,o= + notify: + - Restart prometheus-bird-exporter + +- name: Enable and start bird + systemd: + name: bird.service + state: started + enabled: true + +- name: Enable and start prometheus-bird-exporter + systemd: + name: prometheus-bird-exporter.service + state: started + enabled: true + +... diff --git a/roles/bird/templates/bird.conf.j2 b/roles/bird/templates/bird.conf.j2 new file mode 100644 index 0000000..c7e30ef --- /dev/null +++ b/roles/bird/templates/bird.conf.j2 @@ -0,0 +1,209 @@ +{{ ansible_managed | comment }} + +log syslog all; + +router id {{ bird__router_id }}; + +protocol device { + scan time 10; +} + +protocol direct { + ipv4; + ipv6; +} + +protocol kernel kernel4 { + ipv4 { + import all; + export where source !~ [ RTS_DEVICE, RTS_STATIC ]; + }; +} + +protocol kernel kernel6 { + ipv6 { + import all; + export where source !~ [ RTS_DEVICE, RTS_STATIC ]; + }; +} + + +{% if bird__static_unreachable | ansible.utils.ipv4 %} +protocol static unreachable4 { + ipv4 { + import all; + }; +{% for route in bird__static_unreachable | ansible.utils.ipv4 %} + route {{ route }} unreachable; +{% endfor %} +} +{% endif %} + +{% if bird__static_unreachable | ansible.utils.ipv6 %} +protocol static unreachable6 { + ipv6 { + import all; + }; +{% for route in bird__static_unreachable | ansible.utils.ipv6 %} + route {{ route }} unreachable; +{% endfor %} +} +{% endif %} + +{% if bird__ospf_broadcast_interfaces %} +protocol ospf v2 ospf4 { + + ipv4 { + import all; + export where source ~ [ RTS_STATIC, RTS_DEVICE ]; + }; + + area 0 { +{% for network in bird__ospf_stub_networks | ansible.utils.ipv4 %} + stubnet {{ network }}; +{% endfor %} +{% for name, iface in bird__ospf_broadcast_interfaces.items() %} + interface {{ name | enquote }} { + type broadcast; + hello {{ iface.hello | default(bird__ospf_hello) | int }}; + retransmit {{ iface.retransmit + | default(bird__ospf_retransmit) + | int }}; + wait {{ iface.wait | default(bird__ospf_wait) | int }}; + dead {{ iface.dead | default(bird__ospf_dead) | int }}; + }; +{% endfor %} +{% for name in bird__ospf_stub_interfaces %} + interface {{ name | enquote }} { + stub; + }; +{% endfor %} + }; + +} +{% endif %} + +{% if bird__ospf_broadcast_interfaces %} +protocol ospf v3 ospf6 { + + ipv6 { + import all; + export where source ~ [ RTS_STATIC, RTS_DEVICE ]; + }; + + area 0 { +{% for network in bird__ospf_stub_networks | ansible.utils.ipv6 %} + stubnet {{ network }}; +{% endfor %} +{% for name, iface in bird__ospf_broadcast_interfaces.items() %} + interface {{ name | enquote }} { + type broadcast; + hello {{ iface.hello | default(bird__ospf_hello) | int }}; + retransmit {{ iface.retransmit + | default(bird__ospf_retransmit) + | int }}; + wait {{ iface.wait | default(bird__ospf_wait) | int }}; + dead {{ iface.dead | default(bird__ospf_dead) | int }}; + }; +{% endfor %} +{% for name in bird__ospf_stub_interfaces %} + interface {{ name | enquote }} { + stub; + }; +{% endfor %} + }; + +} +{% endif %} + +{% macro bird_filter(filter, last) %} +{% if filter.as_prepend is defined %} +{% for _ in range(filter.as_prepend.size) %} +bgp_path.prepend({{ filter.as_prepend.asn }}); +{% endfor %} +{% endif %} +{% if filter.local_pref is defined %} +bgp_local_pref = {{ filter.local_pref }}; +{% endif %} +{% if filter.accept is defined %} +{{ filter.accept | ternary("accept", "reject") }}; +{% endif %} +{% endmacro %} + +{% for session in bird__bgp_sessions %} +{% for version in [4, 6] %} +{% for direction in ["import", "export"] %} +filter bgp{{ version }}_{{ direction }}_{{ session.name }} { +{% for filter in session[direction] %} +{% if filter.prefix | default([]) %} +{% set op = + filter.negate + | default(False) + | ternary("!~", "~") %} +{% set networks = + filter.prefix + | default([]) + | ansible.utils.ipaddr(version=version) + | map("suffix", filter.sub + | default(False) + | ternary("+", "")) %} +{% if networks %} + if net {{ op }} [ {{ networks | join(", ") }} ] then { + {{ bird_filter(filter) | indent(8) }} + } +{% endif %} +{% else %} + {{ bird_filter(filter) | indent(4) }} +{% endif %} +{% endfor %} +} + +{% endfor %} +{% endfor %} +{% endfor %} + +{% for session in bird__bgp_sessions %} +{% for local_address in session.local.address %} +{% set version = + local_address + | ansible.utils.ipaddr(query="version") %} +{% set remote_address = + session.remote.address + | ansible.utils.ipaddr(version=version) + | first %} +protocol bgp bgp{{ version }}_{{ session.name }} { + + local {{ local_address }} as {{ session.local.as }}; + neighbor {{ remote_address }} as {{ session.remote.as }}; + + {{ "ipv4" if version == 4 else "ipv6" }} { + import filter bgp{{ version }}_import_{{ session.name }}; + export filter bgp{{ version }}_export_{{ session.name }}; + }; + +} + +{% endfor %} +{% endfor %} + +{% if bird__radv_interfaces %} +protocol radv { + +{% for name, iface in bird__radv_interfaces.items() %} + interface {{ name | enquote }} { + max ra interval {{ bird__radv_max_interval | int }}; +{% for prefix in iface.prefix | default([]) %} + prefix {{ prefix | ipaddr }}; +{% endfor %} +{% for domain in iface.domain_search | default([]) %} + dnssl {{ domain | enquote }}; +{% endfor %} + }; +{% endfor %} + +{% for address in bird__radv_dns_servers %} + rdnss {{ address | ipaddr }}; +{% endfor %} + +} +{% endif %} diff --git a/roles/bird/templates/prometheus-bird-exporter.j2 b/roles/bird/templates/prometheus-bird-exporter.j2 new file mode 100644 index 0000000..86006b0 --- /dev/null +++ b/roles/bird/templates/prometheus-bird-exporter.j2 @@ -0,0 +1,3 @@ +{{ ansible_managed | comment }} + +ARGS="-format.new -bird.v2 -web.listen-address {{ bird__prometheus_listen_address }}" diff --git a/roles/keepalived/defaults/main.yml b/roles/keepalived/defaults/main.yml index ee034f3..4aeec8c 100644 --- a/roles/keepalived/defaults/main.yml +++ b/roles/keepalived/defaults/main.yml @@ -1,5 +1,6 @@ --- keepalived__virtual_addresses: {} +keepalived__virtual_routes: {} keepalived__notify_master: [] keepalived__notify_backup: [] keepalived__notify_fault: [] diff --git a/roles/keepalived/templates/keepalived.conf.j2 b/roles/keepalived/templates/keepalived.conf.j2 index c99ae10..dabd0e2 100644 --- a/roles/keepalived/templates/keepalived.conf.j2 +++ b/roles/keepalived/templates/keepalived.conf.j2 @@ -55,6 +55,15 @@ vrrp_instance instance_v4 { {{ address }} dev {{ dev }} {% endif %} {% endfor %} +{% endfor %} + } + virtual_routes { +{% for dev, addresses in keepalived__virtual_routes.items() %} +{% for address in addresses %} +{% if address | ansible.utils.ipv4 %} + {{ address }} dev {{ dev }} +{% endif %} +{% endfor %} {% endfor %} } {% if not (ipv4_enabled and ipv6_enabled) %} @@ -81,6 +90,15 @@ vrrp_instance instance_v6 { {{ address }} dev {{ dev }} {% endif %} {% endfor %} +{% endfor %} + } + virtual_routes { +{% for dev, addresses in keepalived__virtual_routes.items() %} +{% for address in addresses %} +{% if address | ansible.utils.ipv6 %} + {{ address }} dev {{ dev }} +{% endif %} +{% endfor %} {% endfor %} } {% if not (ipv4_enabled and ipv6_enabled) %}