From 393614aa79db7b45cf996dbb7c3f55baa09d0812 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Mon, 17 Jun 2024 09:52:56 +0200 Subject: [PATCH 1/3] alpine: configure unattended upgrades --- roles/alpine/files/unattended-upgrade | 8 ++++++++ roles/alpine/files/unattended-upgrade.logrotate | 3 +++ roles/alpine/tasks/main.yml | 13 +++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 roles/alpine/files/unattended-upgrade create mode 100644 roles/alpine/files/unattended-upgrade.logrotate diff --git a/roles/alpine/files/unattended-upgrade b/roles/alpine/files/unattended-upgrade new file mode 100644 index 0000000..542c6ba --- /dev/null +++ b/roles/alpine/files/unattended-upgrade @@ -0,0 +1,8 @@ +#!/bin/sh + +upgrade() { + echo "Starting upgrade on $(date)" + apk upgrade --update +} + +upgrade >> /var/log/unattended-upgrade.log diff --git a/roles/alpine/files/unattended-upgrade.logrotate b/roles/alpine/files/unattended-upgrade.logrotate new file mode 100644 index 0000000..5ddf0ef --- /dev/null +++ b/roles/alpine/files/unattended-upgrade.logrotate @@ -0,0 +1,3 @@ +/var/log/unattended-upgrade.log { + missingok +} diff --git a/roles/alpine/tasks/main.yml b/roles/alpine/tasks/main.yml index a774893..95ca640 100644 --- a/roles/alpine/tasks/main.yml +++ b/roles/alpine/tasks/main.yml @@ -22,6 +22,7 @@ name: - git - iproute2 + - logrotate - nftables - procps - rsync @@ -64,3 +65,15 @@ name: qemu-guest-agent enabled: yes state: started + +- name: Install automatic upgrade script + copy: + dest: /etc/periodic/weekly/ + src: unattended-upgrade + mode: 0755 + +- name: Configure log rotation for automatic upgrades + copy: + dest: /etc/logrotate.d/unattended-upgrade + src: unattended-upgrade.logrotate + mode: 0644 From 38c3464279c2dc10c21190cfb428f9a784083ea8 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 19 Jun 2024 13:14:51 +0200 Subject: [PATCH 2/3] alpine: assume one DNS name per host Avoid needless complexity. --- roles/dokuwiki/templates/dokuwiki.conf.j2 | 9 +++------ roles/forgejo/templates/forgejo.conf.j2 | 9 +++------ roles/netbox/tasks/app.yml | 2 +- roles/netbox/templates/netbox.conf.j2 | 9 +++------ roles/nginx/tasks/main.yml | 5 ++--- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/roles/dokuwiki/templates/dokuwiki.conf.j2 b/roles/dokuwiki/templates/dokuwiki.conf.j2 index fd483f3..b36a818 100644 --- a/roles/dokuwiki/templates/dokuwiki.conf.j2 +++ b/roles/dokuwiki/templates/dokuwiki.conf.j2 @@ -1,11 +1,10 @@ -{% for fqdn in fqdns %} server { listen 443 ssl http2; listen [::]:443 ssl http2; - server_name {{ fqdn }}; + server_name {{ dns_name }}; - ssl_certificate /etc/letsencrypt/live/{{ fqdn }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ fqdn }}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/{{ dns_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ dns_name }}/privkey.pem; client_max_body_size 100M; @@ -35,5 +34,3 @@ server { fastcgi_pass unix:/run/php-fpm.socket; } } - -{% endfor %} diff --git a/roles/forgejo/templates/forgejo.conf.j2 b/roles/forgejo/templates/forgejo.conf.j2 index df06a84..29fe6fa 100644 --- a/roles/forgejo/templates/forgejo.conf.j2 +++ b/roles/forgejo/templates/forgejo.conf.j2 @@ -1,10 +1,9 @@ -{% for fqdn in fqdns %} server { - server_name {{ fqdn }}; + server_name {{ dns_name }}; listen [::]:443 ssl ipv6only=off; - ssl_certificate /etc/letsencrypt/live/{{ fqdn }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ fqdn }}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/{{ dns_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ dns_name }}/privkey.pem; location / { proxy_pass http://unix:/var/lib/forgejo/socket; @@ -19,5 +18,3 @@ server { client_max_body_size 512M; } } - -{% endfor %} diff --git a/roles/netbox/tasks/app.yml b/roles/netbox/tasks/app.yml index 581cade..5ea5542 100644 --- a/roles/netbox/tasks/app.yml +++ b/roles/netbox/tasks/app.yml @@ -49,7 +49,7 @@ line: '{{ item.line }}' loop: - key: '^ALLOWED_HOSTS = ' - line: "ALLOWED_HOSTS = [{{ fqdns | map('regex_replace', '^(.*)$', '\"\\1\"') | join(', ') }}]" + line: "ALLOWED_HOSTS = ['{{ dns_name }}']" - key: 'USER.*PostgreSQL username' line: " 'USER': '{{ user }}', # PostgreSQL username" # XXX unnecessary? diff --git a/roles/netbox/templates/netbox.conf.j2 b/roles/netbox/templates/netbox.conf.j2 index c23c6e5..aa7ea00 100644 --- a/roles/netbox/templates/netbox.conf.j2 +++ b/roles/netbox/templates/netbox.conf.j2 @@ -1,10 +1,9 @@ -{% for fqdn in fqdns %} server { - server_name {{ fqdn }}; + server_name {{ dns_name }}; listen [::]:443 ssl ipv6only=off; - ssl_certificate /etc/letsencrypt/live/{{ fqdn }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ fqdn }}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/{{ dns_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ dns_name }}/privkey.pem; client_max_body_size 100m; @@ -19,5 +18,3 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } - -{% endfor %} diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml index 2bbc284..fb5d0ba 100644 --- a/roles/nginx/tasks/main.yml +++ b/roles/nginx/tasks/main.yml @@ -26,9 +26,8 @@ - name: Get LE certificate command: - cmd: certbot certonly --non-interactive --agree-tos --register-unsafely-without-email --webroot --webroot-path /srv/http -d {{ item }} - creates: '/etc/letsencrypt/renewal/{{ item }}.conf' - loop: '{{ fqdns }}' + cmd: certbot certonly --non-interactive --agree-tos --register-unsafely-without-email --webroot --webroot-path /srv/http -d {{ dns_name }} + creates: '/etc/letsencrypt/renewal/{{ dns_name }}.conf' - name: Enable certbot renewal cron: From 29598ef4bb4d1398d4fa47e68ea84257f2a8e627 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 19 Jun 2024 13:33:32 +0200 Subject: [PATCH 3/3] Rework service handling Allow running playbooks without NetBox access. Mainly to bootstrap NetBox itself. Would prefer not to access network from filter plugins, so maybe do that at some point also. --- filter_plugins/netbox.py | 18 ++++++++------- roles/alpine/templates/local.nft.j2 | 2 ++ roles/ceph/templates/nftables.conf.j2 | 2 +- roles/facts/tasks/main.yml | 33 +++++++++++++++------------ roles/proxmox/templates/cluster.fw.j2 | 6 ++--- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/filter_plugins/netbox.py b/filter_plugins/netbox.py index b519b3f..fac0a93 100644 --- a/filter_plugins/netbox.py +++ b/filter_plugins/netbox.py @@ -7,7 +7,8 @@ class FilterModule(object): '''Various utilities for manipulating NetBox data''' def __init__(self): - self.nb = pynetbox.api(os.getenv('NETBOX_API'), os.getenv('NETBOX_TOKEN')) + if 'NETBOX_API' in os.environ and 'NETBOX_TOKEN' in os.environ: + self.nb = pynetbox.api(os.getenv('NETBOX_API'), os.getenv('NETBOX_TOKEN')) def filters(self): return { @@ -37,11 +38,12 @@ class FilterModule(object): def allowed_prefixes(self, service): '''Return a list of allowed IP prefixes for the given service''' - service_data = self.nb.ipam.services.get(service['id']).custom_fields - if service_data['allowed_prefixes']: - yield from self.nb.ipam.prefixes.filter(id=[prefix['id'] for prefix in service_data['allowed_prefixes']]) - if service_data['allowed_vlans']: - yield from self.nb.ipam.prefixes.filter(vlan_id=[vlan['id'] for vlan in service_data['allowed_vlans']]) - if service_data['allowed_clusters']: - for device in self.nb.dcim.devices.filter(cluster_id=[cluster['id'] for cluster in service_data['allowed_clusters']]): + if 'custom_fields' in service: + service = service['custom_fields'] + if prefixes := service.get('allowed_prefixes'): + yield from self.nb.ipam.prefixes.filter(id=[prefix['id'] for prefix in prefixes]) + if vlans := service.get('allowed_vlans'): + yield from self.nb.ipam.prefixes.filter(vlan_id=[vlan['id'] for vlan in vlans]) + if clusters := service.get('allowed_clusters'): + for device in self.nb.dcim.devices.filter(cluster_id=[cluster['id'] for cluster in clusters]): yield from self.nb.ipam.ip_addresses.filter(role='loopback', device_id=device.id) diff --git a/roles/alpine/templates/local.nft.j2 b/roles/alpine/templates/local.nft.j2 index ad84ef6..4a1d32f 100644 --- a/roles/alpine/templates/local.nft.j2 +++ b/roles/alpine/templates/local.nft.j2 @@ -7,7 +7,9 @@ table inet filter { {% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %} {% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %} {% set ports = service.ports | compact_numlist %} +{% if 'name' in service %} # service {{ service.name }} +{% endif %} {% if prefixes4 or prefixes6 %} {% if prefixes4 %} ip saddr { {{ prefixes4 | join(', ') }} } tcp dport { {{ ports }} } accept diff --git a/roles/ceph/templates/nftables.conf.j2 b/roles/ceph/templates/nftables.conf.j2 index 5f1a706..be3e9ce 100644 --- a/roles/ceph/templates/nftables.conf.j2 +++ b/roles/ceph/templates/nftables.conf.j2 @@ -54,7 +54,7 @@ table inet filter { ip saddr @allowed accept # TODO remove exceptions ip6 saddr @allowed/6 accept # TODO remove exceptions -{% for service in cluster.custom_fields.services %} +{% for service in cluster_services %} {% set prefixes = service | allowed_prefixes %} {% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %} {% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %} diff --git a/roles/facts/tasks/main.yml b/roles/facts/tasks/main.yml index 13207d6..89fece7 100644 --- a/roles/facts/tasks/main.yml +++ b/roles/facts/tasks/main.yml @@ -1,18 +1,21 @@ # Make expensive lookups to NetBox once for later reference by any host. -- name: Lookup networks and prefixes - set_fact: - vlans: '{{ query("netbox.netbox.nb_lookup", "vlans", api_filter="group=new-net", raw_data=true) - | sort(attribute="vid") }}' - prefixes: '{{ query("netbox.netbox.nb_lookup", "prefixes", raw_data=true) - | sort(attribute="prefix") | sort(attribute="family.value") }}' +- when: lookup("env", "NETBOX_API") != "" + block: + - name: Lookup networks and prefixes + set_fact: + vlans: '{{ query("netbox.netbox.nb_lookup", "vlans", api_filter="group=new-net", raw_data=true) + | sort(attribute="vid") }}' + prefixes: '{{ query("netbox.netbox.nb_lookup", "prefixes", raw_data=true) + | sort(attribute="prefix") | sort(attribute="family.value") }}' -- name: Get my cluster and all nodes in it - set_fact: - cluster: '{{ query("netbox.netbox.nb_lookup", "clusters", raw_data=true, api_filter="name="+cluster) | first }}' - nodes: '{{ groups["cluster_"+cluster] | map("extract", hostvars) | rejectattr("is_virtual") }}' - when: cluster + - when: 'cluster is defined' + block: + - name: Get my cluster and all nodes in it + set_fact: + cluster: '{{ query("netbox.netbox.nb_lookup", "clusters", raw_data=true, api_filter="name="+cluster) | first }}' + nodes: '{{ groups["cluster_"+cluster] | map("extract", hostvars) | rejectattr("is_virtual") }}' -- name: Get my domain names if any - set_fact: - fqdns: '{{ interfaces | map(attribute="ip_addresses") | flatten - | map(attribute="dns_name") | reject("==", "") | sort | unique }}' + - name: Get cluster services + set_fact: + cluster_services: '{{ (cluster_services|default([])) + query("netbox.netbox.nb_lookup", "services", raw_data=true, api_filter="id="+item) }}' + loop: '{{ cluster.custom_fields.services | map(attribute="id") | map("string") }}' diff --git a/roles/proxmox/templates/cluster.fw.j2 b/roles/proxmox/templates/cluster.fw.j2 index 0970215..734d1e9 100644 --- a/roles/proxmox/templates/cluster.fw.j2 +++ b/roles/proxmox/templates/cluster.fw.j2 @@ -8,16 +8,16 @@ 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 cluster.custom_fields.services %} +{% for service in cluster_services %} {% set prefixes = service | allowed_prefixes %} {% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %} {% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %} {% set ports = service.ports | compact_numlist(range_delimiter=':') %} {% if prefixes4 %} -IN ACCEPT -source {{ prefixes4 | join(',') }} -p {{ service.protocol }} -dport {{ ports }} # {{ service.name }} +IN ACCEPT -source {{ prefixes4 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }} {% endif %} {% if prefixes6 %} -IN ACCEPT -source {{ prefixes6 | join(',') }} -p {{ service.protocol }} -dport {{ ports }} # {{ service.name }} +IN ACCEPT -source {{ prefixes6 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }} {% endif %} {% endfor %}