First version

This commit is contained in:
andy committed 2026-10-15 20:44:05 -03:00
commit 243e92a9dd
84 files changed
+4012

No files matched your search

+222
View File
@@ -0,0 +1,222 @@
- name: Contest firewall (nftables allowlist and local proxy)
hosts: all
become: yes
vars:
# Start the firewall at the end of the run. The current SSH session keeps
# working, but new inbound connections are dropped once it is active.
start_firewall: true
timezone: America/Moncton
dns_servers: "{{ lookup('file', 'dns_servers.txt').splitlines() | map('regex_replace', '#.*$', '') | map('trim') | reject('equalto', '') | list }}"
ntp_servers: "{{ lookup('file', 'ntp_servers.txt').splitlines() | map('regex_replace', '#.*$', '') | map('trim') | reject('equalto', '') | list }}"
tasks:
- name: Install nftables, dig, chrony and the nginx stream module
apt:
name:
- nftables
- dnsutils
- chrony
- nginx
- libnginx-mod-stream
- dnsmasq-base
state: present
update_cache: yes
cache_valid_time: 3600
- name: Create the account of the proxy
user:
name: contest-proxy
system: yes
create_home: no
shell: /usr/sbin/nologin
- name: Create the configuration directory
file:
path: /etc/contest-firewall
state: directory
mode: '0755'
- name: Copy the domain, DNS and NTP lists
copy:
src: "{{ item }}"
dest: "/etc/contest-firewall/{{ item }}"
mode: '0644'
loop:
- allowed_domains.txt
- dns_servers.txt
- ntp_servers.txt
- name: Copy the configuration of the proxy
copy:
src: files/contest-proxy.conf
dest: /etc/contest-firewall/contest-proxy.conf
mode: '0644'
- name: Copy the test script
copy:
src: test_firewall.sh
dest: /test_firewall.sh
mode: '0755'
- name: Copy the firewall scripts
copy:
src: "files/{{ item }}"
dest: "/{{ item }}"
mode: '0500'
loop:
- firewall.sh
- refresh_rules.sh
- enable_firewall.sh
- disable_firewall.sh
- name: Copy the systemd units
copy:
src: "files/{{ item }}"
dest: "/etc/systemd/system/{{ item }}"
mode: '0644'
loop:
- firewall.service
- refresh_rules.service
- refresh_rules.timer
- contest-proxy.service
- contest-dns.service
# The proxy reads the site name, which encrypted client hello (ECH) hides
- name: Turn off encrypted client hello in Firefox
shell: |
python3 - /usr/lib/firefox/distribution/policies.json <<'PY'
import json, os, sys
path = sys.argv[1]
if not os.path.exists(path):
sys.exit(0)
data = json.load(open(path))
prefs = data.setdefault("policies", {}).setdefault("Preferences", {})
before = json.dumps(data, sort_keys=True)
for name in ("network.dns.echconfig.enabled", "network.dns.http3_echconfig.enabled"):
prefs[name] = {"Value": False, "Status": "locked"}
if json.dumps(data, sort_keys=True) != before:
json.dump(data, open(path, "w"), indent=2)
print("changed")
PY
register: ech
changed_when: "'changed' in ech.stdout"
- name: Configure DNS servers in resolved.conf
lineinfile:
path: /etc/systemd/resolved.conf
regexp: '^#?DNS='
line: "DNS={{ dns_servers | join(' ') }}"
state: present
- name: Disable the DNS stub listener
lineinfile:
path: /etc/systemd/resolved.conf
regexp: '^#?DNSStubListener='
line: 'DNSStubListener=no'
state: present
- name: Configure chrony NTP servers
blockinfile:
path: /etc/chrony/chrony.conf
marker: "# {mark} CONTEST FIREWALL NTP SERVERS"
block: |
{% for ntp in ntp_servers %}
pool {{ ntp }} iburst
{% endfor %}
insertbefore: BOF
- name: Remove the default NTP pools from chrony.conf
lineinfile:
path: /etc/chrony/chrony.conf
regexp: '^(pool|server)\s+(?!.*iburst$).*'
state: absent
# Ubuntu 26.04 adds NTS time sources that the firewall blocks, and chrony
# does not use the plain NTP servers while those sources are configured
- name: Disable the Ubuntu NTS time sources
command: mv /etc/chrony/sources.d/ubuntu-ntp-pools.sources /etc/chrony/sources.d/ubuntu-ntp-pools.sources.disabled
args:
removes: /etc/chrony/sources.d/ubuntu-ntp-pools.sources
# The contest machines may also run Windows, which keeps the hardware
# clock in local time. chrony must not write its time into that clock.
- name: Do not write the time into the hardware clock
lineinfile:
path: /etc/chrony/chrony.conf
regexp: '^rtcsync'
state: absent
- name: Step the clock at boot whatever the offset
lineinfile:
path: /etc/chrony/chrony.conf
regexp: '^makestep'
line: makestep 1 3
- name: Copy the clock fallback (time from the contest site if NTP fails)
copy:
src: "files/time/{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- { src: time_from_contest.sh, dest: /time_from_contest.sh, mode: '0500' }
- { src: time_from_contest.service, dest: /etc/systemd/system/time_from_contest.service, mode: '0644' }
- { src: time_from_contest.timer, dest: /etc/systemd/system/time_from_contest.timer, mode: '0644' }
- name: Enable NTP synchronization
command: timedatectl set-ntp true
changed_when: false
- name: Set the timezone
file:
src: "/usr/share/zoneinfo/{{ timezone }}"
dest: /etc/localtime
state: link
force: yes
- name: Block open.kattis.com (past submissions) via the hosts file
lineinfile:
path: /etc/hosts
line: "127.0.0.1\topen.kattis.com"
state: present
- name: Restart systemd-resolved
systemd:
name: systemd-resolved
state: restarted
- name: Enable and restart chrony
systemd:
name: chrony
enabled: yes
state: restarted
- name: Reload systemd
systemd:
daemon_reload: yes
- name: Enable the firewall and refresh services
systemd:
name: "{{ item }}"
enabled: yes
loop:
- firewall.service
- refresh_rules.service
- name: Enable the clock fallback
systemd:
name: time_from_contest.timer
enabled: yes
- name: Enable and start the refresh timer
systemd:
name: refresh_rules.timer
enabled: yes
state: started
# Last task, launched in the background: once the firewall is active,
# Ansible cannot open a new SSH connection to collect a result.
- name: Start the firewall
shell: sleep 2 && systemctl start firewall.service && /refresh_rules.sh
async: 60
poll: 0
when: start_firewall | bool