223 lines
6.8 KiB
YAML
223 lines
6.8 KiB
YAML
- 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
|