aboutsummaryrefslogtreecommitdiffstats
path: root/ansible
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2023-08-13 14:59:36 +0100
committerGravatar Chris Lovering <[email protected]>2023-08-13 15:54:03 +0100
commit431585b5256a0d08f4f3c33122465a88a93ddcb1 (patch)
tree4cc41cda81655332fa467e6c9c85d1ee9c35b90e /ansible
parentServe static files from Turing (#116) (diff)
Move all ansible files to their own folder
Diffstat (limited to 'ansible')
-rw-r--r--ansible/.ansible-lint8
-rw-r--r--ansible/ansible.cfg12
-rw-r--r--ansible/host_vars/lovelace/prometheus.yml35
-rw-r--r--ansible/inventory/hosts.yaml14
-rw-r--r--ansible/local_testing/.gitignore1
-rw-r--r--ansible/local_testing/README.md50
-rw-r--r--ansible/local_testing/Vagrantfile91
-rw-r--r--ansible/local_testing/hosts.yaml56
-rw-r--r--ansible/local_testing/scripts/push-keys7
-rw-r--r--ansible/playbook.yml28
-rw-r--r--ansible/requirements.txt4
-rw-r--r--ansible/roles/certbot/README.md3
-rw-r--r--ansible/roles/certbot/tasks/main.yml105
-rw-r--r--ansible/roles/certbot/templates/renewal-hook.sh.j26
-rw-r--r--ansible/roles/certbot/vars/main/main.yml6
-rw-r--r--ansible/roles/certbot/vars/main/vault.yml9
-rw-r--r--ansible/roles/common/handlers/main.yml9
-rw-r--r--ansible/roles/common/tasks/main.yml78
-rw-r--r--ansible/roles/common/templates/etc-hosts.j27
-rw-r--r--ansible/roles/common/templates/motd.j23
-rw-r--r--ansible/roles/common/templates/sudo_lecture.j26
-rw-r--r--ansible/roles/fail2ban/README.md3
-rw-r--r--ansible/roles/fail2ban/files/jail.local8
-rw-r--r--ansible/roles/fail2ban/handlers/main.yml6
-rw-r--r--ansible/roles/fail2ban/tasks/main.yml27
-rw-r--r--ansible/roles/nginx-cloudflare-mtls/README.md16
-rw-r--r--ansible/roles/nginx-cloudflare-mtls/defaults/main.yml3
-rw-r--r--ansible/roles/nginx-cloudflare-mtls/files/cloudflare.crt35
-rw-r--r--ansible/roles/nginx-cloudflare-mtls/meta/main.yml3
-rw-r--r--ansible/roles/nginx-cloudflare-mtls/tasks/main.yml20
-rw-r--r--ansible/roles/nginx-geoip/meta/main.yml3
-rw-r--r--ansible/roles/nginx-geoip/tasks/main.yml13
-rw-r--r--ansible/roles/nginx-ufw/README.md3
-rw-r--r--ansible/roles/nginx-ufw/meta/main.yml4
-rw-r--r--ansible/roles/nginx-ufw/tasks/main.yml7
-rw-r--r--ansible/roles/nginx/README.md3
-rw-r--r--ansible/roles/nginx/files/default_server.conf32
-rw-r--r--ansible/roles/nginx/files/files.pydis.wtf10
-rw-r--r--ansible/roles/nginx/handlers/main.yml7
-rw-r--r--ansible/roles/nginx/tasks/main.yml45
-rw-r--r--ansible/roles/podman/tasks/main.yml7
-rw-r--r--ansible/roles/postgres/handlers/main.yml4
-rw-r--r--ansible/roles/postgres/tasks/main.yml34
-rw-r--r--ansible/roles/postgres/vars/main.yml7
-rw-r--r--ansible/roles/prometheus-node-exporter/README.md3
-rw-r--r--ansible/roles/prometheus-node-exporter/tasks/main.yml7
-rw-r--r--ansible/roles/prometheus/README.md13
-rw-r--r--ansible/roles/prometheus/defaults/main.yml45
-rw-r--r--ansible/roles/prometheus/handlers/main.yml14
-rw-r--r--ansible/roles/prometheus/tasks/main.yml33
-rw-r--r--ansible/roles/ufw/tasks/main.yml37
-rw-r--r--ansible/roles/ufw/vars/main.yml6
-rw-r--r--ansible/roles/wireguard/defaults/main/vars.yml4
-rw-r--r--ansible/roles/wireguard/handlers/main.yml4
-rw-r--r--ansible/roles/wireguard/tasks/main.yml72
-rw-r--r--ansible/roles/wireguard/templates/wg0.conf.j225
56 files changed, 1101 insertions, 0 deletions
diff --git a/ansible/.ansible-lint b/ansible/.ansible-lint
new file mode 100644
index 0000000..0fb53d0
--- /dev/null
+++ b/ansible/.ansible-lint
@@ -0,0 +1,8 @@
+---
+exclude_paths:
+ - .github # Not ansible roles
+ - roles/certbot/vars/main/vault.yml
+skip_list:
+ - fqcn-builtins
+ - meta-no-info
+ - role-name
diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg
new file mode 100644
index 0000000..6cbcfc6
--- /dev/null
+++ b/ansible/ansible.cfg
@@ -0,0 +1,12 @@
+[defaults]
+remote_user = root
+inventory = inventory/hosts.yaml
+host_key_checking = False
+vault_password_file = vault_passwords
+
+[privilege_escalation]
+become = yes
+become_user = root
+
+[connection]
+pipelining = True
diff --git a/ansible/host_vars/lovelace/prometheus.yml b/ansible/host_vars/lovelace/prometheus.yml
new file mode 100644
index 0000000..63cef52
--- /dev/null
+++ b/ansible/host_vars/lovelace/prometheus.yml
@@ -0,0 +1,35 @@
+---
+prometheus_configuration:
+ global:
+ scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+ evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+ # scrape_timeout is set to the global default (10s).
+
+ # Alertmanager configuration
+ alerting:
+ alertmanagers:
+ - static_configs:
+ - targets: []
+
+ rule_files:
+ # - "first_rules.yml"
+ # - "second_rules.yml"
+
+ scrape_configs:
+ # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
+ - job_name: prometheus
+
+ # Override the global default and scrape targets from this job every 5 seconds.
+ scrape_interval: 5s
+ scrape_timeout: 5s
+
+ # metrics_path defaults to '/metrics'
+ # scheme defaults to 'http'.
+
+ static_configs:
+ - targets: ['localhost:9090']
+
+ - job_name: node
+ # Scrape node exporters on all hosts
+ static_configs:
+ - targets: "{{ hostvars.values() | map(attribute='ansible_wg0.ipv4.address') | map('regex_replace', '^(.*)$', '\\1:9100') | list }}"
diff --git a/ansible/inventory/hosts.yaml b/ansible/inventory/hosts.yaml
new file mode 100644
index 0000000..5239457
--- /dev/null
+++ b/ansible/inventory/hosts.yaml
@@ -0,0 +1,14 @@
+all:
+ hosts:
+ turing:
+ ansible_host: turing.box.pydis.wtf
+ wireguard_subnet: 10.1.0.0/16
+ lovelace:
+ ansible_host: lovelace.box.pydis.wtf
+ wireguard_subnet: 10.2.0.0/16
+ children:
+ nginx:
+ hosts:
+ turing:
+ vars:
+ wireguard_port: 46850
diff --git a/ansible/local_testing/.gitignore b/ansible/local_testing/.gitignore
new file mode 100644
index 0000000..a977916
--- /dev/null
+++ b/ansible/local_testing/.gitignore
@@ -0,0 +1 @@
+.vagrant/
diff --git a/ansible/local_testing/README.md b/ansible/local_testing/README.md
new file mode 100644
index 0000000..db3c409
--- /dev/null
+++ b/ansible/local_testing/README.md
@@ -0,0 +1,50 @@
+# Testing Locally
+
+### Requirements
+
+- [Vagrant](https://developer.hashicorp.com/vagrant/docs/installation)
+- [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
+
+## Get Started
+
+```shell
+vagrant up # This will take a while
+vagrant ssh # Get a shell, password=vagrant
+
+# inside control VM
+/vagrant/scripts/push-keys # Push the control VM's ssh key to all nodes
+
+# run ansible
+cd infra
+# if you're on Windows, the `infra` directory will be mounted with permissions set to 777
+# which ansible will reject as insecure
+# export the below environment variable to force anisble to accept the writable config
+# export ANSIBLE_CONFIG='/home/vagrant/infra/ansible.cfg'
+ansible-playbook playbook.yml --inventory local_testing/hosts.yaml --user vagrant
+```
+
+Below are the IPs of the VMs on the VirtualBox network
+```yaml
+vms:
+- control: 192.168.56.1
+- hopper: 192.168.56.2
+- lovelace: 192.168.56.3
+- neumann: 192.168.56.4
+- richie: 192.168.56.5
+- turing: 192.168.56.6
+```
+
+
+# Fixes/Notes
+
+### There was an error when attempting to rsync a synced folder.
+
+```shell
+vagrant plugin install vagrant-vbguest
+```
+
+
+### Kernel module is not loaded
+```shell
+sudo modprobe vbox{drv,netadp,netflt}
+```
diff --git a/ansible/local_testing/Vagrantfile b/ansible/local_testing/Vagrantfile
new file mode 100644
index 0000000..011f9f3
--- /dev/null
+++ b/ansible/local_testing/Vagrantfile
@@ -0,0 +1,91 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+ config.vm.box = "bento/debian-11"
+ config.vm.box_version = "202212.11.0"
+ config.vm.provision "shell", inline: <<-SHELL
+ apt-get update
+ apt-get install -y python3 openssh-server
+ systemctl enable ssh
+ SHELL
+
+ config.vm.define "control", primary: true do |control|
+ control.vm.hostname = "control"
+ control.vm.network "private_network", ip: "192.168.56.1",
+ virtualbox__intnet: true
+ control.vm.synced_folder "../", "/home/vagrant/infra"
+ control.vm.provision "shell", inline: <<-SHELL
+ apt-get install -y sshpass python3-pip
+ SHELL
+
+ control.vm.provision "shell", privileged: false, inline: <<-SHELL
+ python3 -m pip install --user ansible dnspython
+ ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa <<< y
+ SHELL
+
+ control.vm.provider "virtualbox" do |v|
+ v.name = "pydis_control"
+ end
+ end
+
+ config.vm.define "hopper" do |hopper|
+ hopper.vm.hostname = "hopper"
+ hopper.vm.network "private_network", ip: "192.168.56.2",
+ virtualbox__intnet: true
+ hopper.vm.synced_folder '.', '/vagrant', disabled: true
+
+ hopper.vm.provider "virtualbox" do |v|
+ v.name = "pydis_hopper"
+ v.memory = 2048
+ end
+ end
+
+ config.vm.define "lovelace" do |lovelace|
+ lovelace.vm.hostname = "lovelace"
+ lovelace.vm.network "private_network", ip: "192.168.56.3",
+ virtualbox__intnet: true
+ lovelace.vm.synced_folder '.', '/vagrant', disabled: true
+
+ lovelace.vm.provider "virtualbox" do |v|
+ v.name = "pydis_lovelace"
+ v.memory = 2048
+ end
+ end
+
+ config.vm.define "neumann" do |neumann|
+ neumann.vm.hostname = "neumann"
+ neumann.vm.network "private_network", ip: "192.168.56.4",
+ virtualbox__intnet: true
+ neumann.vm.synced_folder '.', '/vagrant', disabled: true
+
+ neumann.vm.provider "virtualbox" do |v|
+ v.name = "pydis_neumann"
+ v.memory = 2048
+ end
+ end
+
+ config.vm.define "ritchie" do |ritchie|
+ ritchie.vm.hostname = "ritchie"
+ ritchie.vm.network "private_network", ip: "192.168.56.5",
+ virtualbox__intnet: true
+ ritchie.vm.synced_folder '.', '/vagrant', disabled: true
+
+ ritchie.vm.provider "virtualbox" do |v|
+ v.name = "pydis_ritchie"
+ v.memory = 2048
+ end
+ end
+
+ config.vm.define "turing" do |turing|
+ turing.vm.hostname = "turing"
+ turing.vm.network "private_network", ip: "192.168.56.6",
+ virtualbox__intnet: true
+ turing.vm.synced_folder '.', '/vagrant', disabled: true
+
+ turing.vm.provider "virtualbox" do |v|
+ v.name = "pydis_turing"
+ v.memory = 2048
+ end
+ end
+end
diff --git a/ansible/local_testing/hosts.yaml b/ansible/local_testing/hosts.yaml
new file mode 100644
index 0000000..d1759a3
--- /dev/null
+++ b/ansible/local_testing/hosts.yaml
@@ -0,0 +1,56 @@
+all:
+ hosts:
+ hopper:
+ ansible_host: 192.168.56.2
+ ip: 192.168.56.2
+ access_ip: 192.168.56.2
+ lovelace:
+ ansible_host: 192.168.56.3
+ ip: 192.168.56.3
+ access_ip: 192.168.56.3
+ neumann:
+ ansible_host: 192.168.56.4
+ ip: 192.168.56.4
+ access_ip: 192.168.56.4
+ ritchie:
+ ansible_host: 192.168.56.5
+ ip: 192.168.56.5
+ access_ip: 192.168.56.5
+ turing:
+ ansible_host: 192.168.56.6
+ ip: 192.168.56.6
+ access_ip: 192.168.56.6
+ children:
+ kube_control_plane:
+ hosts:
+ hopper:
+ turing:
+ kube_node:
+ hosts:
+ hopper:
+ turing:
+ lovelace:
+ neumann:
+ ritchie:
+ etcd:
+ hosts:
+ hopper:
+ turing:
+ lovelace:
+ k8s_cluster:
+ children:
+ kube_control_plane:
+ kube_node:
+ calico_rr:
+ hosts: {}
+ podman:
+ hosts:
+ turing:
+ lovelace:
+ hopper:
+ ritchie:
+ nginx:
+ hosts:
+ turing:
+ ritchie:
+ neumann:
diff --git a/ansible/local_testing/scripts/push-keys b/ansible/local_testing/scripts/push-keys
new file mode 100644
index 0000000..6fdc85f
--- /dev/null
+++ b/ansible/local_testing/scripts/push-keys
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# Intended to be used in the "control" VM to push keys to the other hosts
+
+for i in {1..6} ; do
+ ssh-keyscan 192.168.56.$i >> ~/.ssh/known_hosts
+ sshpass -p vagrant ssh-copy-id 192.168.56.$i
+done
diff --git a/ansible/playbook.yml b/ansible/playbook.yml
new file mode 100644
index 0000000..b4c7cf0
--- /dev/null
+++ b/ansible/playbook.yml
@@ -0,0 +1,28 @@
+- name: Deploy common services
+ hosts: all
+ roles:
+ - common
+ - ufw
+ - prometheus-node-exporter
+ - wireguard
+ - fail2ban
+ - podman
+
+- name: Deploy our monitoring stack
+ hosts: lovelace
+ roles:
+ - prometheus
+
+- name: Deploy nginx & certbot to hosts
+ hosts: nginx
+ roles:
+ - certbot
+ - nginx
+ - nginx-geoip
+ - nginx-ufw
+ - nginx-cloudflare-mtls
+
+- name: Deploy our PostgreSQL database hosts
+ hosts: lovelace
+ roles:
+ - postgres
diff --git a/ansible/requirements.txt b/ansible/requirements.txt
new file mode 100644
index 0000000..c22a2fe
--- /dev/null
+++ b/ansible/requirements.txt
@@ -0,0 +1,4 @@
+ansible==8.2.0
+ansible-lint[yamllint]==6.17.2
+pre-commit==3.3.3
+dnspython==2.4.1
diff --git a/ansible/roles/certbot/README.md b/ansible/roles/certbot/README.md
new file mode 100644
index 0000000..b9d3e36
--- /dev/null
+++ b/ansible/roles/certbot/README.md
@@ -0,0 +1,3 @@
+# Role "certbot"
+
+Installs certbot and the Cloudflare DNS plugin for certbot to provision and deploy TLS certificates for web properties.
diff --git a/ansible/roles/certbot/tasks/main.yml b/ansible/roles/certbot/tasks/main.yml
new file mode 100644
index 0000000..2cf859c
--- /dev/null
+++ b/ansible/roles/certbot/tasks/main.yml
@@ -0,0 +1,105 @@
+---
+- name: Install certbot and certbot Cloudflare plugin
+ when: inventory_hostname == ansible_play_hosts_all[0]
+ package:
+ name:
+ - python3-certbot
+ - python3-certbot-dns-cloudflare
+ state: present
+ tags:
+ - role::certbot
+
+- name: Install rsync on certbot hosts
+ package:
+ name: rsync
+ state: present
+ tags:
+ - role::certbot
+
+- name: Generate Cloudflare credentials file on designated leader
+ when: inventory_hostname == ansible_play_hosts_all[0]
+ copy:
+ content: |
+ # This file is managed by Ansible
+ dns_cloudflare_api_token = {{ certbot_cloudflare_token }}
+ dest: /etc/letsencrypt/cloudflare.ini
+ owner: root
+ group: root
+ mode: "0400"
+ tags:
+ - role::certbot
+
+- name: Generate SSH key for certificate distribution
+ when: inventory_hostname == ansible_play_hosts_all[0]
+ community.crypto.openssh_keypair:
+ path: /root/.ssh/cert_{{ item }}_key_ed25519
+ type: ed25519
+ state: present
+ comment: certificate distribution key for {{ item }}
+ with_items:
+ - "{{ ansible_play_hosts | reject('in', [inventory_hostname]) }}"
+ tags:
+ - role::certbot
+ register: generated_keys
+
+- name: Create certificate directories on replica certificate hosts
+ when: inventory_hostname != ansible_play_hosts[0]
+ file:
+ path: /etc/letsencrypt/live
+ recurse: true
+ state: directory
+ owner: root
+ group: root
+ mode: "0700"
+ tags:
+ - role::certbot
+
+- name: Install certificate distribution keys to other NGINX nodes
+ when: inventory_hostname != ansible_play_hosts[0]
+ ansible.posix.authorized_key:
+ user: root
+ state: present
+ key: |
+ {{ hostvars[ansible_play_hosts_all[0]]['generated_keys']['results']
+ | selectattr('item', 'equalto', inventory_hostname)
+ | map(attribute='public_key')
+ | first }}
+ comment: "certificate distribution key"
+ key_options: 'from="{{ hostvars[ansible_play_hosts_all[0]]["wireguard_subnet"] }}",restrict,command="/opt/cert_rsync.sh"'
+ tags:
+ - role::certbot
+
+- name: Ensure renewal-hooks deploy directory exists
+ file:
+ path: /etc/letsencrypt/renewal-hooks/deploy
+ recurse: true
+ state: directory
+
+- name: Create renewal hook to synchronize certificates
+ when: inventory_hostname == ansible_play_hosts_all[0]
+ template:
+ src: renewal-hook.sh.j2
+ dest: /etc/letsencrypt/renewal-hooks/deploy/distribute-certs
+ owner: root
+ group: root
+ mode: "0700"
+ tags:
+ - role::certbot
+
+- name: Request certificates for configured domains
+ when: inventory_hostname == ansible_play_hosts_all[0]
+ command: |
+ certbot certonly
+ --agree-tos
+ --non-interactive
+ --email {{ certbot_email }}
+ --dns-cloudflare
+ --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
+ --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/distribute-certs
+ -d {{ item }} -d *.{{ item }} -d cloud.native.is.fun.and.easy.pydis.wtf
+ args:
+ creates: "/etc/letsencrypt/live/{{ item }}/fullchain.pem"
+ with_items:
+ - "{{ certbot_domains }}"
+ tags:
+ - role::certbot
diff --git a/ansible/roles/certbot/templates/renewal-hook.sh.j2 b/ansible/roles/certbot/templates/renewal-hook.sh.j2
new file mode 100644
index 0000000..7fa7252
--- /dev/null
+++ b/ansible/roles/certbot/templates/renewal-hook.sh.j2
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -ex
+
+{% for host in ansible_play_hosts if host != inventory_hostname %}
+rsync --copy-links --delete --recursive -e "ssh -i /root/.ssh/cert_{{ host }}_key_ed25519 -o StrictHostKeyChecking=accept-new" /etc/letsencrypt/live/* root@{{ hostvars[host]['wireguard_subnet'] | split("/") | first }}:/etc/letsencrypt/live
+{% endfor %}
diff --git a/ansible/roles/certbot/vars/main/main.yml b/ansible/roles/certbot/vars/main/main.yml
new file mode 100644
index 0000000..fdfc7b1
--- /dev/null
+++ b/ansible/roles/certbot/vars/main/main.yml
@@ -0,0 +1,6 @@
+---
+certbot_cloudflare_token: "{{ encrypted_cloudflare_token }}"
+certbot_email: "[email protected]"
+certbot_domains:
+ - pydis.wtf
+ - pythondiscord.com
diff --git a/ansible/roles/certbot/vars/main/vault.yml b/ansible/roles/certbot/vars/main/vault.yml
new file mode 100644
index 0000000..c669b69
--- /dev/null
+++ b/ansible/roles/certbot/vars/main/vault.yml
@@ -0,0 +1,9 @@
+$ANSIBLE_VAULT;1.1;AES256
+66336535306366333038666137306135663438346366643735383962623339636236343438633766
+6565343931306531623330373936313730353539303264390a333031363634663236636232386461
+34353239643364653464373531653236383963303137326438343239313136376537336636326162
+3537383737323732310a623836363138646434636165643130366362656661393937346534313632
+37663966613031363036623838326666636231313462363831396366363837343632646131303863
+35363032386463346164623733656463633735376161653361343231326166313466643236623762
+31343562323362353238663666303435353138643463656531373466336639316464376632623731
+32646464393438656134
diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml
new file mode 100644
index 0000000..02cc88e
--- /dev/null
+++ b/ansible/roles/common/handlers/main.yml
@@ -0,0 +1,9 @@
+- name: Restart ssh
+ service:
+ name: ssh
+ state: restarted
+
+- name: Restart systemd-timesyncd
+ service:
+ name: systemd-timesyncd
+ state: restarted
diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml
new file mode 100644
index 0000000..b9b9c52
--- /dev/null
+++ b/ansible/roles/common/tasks/main.yml
@@ -0,0 +1,78 @@
+- name: Update hostname to match Ansible inventory
+ hostname:
+ name: "{{ inventory_hostname }}"
+ tags:
+ - role::common
+
+- name: Update /etc/hosts to match Ansible inventory
+ template:
+ src: etc-hosts.j2
+ dest: /etc/hosts
+ mode: '0644'
+ owner: root
+ group: root
+ tags:
+ - role::common
+
+- name: Disable SSH password authentication
+ lineinfile:
+ dest: /etc/ssh/sshd_config
+ regexp: "^PasswordAuthentication"
+ line: "PasswordAuthentication no"
+ state: present
+ notify:
+ - Restart ssh
+ tags:
+ - role::common
+
+- name: Set timezone to UTC
+ file:
+ src: /usr/share/zoneinfo/Etc/UTC
+ dest: /etc/localtime
+ mode: '0644'
+ owner: root
+ group: root
+ notify:
+ - Restart systemd-timesyncd
+ tags:
+ - role::common
+
+- name: Create sudoers lecture
+ template:
+ src: sudo_lecture.j2
+ dest: /etc/sudo_lecture
+ mode: '0644'
+ owner: root
+ group: root
+ tags:
+ - role::common
+
+- name: Add sudoers lecture path
+ lineinfile:
+ dest: /etc/sudoers
+ regexp: '^Defaults +?lecture_file ?= ?".+?"$'
+ line: 'Defaults lecture_file = "/etc/sudo_lecture"'
+ state: present
+ validate: /usr/sbin/visudo -cf %s
+ tags:
+ - role::common
+
+- name: Configure MOTD
+ template:
+ src: motd.j2
+ dest: /etc/motd
+ mode: '0644'
+ owner: root
+ group: root
+ tags:
+ - role::common
+
+- name: Enable default .bashrc for root
+ copy:
+ src: /etc/skel/.bashrc
+ dest: /root/.bashrc
+ mode: '0644'
+ owner: root
+ group: root
+ tags:
+ - role::common
diff --git a/ansible/roles/common/templates/etc-hosts.j2 b/ansible/roles/common/templates/etc-hosts.j2
new file mode 100644
index 0000000..6fdbdaa
--- /dev/null
+++ b/ansible/roles/common/templates/etc-hosts.j2
@@ -0,0 +1,7 @@
+127.0.0.1 localhost
+127.0.1.1 {{ inventory_hostname }}.box.pydis.wtf {{ inventory_hostname }}
+
+::1 localhost ip6-localhost ip6-loopback
+ff02::1 ip6-allnodes
+ff02::2 ip6-allrouters
+{{ lookup('dig', ansible_host) }} {{ inventory_hostname }}.box.pydis.wtf {{ inventory_hostname }}
diff --git a/ansible/roles/common/templates/motd.j2 b/ansible/roles/common/templates/motd.j2
new file mode 100644
index 0000000..ff6cfcd
--- /dev/null
+++ b/ansible/roles/common/templates/motd.j2
@@ -0,0 +1,3 @@
+[[[ To any NSA and FBI agents accessing our servers: please consider ]]]
+[[[ whether defending the US Constitution against all enemies, ]]]
+[[[ foreign or domestic, requires you to follow Snowden's example. ]]]
diff --git a/ansible/roles/common/templates/sudo_lecture.j2 b/ansible/roles/common/templates/sudo_lecture.j2
new file mode 100644
index 0000000..1758dd0
--- /dev/null
+++ b/ansible/roles/common/templates/sudo_lecture.j2
@@ -0,0 +1,6 @@
+
+ "Bee" careful __
+ with sudo! // \
+ \\_/ //
+ ''-.._.-''-.._.. -(||)(')
+ '''
diff --git a/ansible/roles/fail2ban/README.md b/ansible/roles/fail2ban/README.md
new file mode 100644
index 0000000..60bb3ac
--- /dev/null
+++ b/ansible/roles/fail2ban/README.md
@@ -0,0 +1,3 @@
+# Role "fail2ban"
+
+This role installs and configures fail2ban to all Python Discord hosts.
diff --git a/ansible/roles/fail2ban/files/jail.local b/ansible/roles/fail2ban/files/jail.local
new file mode 100644
index 0000000..c25dde5
--- /dev/null
+++ b/ansible/roles/fail2ban/files/jail.local
@@ -0,0 +1,8 @@
+[DEFAULT]
+ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 10.0.0.0/8
+bantime = 24h
+maxretry = 3
+findtime = 2h
+
+[sshd]
+mode=aggressive
diff --git a/ansible/roles/fail2ban/handlers/main.yml b/ansible/roles/fail2ban/handlers/main.yml
new file mode 100644
index 0000000..dbff530
--- /dev/null
+++ b/ansible/roles/fail2ban/handlers/main.yml
@@ -0,0 +1,6 @@
+- name: Reload fail2ban
+ service:
+ name: fail2ban
+ state: reloaded
+ tags:
+ - role::fail2ban
diff --git a/ansible/roles/fail2ban/tasks/main.yml b/ansible/roles/fail2ban/tasks/main.yml
new file mode 100644
index 0000000..74a5442
--- /dev/null
+++ b/ansible/roles/fail2ban/tasks/main.yml
@@ -0,0 +1,27 @@
+---
+- name: Install fail2ban package
+ package:
+ name: fail2ban
+ state: present
+ tags:
+ - role::fail2ban
+
+- name: Copy fail2ban config
+ copy:
+ src: jail.local
+ dest: /etc/fail2ban/jail.local
+ owner: root
+ group: root
+ mode: "0644"
+ tags:
+ - role::fail2ban
+ notify:
+ - Reload fail2ban
+
+- name: Enable fail2ban service
+ service:
+ name: fail2ban
+ state: started
+ enabled: true
+ tags:
+ - role::fail2ban
diff --git a/ansible/roles/nginx-cloudflare-mtls/README.md b/ansible/roles/nginx-cloudflare-mtls/README.md
new file mode 100644
index 0000000..081cacb
--- /dev/null
+++ b/ansible/roles/nginx-cloudflare-mtls/README.md
@@ -0,0 +1,16 @@
+# Role "nginx-cloudflare-mtls"
+
+Installs the certificate required for performing mutual TLS authentication
+between NGINX and Cloudflare.
+
+To use mutual TLS in your NGINX virtual hosts, add this configuration snippet:
+
+```nginx
+ssl_client_certificate {{ nginx_cloudflare_mtls_certificate_path }};
+ssl_verify_client on;
+```
+
+
+## Variables
+
+See [role defaults](./defaults/main.yml) for an annotated overview.
diff --git a/ansible/roles/nginx-cloudflare-mtls/defaults/main.yml b/ansible/roles/nginx-cloudflare-mtls/defaults/main.yml
new file mode 100644
index 0000000..ff1c667
--- /dev/null
+++ b/ansible/roles/nginx-cloudflare-mtls/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+# The path at which to install the certificate.
+nginx_cloudflare_mtls_certificate_path: /etc/nginx/certs/cloudflare.crt
diff --git a/ansible/roles/nginx-cloudflare-mtls/files/cloudflare.crt b/ansible/roles/nginx-cloudflare-mtls/files/cloudflare.crt
new file mode 100644
index 0000000..965f0bf
--- /dev/null
+++ b/ansible/roles/nginx-cloudflare-mtls/files/cloudflare.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGCjCCA/KgAwIBAgIIV5G6lVbCLmEwDQYJKoZIhvcNAQENBQAwgZAxCzAJBgNV
+BAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMRQwEgYDVQQLEwtPcmln
+aW4gUHVsbDEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
+cm5pYTEjMCEGA1UEAxMab3JpZ2luLXB1bGwuY2xvdWRmbGFyZS5uZXQwHhcNMTkx
+MDEwMTg0NTAwWhcNMjkxMTAxMTcwMDAwWjCBkDELMAkGA1UEBhMCVVMxGTAXBgNV
+BAoTEENsb3VkRmxhcmUsIEluYy4xFDASBgNVBAsTC09yaWdpbiBQdWxsMRYwFAYD
+VQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMSMwIQYDVQQD
+ExpvcmlnaW4tcHVsbC5jbG91ZGZsYXJlLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAN2y2zojYfl0bKfhp0AJBFeV+jQqbCw3sHmvEPwLmqDLqynI
+42tZXR5y914ZB9ZrwbL/K5O46exd/LujJnV2b3dzcx5rtiQzso0xzljqbnbQT20e
+ihx/WrF4OkZKydZzsdaJsWAPuplDH5P7J82q3re88jQdgE5hqjqFZ3clCG7lxoBw
+hLaazm3NJJlUfzdk97ouRvnFGAuXd5cQVx8jYOOeU60sWqmMe4QHdOvpqB91bJoY
+QSKVFjUgHeTpN8tNpKJfb9LIn3pun3bC9NKNHtRKMNX3Kl/sAPq7q/AlndvA2Kw3
+Dkum2mHQUGdzVHqcOgea9BGjLK2h7SuX93zTWL02u799dr6Xkrad/WShHchfjjRn
+aL35niJUDr02YJtPgxWObsrfOU63B8juLUphW/4BOjjJyAG5l9j1//aUGEi/sEe5
+lqVv0P78QrxoxR+MMXiJwQab5FB8TG/ac6mRHgF9CmkX90uaRh+OC07XjTdfSKGR
+PpM9hB2ZhLol/nf8qmoLdoD5HvODZuKu2+muKeVHXgw2/A6wM7OwrinxZiyBk5Hh
+CvaADH7PZpU6z/zv5NU5HSvXiKtCzFuDu4/Zfi34RfHXeCUfHAb4KfNRXJwMsxUa
++4ZpSAX2G6RnGU5meuXpU5/V+DQJp/e69XyyY6RXDoMywaEFlIlXBqjRRA2pAgMB
+AAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1Ud
+DgQWBBRDWUsraYuA4REzalfNVzjann3F6zAfBgNVHSMEGDAWgBRDWUsraYuA4REz
+alfNVzjann3F6zANBgkqhkiG9w0BAQ0FAAOCAgEAkQ+T9nqcSlAuW/90DeYmQOW1
+QhqOor5psBEGvxbNGV2hdLJY8h6QUq48BCevcMChg/L1CkznBNI40i3/6heDn3IS
+zVEwXKf34pPFCACWVMZxbQjkNRTiH8iRur9EsaNQ5oXCPJkhwg2+IFyoPAAYURoX
+VcI9SCDUa45clmYHJ/XYwV1icGVI8/9b2JUqklnOTa5tugwIUi5sTfipNcJXHhgz
+6BKYDl0/UP0lLKbsUETXeTGDiDpxZYIgbcFrRDDkHC6BSvdWVEiH5b9mH2BON60z
+0O0j8EEKTwi9jnafVtZQXP/D8yoVowdFDjXcKkOPF/1gIh9qrFR6GdoPVgB3SkLc
+5ulBqZaCHm563jsvWb/kXJnlFxW+1bsO9BDD6DweBcGdNurgmH625wBXksSdD7y/
+fakk8DagjbjKShYlPEFOAqEcliwjF45eabL0t27MJV61O/jHzHL3dknXeE4BDa2j
+bA+JbyJeUMtU7KMsxvx82RmhqBEJJDBCJ3scVptvhDMRrtqDBW5JShxoAOcpFQGm
+iYWicn46nPDjgTU0bX1ZPpTpryXbvciVL5RkVBuyX2ntcOLDPlZWgxZCBp96x07F
+AnOzKgZk4RzZPNAxCXERVxajn/FLcOhglVAKo5H0ac+AitlQ0ip55D2/mf8o72tM
+fVQ6VpyjEXdiIXWUq/o=
+-----END CERTIFICATE-----
diff --git a/ansible/roles/nginx-cloudflare-mtls/meta/main.yml b/ansible/roles/nginx-cloudflare-mtls/meta/main.yml
new file mode 100644
index 0000000..8b662c9
--- /dev/null
+++ b/ansible/roles/nginx-cloudflare-mtls/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - role: nginx
diff --git a/ansible/roles/nginx-cloudflare-mtls/tasks/main.yml b/ansible/roles/nginx-cloudflare-mtls/tasks/main.yml
new file mode 100644
index 0000000..21d1b28
--- /dev/null
+++ b/ansible/roles/nginx-cloudflare-mtls/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+- name: Create nginx certificates directory
+ file:
+ path: /etc/nginx/certs
+ state: directory
+ owner: root
+ group: root
+ mode: "0444"
+ tags:
+ - role::nginx-cloudflare-mtls
+
+- name: Copy the cloudflare mutual TLS certificate
+ copy:
+ src: cloudflare.crt
+ dest: /etc/nginx/certs/cloudflare.crt
+ owner: root
+ group: root
+ mode: "0444"
+ tags:
+ - role::nginx-cloudflare-mtls
diff --git a/ansible/roles/nginx-geoip/meta/main.yml b/ansible/roles/nginx-geoip/meta/main.yml
new file mode 100644
index 0000000..8b662c9
--- /dev/null
+++ b/ansible/roles/nginx-geoip/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - role: nginx
diff --git a/ansible/roles/nginx-geoip/tasks/main.yml b/ansible/roles/nginx-geoip/tasks/main.yml
new file mode 100644
index 0000000..e41b1e4
--- /dev/null
+++ b/ansible/roles/nginx-geoip/tasks/main.yml
@@ -0,0 +1,13 @@
+---
+- name: Configure the geoip module
+ copy:
+ # ref https://nginx.org/en/docs/http/ngx_http_geoip_module.html
+ content: geoip_country /usr/share/GeoIP/GeoIP.dat;
+ dest: /etc/nginx/conf.d/geoip.conf
+ owner: root
+ group: root
+ mode: "0444"
+ tags:
+ - role::nginx-geoip
+ notify:
+ - Reload the nginx service
diff --git a/ansible/roles/nginx-ufw/README.md b/ansible/roles/nginx-ufw/README.md
new file mode 100644
index 0000000..e657afb
--- /dev/null
+++ b/ansible/roles/nginx-ufw/README.md
@@ -0,0 +1,3 @@
+# Role "nginx-ufw"
+
+Allows NGINX HTTP and HTTPS traffic through the UFW firewall.
diff --git a/ansible/roles/nginx-ufw/meta/main.yml b/ansible/roles/nginx-ufw/meta/main.yml
new file mode 100644
index 0000000..a6e9124
--- /dev/null
+++ b/ansible/roles/nginx-ufw/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - role: nginx
+ - role: ufw
diff --git a/ansible/roles/nginx-ufw/tasks/main.yml b/ansible/roles/nginx-ufw/tasks/main.yml
new file mode 100644
index 0000000..3b52f14
--- /dev/null
+++ b/ansible/roles/nginx-ufw/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Allow http(s) traffic through the firewall
+ community.general.ufw:
+ app: Nginx Full
+ rule: allow
+ tags:
+ - role::nginx-ufw
diff --git a/ansible/roles/nginx/README.md b/ansible/roles/nginx/README.md
new file mode 100644
index 0000000..9961a69
--- /dev/null
+++ b/ansible/roles/nginx/README.md
@@ -0,0 +1,3 @@
+# Role "nginx"
+
+Installs nginx on target hosts and provides a handler for reloading nginx, for instance on configuration change.
diff --git a/ansible/roles/nginx/files/default_server.conf b/ansible/roles/nginx/files/default_server.conf
new file mode 100644
index 0000000..1d68ff5
--- /dev/null
+++ b/ansible/roles/nginx/files/default_server.conf
@@ -0,0 +1,32 @@
+# Managed by Ansible
+server {
+ listen 80 default_server;
+
+ server_name _;
+
+ return 301 https://$host$request_uri;
+}
+
+server {
+ listen 443 ssl http2 default_server;
+
+ ssl_certificate /etc/letsencrypt/live/pydis.wtf/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/pydis.wtf/privkey.pem;
+
+ location / {
+ set_by_lua_block $url {
+ local urls = {
+ "https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride",
+ "https://en.wikipedia.org/wiki/Tax_evasion",
+ "https://jchri.st/blog/apfs-sadness-on-macos-big-sur.html",
+ "https://cdn.discordapp.com/attachments/675756741417369640/852688961516077086/Screenshot_2021-06-11_at_00.21.22.png",
+ "https://news.ycombinator.com/",
+ "https://www.hertfordshire.gov.uk/latest/letchworth-webcam.jpg",
+ "https://media.discordapp.net/attachments/922169059175444501/952929630459924501/1svkf3xto3n61.png"
+ }
+ return urls [ math.random(#urls) ]
+ }
+
+ return 302 $url;
+ }
+}
diff --git a/ansible/roles/nginx/files/files.pydis.wtf b/ansible/roles/nginx/files/files.pydis.wtf
new file mode 100644
index 0000000..db8416e
--- /dev/null
+++ b/ansible/roles/nginx/files/files.pydis.wtf
@@ -0,0 +1,10 @@
+# Managed by Ansible
+server {
+ listen 443;
+ server_name files.pydis.wtf;
+ root /var/www/turing;
+
+ location / {
+ try_files $uri $uri/;
+ }
+}
diff --git a/ansible/roles/nginx/handlers/main.yml b/ansible/roles/nginx/handlers/main.yml
new file mode 100644
index 0000000..2e84daf
--- /dev/null
+++ b/ansible/roles/nginx/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Reload the nginx service
+ service:
+ name: nginx
+ state: reloaded
+ tags:
+ - role::nginx
diff --git a/ansible/roles/nginx/tasks/main.yml b/ansible/roles/nginx/tasks/main.yml
new file mode 100644
index 0000000..85fe7ec
--- /dev/null
+++ b/ansible/roles/nginx/tasks/main.yml
@@ -0,0 +1,45 @@
+---
+- name: Install NGINX & modules
+ package:
+ name:
+ - nginx
+ - libnginx-mod-http-lua
+ - libnginx-mod-http-geoip
+ state: present
+ tags:
+ - role::nginx
+
+- name: Copy NGINX default config
+ copy:
+ src: default_server.conf
+ dest: /etc/nginx/conf.d/default_server.conf
+ group: root
+ owner: root
+ mode: "0644"
+ tags:
+ - role::nginx
+ notify:
+ - Reload the nginx service
+
+- name: Remove default nginx site
+ file:
+ path: /etc/nginx/sites-enabled/default
+ state: absent
+
+- name: Copy file server config
+ copy:
+ src: files.pydis.wtf
+ dest: /etc/nginx/sites-available/files.pydis.wtf
+ group: root
+ owner: root
+ mode: "0644"
+ tags:
+ - role::nginx
+ notify:
+ - Reload the nginx service
+
+- name: Enable file server
+ file:
+ src: /etc/nginx/sites-available/files.pydis.wtf
+ dest: /etc/nginx/sites-enabled/files.pydis.wtf
+ state: link
diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml
new file mode 100644
index 0000000..154fa6c
--- /dev/null
+++ b/ansible/roles/podman/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Install podman
+ package:
+ name: podman
+ state: present
+ tags:
+ - role::podman
diff --git a/ansible/roles/postgres/handlers/main.yml b/ansible/roles/postgres/handlers/main.yml
new file mode 100644
index 0000000..a036301
--- /dev/null
+++ b/ansible/roles/postgres/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: Restart postgres.
+ service:
+ name: '{{ postgresql_daemon }}'
+ state: "restarted"
diff --git a/ansible/roles/postgres/tasks/main.yml b/ansible/roles/postgres/tasks/main.yml
new file mode 100644
index 0000000..9551c4e
--- /dev/null
+++ b/ansible/roles/postgres/tasks/main.yml
@@ -0,0 +1,34 @@
+- name: Install postgres packages
+ apt:
+ name:
+ - python3-psycopg2
+ - postgresql-{{ postgresql_version }}
+ - postgresql-contrib-{{ postgresql_version }}
+ - libpq-dev
+ state: present
+ tags:
+ - role::postgres
+
+- name: Check postgres is started and enabled on boot
+ service:
+ name: '{{ postgresql_daemon }}'
+ state: started
+ enabled: true
+ tags:
+ - role::postgres
+
+- name: Add postgres users
+ community.postgresql.postgresql_user: "{{ item }}"
+ with_items: "{{ postgresql_users }}"
+ become: true
+ become_user: "{{ postgresql_user }}"
+ tags:
+ - role::postgres
+
+- name: Add postgres databases
+ community.postgresql.postgresql_db: "{{ item }}"
+ with_items: "{{ postgresql_databases }}"
+ become: true
+ become_user: "{{ postgresql_user }}"
+ tags:
+ - role::postgres
diff --git a/ansible/roles/postgres/vars/main.yml b/ansible/roles/postgres/vars/main.yml
new file mode 100644
index 0000000..ddb483a
--- /dev/null
+++ b/ansible/roles/postgres/vars/main.yml
@@ -0,0 +1,7 @@
+postgresql_version: "15"
+postgresql_daemon: "postgresql@{{ postgresql_version }}-main"
+postgres_user: "postgres"
+
+postgresql_users: []
+
+postgresql_databases: []
diff --git a/ansible/roles/prometheus-node-exporter/README.md b/ansible/roles/prometheus-node-exporter/README.md
new file mode 100644
index 0000000..97ed275
--- /dev/null
+++ b/ansible/roles/prometheus-node-exporter/README.md
@@ -0,0 +1,3 @@
+# Role "prometheus-node-exporter"
+
+Installs prometheus-node-exporter on target hosts.
diff --git a/ansible/roles/prometheus-node-exporter/tasks/main.yml b/ansible/roles/prometheus-node-exporter/tasks/main.yml
new file mode 100644
index 0000000..b6247e4
--- /dev/null
+++ b/ansible/roles/prometheus-node-exporter/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Install prometheus-node-exporter
+ package:
+ name: prometheus-node-exporter
+ state: present
+ tags:
+ - role::prometheus-node-exporter
diff --git a/ansible/roles/prometheus/README.md b/ansible/roles/prometheus/README.md
new file mode 100644
index 0000000..febe029
--- /dev/null
+++ b/ansible/roles/prometheus/README.md
@@ -0,0 +1,13 @@
+# Role "prometheus"
+
+Installs and configured Prometheus on target servers.
+
+
+## Variables
+
+- `prometheus_cmdline_options` configures arguments to be added
+ to the prometheus command line, and changing it will result in
+ a restart.
+
+- `prometheus_configuration` is the prometheus configuration, serialized to
+ YAML by Ansible. If unset, the default Prometheus configuration is used.
diff --git a/ansible/roles/prometheus/defaults/main.yml b/ansible/roles/prometheus/defaults/main.yml
new file mode 100644
index 0000000..fbefe91
--- /dev/null
+++ b/ansible/roles/prometheus/defaults/main.yml
@@ -0,0 +1,45 @@
+---
+# Default Prometheus configuration sample
+prometheus_configuration:
+ global:
+ scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+ evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+ # scrape_timeout is set to the global default (10s).
+
+ # Attach these labels to any time series or alerts when communicating with
+ # external systems (federation, remote storage, Alertmanager).
+ external_labels:
+ monitor: 'example'
+
+ # Alertmanager configuration
+ alerting:
+ alertmanagers:
+ - static_configs:
+ - targets: ['localhost:9093']
+
+ # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
+ rule_files:
+ # - "first_rules.yml"
+ # - "second_rules.yml"
+
+ # A scrape configuration containing exactly one endpoint to scrape:
+ # Here it's Prometheus itself.
+ scrape_configs:
+ # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
+ - job_name: 'prometheus'
+
+ # Override the global default and scrape targets from this job every 5 seconds.
+ scrape_interval: 5s
+ scrape_timeout: 5s
+
+ # metrics_path defaults to '/metrics'
+ # scheme defaults to 'http'.
+
+ static_configs:
+ - targets: ['localhost:9090']
+
+ - job_name: node
+ # If prometheus-node-exporter is installed, grab stats about the local
+ # machine by default.
+ static_configs:
+ - targets: ['localhost:9100']
diff --git a/ansible/roles/prometheus/handlers/main.yml b/ansible/roles/prometheus/handlers/main.yml
new file mode 100644
index 0000000..2031275
--- /dev/null
+++ b/ansible/roles/prometheus/handlers/main.yml
@@ -0,0 +1,14 @@
+---
+- name: Reload the prometheus service
+ service:
+ name: prometheus
+ state: reloaded
+ tags:
+ - role::prometheus
+
+- name: Restart the prometheus service
+ service:
+ name: prometheus
+ state: restarted
+ tags:
+ - role::prometheus
diff --git a/ansible/roles/prometheus/tasks/main.yml b/ansible/roles/prometheus/tasks/main.yml
new file mode 100644
index 0000000..b1bb67a
--- /dev/null
+++ b/ansible/roles/prometheus/tasks/main.yml
@@ -0,0 +1,33 @@
+---
+- name: Install prometheus
+ package:
+ name: prometheus
+ state: present
+ tags:
+ - role::prometheus
+
+- name: Configure prometheus command line options
+ lineinfile:
+ path: /etc/default/prometheus
+ regexp: ^ARGS.*
+ line: ARGS="{{ prometheus_cmdline_options }}"
+ tags:
+ - role::prometheus
+ when:
+ - prometheus_cmdline_options is defined
+ notify:
+ - Restart the prometheus service
+
+- name: Configure prometheus
+ copy:
+ content: |
+ # Ansible managed
+ {{ prometheus_configuration | to_nice_yaml }}
+ dest: /etc/prometheus/prometheus.yml
+ owner: prometheus
+ group: prometheus
+ mode: "0400"
+ tags:
+ - role::prometheus
+ notify:
+ - Reload the prometheus service
diff --git a/ansible/roles/ufw/tasks/main.yml b/ansible/roles/ufw/tasks/main.yml
new file mode 100644
index 0000000..1204060
--- /dev/null
+++ b/ansible/roles/ufw/tasks/main.yml
@@ -0,0 +1,37 @@
+- name: Install UFW
+ apt:
+ update_cache: true
+ cache_valid_time: 3600
+ pkg:
+ - ufw
+ tags:
+ - role::ufw
+
+- name: Allow OpenSSH
+ community.general.ufw:
+ rule: allow
+ name: OpenSSH
+ tags:
+ - role::ufw
+
+- name: Enable UFW and deny all traffic by default
+ community.general.ufw:
+ state: enabled
+ policy: deny
+ tags:
+ - role::ufw
+
+- name: Allow WireGuard
+ community.general.ufw:
+ rule: allow
+ proto: udp
+ port: "{{ wireguard_port }}"
+ comment: "Allow WireGuard"
+ tags:
+ - role::ufw
+
+- name: Apply service-specific rules
+ community.general.ufw: "{{ item }}"
+ with_items: "{{ rules }}"
+ tags:
+ - role::ufw
diff --git a/ansible/roles/ufw/vars/main.yml b/ansible/roles/ufw/vars/main.yml
new file mode 100644
index 0000000..da156e5
--- /dev/null
+++ b/ansible/roles/ufw/vars/main.yml
@@ -0,0 +1,6 @@
+rules:
+ - comment: Allow internal traffic
+ interface: wg0
+ direction: in
+ rule: allow
+ from_ip: 10.0.0.0/8
diff --git a/ansible/roles/wireguard/defaults/main/vars.yml b/ansible/roles/wireguard/defaults/main/vars.yml
new file mode 100644
index 0000000..10c80ae
--- /dev/null
+++ b/ansible/roles/wireguard/defaults/main/vars.yml
@@ -0,0 +1,4 @@
+extra_keys:
+ - name: Joe
+ pubkey: /dJ+tKXzxv7nrUleNlF+CGyq7OIVlqL8/9Sn8j+cEAc=
+ subnet: 10.0.1.0/24
diff --git a/ansible/roles/wireguard/handlers/main.yml b/ansible/roles/wireguard/handlers/main.yml
new file mode 100644
index 0000000..86f6400
--- /dev/null
+++ b/ansible/roles/wireguard/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: Reload wg-quick
+ service:
+ name: wg-quick@wg0
+ state: reloaded
diff --git a/ansible/roles/wireguard/tasks/main.yml b/ansible/roles/wireguard/tasks/main.yml
new file mode 100644
index 0000000..9dc92dd
--- /dev/null
+++ b/ansible/roles/wireguard/tasks/main.yml
@@ -0,0 +1,72 @@
+- name: Install WireGuard
+ apt:
+ update_cache: true
+ cache_valid_time: 3600
+ pkg:
+ - wireguard
+ - wireguard-tools
+ - linux-headers-{{ ansible_kernel }}
+ tags:
+ - role::wireguard
+
+- name: Generate WireGuard private key
+ shell: set -o pipefail && wg genkey > /etc/wireguard/key.priv
+ args:
+ executable: /bin/bash
+ creates: /etc/wireguard/key.priv
+ tags:
+ - role::wireguard
+
+- name: Generate WireGuard public key
+ shell: set -o pipefail && cat /etc/wireguard/key.priv | wg pubkey > /etc/wireguard/key.pub
+ args:
+ executable: /bin/bash
+ creates: /etc/wireguard/key.pub
+ tags:
+ - role::wireguard
+
+- name: Ensure file permissions for keys set correctly
+ file:
+ path: '{{ item }}'
+ owner: root
+ group: root
+ mode: '0600'
+ with_items:
+ - /etc/wireguard/key.priv
+ - /etc/wireguard/key.pub
+ tags:
+ - role::wireguard
+
+- name: Fetch private key for all hosts
+ slurp:
+ src: /etc/wireguard/key.priv
+ register: wg_priv_key
+ tags:
+ - role::wireguard
+
+- name: Fetch public key for all hosts
+ slurp:
+ src: /etc/wireguard/key.pub
+ register: wg_pub_key
+ tags:
+ - role::wireguard
+
+- name: Generate WireGuard configuration file
+ template:
+ src: wg0.conf.j2
+ dest: /etc/wireguard/wg0.conf
+ mode: '0600'
+ group: root
+ owner: root
+ notify:
+ - Reload wg-quick
+ tags:
+ - role::wireguard
+
+- name: Start and enable the WireGuard service
+ service:
+ name: wg-quick@wg0
+ enabled: true
+ state: started
+ tags:
+ - role::wireguard
diff --git a/ansible/roles/wireguard/templates/wg0.conf.j2 b/ansible/roles/wireguard/templates/wg0.conf.j2
new file mode 100644
index 0000000..647854a
--- /dev/null
+++ b/ansible/roles/wireguard/templates/wg0.conf.j2
@@ -0,0 +1,25 @@
+# Configuration managed by Ansible
+[Interface]
+Address = {{ wireguard_subnet }}
+ListenPort = {{ wireguard_port }}
+PrivateKey = {{ wg_priv_key['content'] | b64decode | trim }}
+
+PostUp = ip route add local {{ wireguard_subnet }} dev eth0
+
+{% for host in hostvars.keys() if not host == inventory_hostname %}
+# Peer config for: {{ host }}
+[Peer]
+AllowedIPs = {{ hostvars[host]['wireguard_subnet'] }}
+PublicKey = {{ hostvars[host]['wg_pub_key']['content'] | b64decode | trim }}
+Endpoint = {{ host }}.box.pydis.wtf:{{ wireguard_port }}
+PersistentKeepalive = 30
+
+{% endfor %}
+
+{% for key in extra_keys %}
+# DevOps config for: {{ key.name }}
+[Peer]
+AllowedIPs = {{ key.subnet }}
+PublicKey = {{ key.pubkey }}
+
+{% endfor %}