Skip to content

Commit d105ec6

Browse files
committed
Add LKE IP automatic whitelist updating
Credits to my boys, closes #638
1 parent 07d8c86 commit d105ec6

File tree

10 files changed

+208
-15
lines changed

10 files changed

+208
-15
lines changed

ansible/group_vars/all/linode.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
---
2-
lke_all_addresses: "{{ lookup('ansible.builtin.url', 'https://geoip.linode.com/', wantlist=True) }}"
3-
lke_frankfurt_addresses: "{{ lke_all_addresses | select('search', '^.*Frankfurt.*$') | map('split', ',') | map(attribute=0) | list }}"
4-
lke_frankfurt_ipv4_addresses: "{{ lke_frankfurt_addresses | select('search', '^.*\\..*$') }}"
5-
lke_frankfurt_ipv6_addresses: "{{ lke_frankfurt_addresses | select('search', '^.*:.*$') }}"
2+
# Variables are now set by the linode-ips role to avoid repeated lookups.

ansible/group_vars/all/nftables.yml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,7 @@ nftables_configuration: |
3333
3434
{% if "databases" in group_names %}
3535
# Access control for database server
36-
set possible_lke_ipv4_addrs {
37-
type ipv4_addr
38-
flags interval
39-
elements = { {{ lke_frankfurt_ipv4_addresses | join(", ") }} }
40-
}
41-
42-
set possible_lke_ipv6_addrs {
43-
type ipv6_addr
44-
flags interval
45-
elements = { {{ lke_frankfurt_ipv6_addresses | join(", ") }} }
46-
}
36+
include "/etc/nftables/lke.nft"
4737
{% endif %}
4838
4939
chain input {

ansible/playbook.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
- name: Deploy common services
22
hosts: all
33
roles:
4+
- linode-ips
45
- common
56
- pydis-mtls
67
- wireguard
@@ -12,6 +13,7 @@
1213
- certbot
1314
- ci-user
1415
- alloy
16+
- lke-nftables-update
1517
- nftables
1618
- prometheus-node-exporter
1719
- fail2ban
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
- name: Fetch Linode GeoIP data
3+
ansible.builtin.uri:
4+
url: https://geoip.linode.com/
5+
return_content: true
6+
register: linode_geoip_response
7+
run_once: true
8+
delegate_to: localhost
9+
become: false
10+
tags:
11+
- role::linode-ips
12+
- role::lke-nftables-update
13+
- role::nftables
14+
- role::fail2ban
15+
16+
- name: Set LKE address facts
17+
ansible.builtin.set_fact:
18+
lke_all_addresses: "{{ linode_geoip_response.content.splitlines() }}"
19+
run_once: true
20+
delegate_to: localhost
21+
become: false
22+
tags:
23+
- role::linode-ips
24+
- role::lke-nftables-update
25+
- role::nftables
26+
- role::fail2ban
27+
28+
- name: Filter Frankfurt addresses
29+
ansible.builtin.set_fact:
30+
lke_frankfurt_addresses: "{{ lke_all_addresses | select('search', '^.*Frankfurt.*$') | map('split', ',') | map(attribute=0) | list }}"
31+
run_once: true
32+
delegate_to: localhost
33+
become: false
34+
tags:
35+
- role::linode-ips
36+
- role::lke-nftables-update
37+
- role::nftables
38+
- role::fail2ban
39+
40+
- name: Set LKE IPv4 and IPv6 facts
41+
ansible.builtin.set_fact:
42+
lke_frankfurt_ipv4_addresses: "{{ lke_frankfurt_addresses | select('search', '^.*\\..*$') | list }}"
43+
lke_frankfurt_ipv6_addresses: "{{ lke_frankfurt_addresses | select('search', '^.*:.*$') | list }}"
44+
run_once: true
45+
delegate_to: localhost
46+
become: false
47+
tags:
48+
- role::linode-ips
49+
- role::lke-nftables-update
50+
- role::nftables
51+
- role::fail2ban
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
- name: Reload systemd
3+
ansible.builtin.systemd:
4+
daemon_reload: true
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
- name: Ensure curl is installed
3+
ansible.builtin.package:
4+
name: curl
5+
state: present
6+
tags:
7+
- role::lke-nftables-update
8+
9+
- name: Ensure /etc/nftables directory exists
10+
ansible.builtin.file:
11+
path: /etc/nftables
12+
state: directory
13+
owner: root
14+
group: root
15+
mode: "0755"
16+
tags:
17+
- role::lke-nftables-update
18+
19+
- name: Template initial LKE IPs file
20+
ansible.builtin.template:
21+
src: lke.nft.j2
22+
dest: /etc/nftables/lke.nft
23+
owner: root
24+
group: root
25+
mode: "0644"
26+
tags:
27+
- role::lke-nftables-update
28+
29+
- name: Template LKE IP update script
30+
ansible.builtin.template:
31+
src: update-lke-ips.sh.j2
32+
dest: /usr/local/bin/update-lke-ips.sh
33+
owner: root
34+
group: root
35+
mode: "0755"
36+
tags:
37+
- role::lke-nftables-update
38+
39+
- name: Template LKE IP update service
40+
ansible.builtin.template:
41+
src: lke-nftables-update.service.j2
42+
dest: /etc/systemd/system/lke-nftables-update.service
43+
owner: root
44+
group: root
45+
mode: "0644"
46+
notify:
47+
- Reload systemd
48+
tags:
49+
- role::lke-nftables-update
50+
51+
- name: Template LKE IP update timer
52+
ansible.builtin.template:
53+
src: lke-nftables-update.timer.j2
54+
dest: /etc/systemd/system/lke-nftables-update.timer
55+
owner: root
56+
group: root
57+
mode: "0644"
58+
notify:
59+
- Reload systemd
60+
tags:
61+
- role::lke-nftables-update
62+
63+
- name: Enable and start LKE IP update timer
64+
ansible.builtin.service:
65+
name: lke-nftables-update.timer
66+
state: started
67+
enabled: true
68+
tags:
69+
- role::lke-nftables-update
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Unit]
2+
Description=Update LKE IP whitelist in nftables
3+
After=network-online.target
4+
Wants=network-online.target
5+
6+
[Service]
7+
Type=oneshot
8+
ExecStart=/usr/local/bin/update-lke-ips.sh
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Unit]
2+
Description=Hourly update of LKE IP whitelist in nftables
3+
4+
[Timer]
5+
OnCalendar=hourly
6+
RandomizedDelaySec=600
7+
Persistent=true
8+
9+
[Install]
10+
WantedBy=timers.target
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Managed by Ansible - do not edit manually.
2+
# This file is updated periodically by a systemd timer.
3+
4+
set possible_lke_ipv4_addrs {
5+
type ipv4_addr
6+
flags interval
7+
elements = { {{ lke_frankfurt_ipv4_addresses | join(", ") }} }
8+
}
9+
10+
set possible_lke_ipv6_addrs {
11+
type ipv6_addr
12+
flags interval
13+
elements = { {{ lke_frankfurt_ipv6_addresses | join(", ") }} }
14+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Fetch IPs from Linode
5+
IPS=$(curl -s --connect-timeout 10 --max-time 30 https://geoip.linode.com/)
6+
7+
# Filter for Frankfurt and extract IPs
8+
# The CSV format is: ip,city,country,region,provider
9+
FRANKFURT_IPS=$(echo "$IPS" | grep "Frankfurt" | cut -d',' -f1)
10+
11+
# Ensure we got some IPs to avoid clearing the whitelist on error
12+
if [ -z "$FRANKFURT_IPS" ]; then
13+
echo "No Frankfurt IPs found in Linode GeoIP data." >&2
14+
exit 1
15+
fi
16+
17+
IPV4_IPS=$(echo "$FRANKFURT_IPS" | grep '\.' || true)
18+
IPV6_IPS=$(echo "$FRANKFURT_IPS" | grep ':' || true)
19+
20+
# Helper to join by comma
21+
join_ips() {
22+
local ips=$1
23+
echo "$ips" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g'
24+
}
25+
26+
IPV4_ELEMENTS=$(join_ips "$IPV4_IPS")
27+
IPV6_ELEMENTS=$(join_ips "$IPV6_IPS")
28+
29+
# Create temporary file
30+
TMP_FILE="/etc/nftables/lke.nft.tmp"
31+
cat <<EOF > "$TMP_FILE"
32+
# Automatically updated by update-lke-ips.sh
33+
set possible_lke_ipv4_addrs {
34+
type ipv4_addr
35+
flags interval
36+
elements = { $IPV4_ELEMENTS }
37+
}
38+
39+
set possible_lke_ipv6_addrs {
40+
type ipv6_addr
41+
flags interval
42+
elements = { $IPV6_ELEMENTS }
43+
}
44+
EOF
45+
46+
# Move to final location and reload
47+
mv "$TMP_FILE" /etc/nftables/lke.nft
48+
systemctl reload nftables

0 commit comments

Comments
 (0)