Factor frr role from debian, ceph and proxmox

Consolidate base system and networking setup into debian role and BGP
configuration into frr role. Add facts role to collect data from NetBox
once to avoid many slow lookups. Also many other tweaks and cleanups.
This commit is contained in:
Timotej Lazar 2024-05-18 18:35:41 +02:00
parent 256dae2955
commit 25bcddede1
31 changed files with 167 additions and 312 deletions

View file

@ -1,16 +0,0 @@
[Unit]
Description=OpenBSD Secure Shell server (management VRF)
After=network.target auditd.service
[Service]
ExecStartPre=/usr/sbin/sshd -t
ExecStart=ip vrf exec mgmt /usr/sbin/sshd -f /etc/ssh/sshd_config.mgmt
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
[Install]
WantedBy=multi-user.target

View file

@ -1,15 +0,0 @@
# This is for sshd in management VRF, for ansible and other not-really-OOB stuff.
PidFile none
UsePAM no
Subsystem sftp /usr/lib/openssh/sftp-server
# Only allow pubkey auth.
KbdInteractiveAuthentication no
PasswordAuthentication no
PermitRootLogin prohibit-password
# Disable what we can.
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no

View file

@ -1,8 +1,3 @@
- name: reboot
reboot:
- name: reload interfaces
command: ifreload -a
- name: reload frr
command: /usr/lib/frr/frr-reload.py --reload /etc/frr/frr.conf
when: "'handler' not in ansible_skip_tags"

View file

@ -5,4 +5,4 @@
mode: 0640
owner: root
group: www-data
when: inventory_hostname == primary
when: is_primary

View file

@ -1,22 +0,0 @@
- name: Install FRR
package:
name: frr
- name: Configure FRR
template:
dest: /etc/frr/frr.conf
src: frr.conf.j2
mode: 0644
notify: reload frr
- name: Set datacenter defaults for FRR
lineinfile:
path: /etc/frr/daemons
line: 'frr_profile="datacenter"'
regexp: '#?frr_profile="datacenter"'
- name: Enable FRR
service:
name: frr
enabled: yes
state: started

View file

@ -1,11 +1,7 @@
- name: Get all nodes in my cluster
set_fact:
nodes: "{{ groups['cluster_'+cluster] | map('extract', hostvars) }}"
# choose a node for tasks that should only run on (any) one node, e.g. when writing to /etc/pve
- name: Select primary node
- name: Select the primary node
set_fact:
primary: '{{ nodes | map(attribute="inventory_hostname") | sort | first }}'
is_primary: '{{ inventory_hostname == (nodes | map(attribute="inventory_hostname") | sort | first) }}'
- name: Disable enterprise repositories
apt_repository:
@ -22,27 +18,15 @@
apt_repository:
repo: 'deb http://download.proxmox.com/debian/pve {{ ansible_distribution_release }} pve-no-subscription'
- name: Set hostname
hostname:
name: '{{ inventory_hostname }}'
- name: Add rules to rename network interfaces
template:
dest: /etc/udev/rules.d/10-network.rules
src: 10-network.rules.j2
mode: 0644
notify: reboot
- name: Set up sysctls
copy:
dest: /etc/sysctl.d/local.conf
src: sysctl.conf
- name: Set up interfaces
- name: Set VXLAN local tunnel IP
template:
dest: /etc/network/interfaces.d/real.intf
src: real.intf.j2
mode: 0644
dest: /etc/network/interfaces.d/loopback.intf
src: loopback.intf.j2
notify: reload interfaces
- name: Set up bridges
@ -52,10 +36,6 @@
mode: 0644
notify: reload interfaces
- include_tasks: mgmt.yml
- include_tasks: firewall.yml
- include_tasks: frr.yml
- include_tasks: user.yml

View file

@ -1,24 +0,0 @@
# We could probably avoid rebooting in some cases, but those should never happen
# in normal operation anyway. This way all setup is done before rebooting once.
- name: Configure SSH instance in management VRF
copy:
dest: /etc/ssh/
src: sshd_config.mgmt
mode: 0644
notify: reboot
- name: Set up a SSH instance in management VRF
copy:
dest: /etc/systemd/system/
src: sshd@mgmt.service
mode: 0644
notify: reboot
- name: Enable management SSH
service:
name: sshd@mgmt
enabled: yes
notify: reboot
- meta: flush_handlers

View file

@ -1,17 +1,19 @@
# synchronize user and group data from LDAP when sync-ldap context key is set to a realm
- block:
- name: Set up LDAP user synchronization
when: '"sync-ldap" in hostvars[inventory_hostname]'
block:
- name: Install LDAP sync script
template:
dest: /usr/local/bin/sync-ldap.py
src: sync-ldap.py.j2
mode: 0700
when: primary == inventory_hostname
when: is_primary
- name: Remove LDAP sync script
file:
path: /usr/local/bin/sync-ldap.py
state: absent
when: primary != inventory_hostname
when: not is_primary
- name: Configure cronjob
cron:
@ -21,5 +23,4 @@
cron_file: sync-ldap
hour: "2"
minute: "51"
state: '{{ "present" if inventory_hostname == primary else "absent" }}'
when: '"sync-ldap" in hostvars[inventory_hostname]'
state: '{{ "present" if is_primary else "absent" }}'

