diff options
author | 2023-08-13 14:59:36 +0100 | |
---|---|---|
committer | 2023-08-13 15:54:03 +0100 | |
commit | 431585b5256a0d08f4f3c33122465a88a93ddcb1 (patch) | |
tree | 4cc41cda81655332fa467e6c9c85d1ee9c35b90e /ansible | |
parent | Serve static files from Turing (#116) (diff) |
Move all ansible files to their own folder
Diffstat (limited to 'ansible')
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 @@ + +[1m [32m"Bee" careful [34m__ + [32mwith sudo! [34m// \ + \\_/ [33m// + [35m''-.._.-''-.._.. [33m-(||)(') + '''[0m 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 %} |