View file

@ -1,5 +1,3 @@
{% set services = query('netbox.netbox.nb_lookup', 'clusters', raw_data=true, api_filter='name='+cluster)
| map(attribute='custom_fields.services') | flatten -%}
[OPTIONS]
enable: 1
@ -10,7 +8,7 @@ IN Ping(ACCEPT) -log nolog # don’t be rude
IN SSH(ACCEPT) -i mgmt # for ansible etc.
IN ACCEPT -source {{ nodes | map('device_address') | flatten | selectattr('family.value', '==', 4) | map(attribute='address') | join(',') }} # my cluster
IN ACCEPT -source {{ nodes | map('device_address') | flatten | selectattr('family.value', '==', 6) | map(attribute='address') | join(',') }} # my cluster
{% for service in services %}
{% for service in cluster.custom_fields.services %}
{% set prefixes = service | allowed_prefixes %}
{% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %}
{% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %}

View file

@ -1,44 +0,0 @@
frr defaults datacenter
service integrated-vtysh-config
log syslog
# We only have the default route, so allow talking to BGP peers over it.
ip nht resolve-via-default
router bgp {{ asn.asn }}
bgp bestpath as-path multipath-relax
neighbor fabric peer-group
neighbor fabric remote-as external
neighbor fabric capability extended-nexthop
{% for iface in interfaces | rejectattr('name', '==', 'lo') | rejectattr('mgmt_only') | rejectattr('vrf') %}
neighbor {{ iface.name }} interface peer-group fabric
neighbor {{ iface.name }} bfd
{% endfor %}
address-family ipv4 unicast
redistribute connected route-map loopback
neighbor fabric activate
neighbor fabric route-map local out
exit-address-family
address-family ipv6 unicast
redistribute connected route-map loopback
neighbor fabric activate
neighbor fabric route-map local out
exit-address-family
address-family l2vpn evpn
neighbor fabric activate
neighbor fabric route-map local out
advertise-all-vni
exit-address-family
bgp as-path access-list local permit ^$
route-map loopback permit 1
match interface lo
route-map local permit 1
match as-path local

View file

@ -1,8 +1,6 @@
{% set my_cluster = query('netbox.netbox.nb_lookup', 'clusters', raw_data=true,
api_filter='name='~cluster) | first -%}
# bridges must be named vmbrN for proxmox to care
{% for vlan in my_cluster.custom_fields.vlans | default([], true) | sort(attribute='vid') %}
{# bridges must be named vmbrN for proxmox to care #}
{% for vlan in cluster.custom_fields.vlans | default([], true) | sort(attribute='vid') %}
# bridge and VNI for {{ vlan.name }}
auto vmbr{{ vlan.vid }}
iface vmbr{{ vlan.vid }}
# {{ vlan.name }}

View file

@ -0,0 +1,7 @@
{% set lo = hostvars[inventory_hostname].interfaces | selectattr('name', '==', 'lo') | first %}
{% set vxlan_ip = lo.ip_addresses | selectattr('family.value', '==', 4)
| map(attribute='address') | first
| ipaddr('address') %}
iface lo
# without this the local tunnel IP is sometimes advertised as 0.0.0.0 on boot
vxlan-local-tunnelip {{ vxlan_ip }}

View file

@ -1,27 +0,0 @@
# Management VRF.
auto mgmt
iface mgmt
address 127.0.0.1/8
address ::1/128
vrf-table auto
{% for iface in interfaces | rejectattr('mgmt_only') %}
auto {{ iface.name }}
iface {{ iface.name }} inet {% if iface.name == 'lo' %}loopback{% else %}static{% endif +%}
{% if iface.mtu %}
mtu {{ iface.mtu }}
{% endif %}
{% if iface.vrf %}
vrf {{ iface.vrf.name }}
{% endif %}
{% for ip in iface.ip_addresses %}
address {{ ip.address }}
{% endfor %}
{% if iface.custom_fields.gateway %}
gateway {{ iface.custom_fields.gateway.address | ipaddr('address') }}
{% endif %}
{% if iface.name == 'lo' %}
vxlan-local-tunnelip {{ iface.ip_addresses | selectattr('family.value', '==', 4) | map(attribute='address') | sort | first | ipaddr('address') }}
{% endif %}
{% endfor %}

View file

@ -6,7 +6,7 @@ import re
import ldap3
{% set password = lookup('passwordstore', "cluster/"+cluster, returnall=true) | from_yaml %}
{% set password = lookup('passwordstore', "cluster/"+cluster.name, returnall=true) | from_yaml %}
realm = '{{ hostvars[inventory_hostname]["sync-ldap"] }}'
ldap_host = '{{ domain }}'
ldap_user = '{{ password.ldap_user }}'