summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Fennell <matthew@fennell.dev>2024-04-04 22:54:05 +0100
committerMatthew Fennell <matthew@fennell.dev>2024-04-04 22:54:05 +0100
commitde867dadbcc3c69d97acf96bf3e86d11295eea39 (patch)
tree2962ef65772790097581f19a2af9b12ac30087e4
parentd29a60b1afec942fe4f7ca7baa95d870afaae83e (diff)
Bring repo up-to-date with current prod config
-rw-r--r--Makefile18
-rw-r--r--files/50-disable-password-auth.conf5
-rw-r--r--files/nginx_conf.j231
-rw-r--r--files/prosody.cfg.lua.j222
-rw-r--r--files/virtual_host.cfg.lua.j220
-rw-r--r--playbook.yaml218
6 files changed, 285 insertions, 29 deletions
diff --git a/Makefile b/Makefile
index 1b9b4dd..21f2c63 100644
--- a/Makefile
+++ b/Makefile
@@ -3,17 +3,29 @@
#
# SPDX-License-Identifier: AGPL-3.0-only
-.PHONY: check lint staging prod
+.PHONY: check staging prod lint staging-fresh prod-fresh
check:
make lint
make staging
staging:
- ansible-playbook --inventory inventory/staging.ini playbook.yaml
+ ansible-playbook --forks 1 --inventory inventory/staging.ini playbook.yaml
prod:
- ansible-playbook --inventory inventory/prod.ini playbook.yaml
+ ansible-playbook --forks 1 --inventory inventory/prod.ini playbook.yaml
lint:
precious --ascii lint --all
+
+staging-fresh:
+ ~/Documents/libcloud/recreate xmpp-continuous-nonprod
+ ANSIBLE_SSH_ARGS="-F /tmp/early_ssh_config" ansible-playbook --forks 1 \
+ --inventory inventory/staging.ini playbook.yaml
+
+prod-fresh:
+ echo "You are about to recreate PROD. Sleeping for 60s to let this sink in"
+ sleep 60
+ ~/Documents/libcloud/recreate xmpp-prod
+ ANSIBLE_SSH_ARGS="-F /tmp/early_ssh_config" ansible-playbook --forks 1 \
+ --inventory inventory/prod.ini playbook.yaml
diff --git a/files/50-disable-password-auth.conf b/files/50-disable-password-auth.conf
new file mode 100644
index 0000000..6c684d3
--- /dev/null
+++ b/files/50-disable-password-auth.conf
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
+#
+# SPDX-License-Identifier: AGPL-3.0-only
+
+PasswordAuthentication no
diff --git a/files/nginx_conf.j2 b/files/nginx_conf.j2
new file mode 100644
index 0000000..fbddce8
--- /dev/null
+++ b/files/nginx_conf.j2
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
+#
+# SPDX-License-Identifier: AGPL-3.0-only
+
+server {
+ listen 443 ssl;
+ listen [::]:443;
+
+ ssl_certificate /etc/prosody/certs/{{ virtual_host }}.crt;
+ ssl_certificate_key /etc/prosody/certs/{{ virtual_host }}.key;
+
+ server_name {{ delegate_host }};
+
+ location / {
+ proxy_pass https://localhost:5281;
+ proxy_set_header Host "{{ delegate_host }}";
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_buffering off;
+ tcp_nodelay on;
+ }
+}
+
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name {{ delegate_host }};
+
+ return 301 https://$host$request_uri;
+}
diff --git a/files/prosody.cfg.lua.j2 b/files/prosody.cfg.lua.j2
index b6d9fc1..e20ba4e 100644
--- a/files/prosody.cfg.lua.j2
+++ b/files/prosody.cfg.lua.j2
@@ -2,8 +2,6 @@
--
-- SPDX-License-Identifier: AGPL-3.0-only
-admins = { }
-
plugin_paths = { "/usr/local/lib/prosody/modules" }
modules_enabled = {
@@ -17,6 +15,8 @@ modules_enabled = {
"dialback";
"disco";
"external_services";
+ "http";
+ "http_libjs";
"limits";
"mam";
"mimicking";
@@ -25,9 +25,6 @@ modules_enabled = {
"posix";
"private";
"proxy65";
- "register";
- "register_apps";
- "reload_modules";
"roster";
"saslauth";
"server_contact_info";
@@ -41,10 +38,6 @@ modules_enabled = {
"version";
}
-reload_modules = {
- "tls";
-}
-
modules_disabled = { }
pidfile = "/run/prosody/prosody.pid";
@@ -62,15 +55,18 @@ authentication = "internal_hashed"
certificates = "certs"
c2s_direct_tls_ports = { 5223 }
s2s_direct_tls_ports = { 5270 }
+use_dane = true
-- From Monal considerations for XMPP server admins
smacks_hibernation_time = 86400
allow_registration = true
-site_apps_show = {
- "conversations";
- "dino";
- "monal";
+invite_expiry = 86400 * 365
+registration_invite_only = true
+
+firewall_scripts = {
+ "module:scripts/spam-blocking.pfw";
+ "module:script/spam-blocklists.pfw";
}
limits = {
diff --git a/files/virtual_host.cfg.lua.j2 b/files/virtual_host.cfg.lua.j2
index 6db9e98..272e24b 100644
--- a/files/virtual_host.cfg.lua.j2
+++ b/files/virtual_host.cfg.lua.j2
@@ -4,9 +4,27 @@
VirtualHost "{{ virtual_host }}"
+admins = { "admin@{{ virtual_host }}" };
+
+modules_enabled = {
+ "invites";
+ "invites_adhoc";
+ "invites_page";
+ "invites_register";
+ "register";
+ "register_apps";
+}
+
+contact_info = {
+ abuse = { "xmpp:abuse@{{ virtual_host }}" };
+ admin = { "xmpp:admin@{{ virtual_host }}" };
+}
+
turn_external_host = "turn.{{ virtual_host }}"
turn_external_secret = "{{ turn_secret }}"
+allow_user_invites = true
+http_external_url = "https://{{ virtual_host }}/"
site_name = "{{ virtual_host }}"
Component "upload.{{ virtual_host }}" "http_file_share"
@@ -19,3 +37,5 @@ modules_enabled = {
log_all_rooms = true
restrict_room_creation = "local"
+muc_room_default_public = false
+muc_room_default_public_jids = true
diff --git a/playbook.yaml b/playbook.yaml
index bfbc5eb..5de4fea 100644
--- a/playbook.yaml
+++ b/playbook.yaml
@@ -51,6 +51,16 @@
group: admin
mode: preserve
+ - name: Retrieve DANE hash
+ ansible.builtin.shell:
+ cmd: >
+ openssl x509 -in ~/.lego/certificates/{{ virtual_host }}.crt -noout
+ -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256
+ -binary | hexdump -ve '/1 "%02x"'
+ register: dane_hash
+ changed_when: false
+ delegate_to: localhost
+
# We allow status code 400 here as this is returned by deSEC if the domain
# already exists. Ideally, we should filter out genuinely good/bad requests
# here using the response.
@@ -98,6 +108,7 @@
ttl: 3600
records: "{{ domain_keys }}"
delegate_to: localhost
+ when: domain_with_ds != ""
- name: Ensure records are registered in subdomain
ansible.builtin.uri:
@@ -107,22 +118,66 @@
headers:
Authorization: Token {{ desec_token }}
body:
+ - subname: "conference"
+ type: CNAME
+ ttl: 3600
+ records: ["{{ delegate_host }}."]
- subname: "turn"
type: CNAME
ttl: 3600
- records: ["{{ virtual_host }}."]
+ records: ["{{ delegate_host }}."]
- subname: "upload"
type: CNAME
ttl: 3600
- records: ["{{ virtual_host }}."]
+ records: ["{{ delegate_host }}."]
+ - subname: "_xmpp-client._tcp"
+ type: SRV
+ ttl: 3600
+ records: ["0 5 5222 {{ delegate_host }}."]
- subname: "_xmpps-client._tcp"
type: SRV
ttl: 3600
- records: ["0 5 5223 {{ virtual_host }}."]
+ records: ["0 5 5223 {{ delegate_host }}."]
+ - subname: "_xmpp-server._tcp"
+ type: SRV
+ ttl: 3600
+ records: ["0 5 5269 {{ delegate_host }}."]
- subname: "_xmpps-server._tcp"
type: SRV
ttl: 3600
- records: ["0 5 5270 {{ virtual_host }}."]
+ records: ["0 5 5270 {{ delegate_host }}."]
+ - subname: "_xmpps-server._tcp.upload"
+ type: SRV
+ ttl: 3600
+ records: ["0 5 5270 {{ delegate_host }}."]
+ - subname: "_xmpps-server._tcp.conference"
+ type: SRV
+ ttl: 3600
+ records: ["0 5 5270 {{ delegate_host }}."]
+ - subname: '{{ "_5222._tcp" + tlsa_appended_subdomain }}'
+ type: TLSA
+ ttl: 3600
+ records: ["3 1 1 {{ dane_hash.stdout }}"]
+ - subname: '{{ "_5223._tcp" + tlsa_appended_subdomain }}'
+ type: TLSA
+ ttl: 3600
+ records: ["3 1 1 {{ dane_hash.stdout }}"]
+ - subname: '{{ "_5269._tcp" + tlsa_appended_subdomain }}'
+ type: TLSA
+ ttl: 3600
+ records: ["3 1 1 {{ dane_hash.stdout }}"]
+ - subname: '{{ "_5270._tcp" + tlsa_appended_subdomain }}'
+ type: TLSA
+ ttl: 3600
+ records: ["3 1 1 {{ dane_hash.stdout }}"]
+ - subname: ""
+ type: CAA
+ ttl: 3600
+ records:
+ - "0 issue \"letsencrypt.org;validationmethods=dns-01;accounturi=\
+ {{ acme_account_uri_prod }}\""
+ - "0 issue \"letsencrypt.org;validationmethods=dns-01;accounturi=\
+ {{ acme_account_uri_nonprod }}\""
delegate_to: localhost
# We specifically use apt instead of the more general package module here,
@@ -136,10 +191,14 @@
name:
- borgmatic # Backups
- coturn # Audio / video calling server
+ - libjs-bootstrap4 # Used by invite webpage
+ - libjs-jquery # Used by invite webpage
- lua-dbi-postgresql # Prosody postgres connection
+ - nginx # Serve invite webpages
- postgresql # Database
- prosody # XMPP server
- prosody-modules # Extra addons
+ - python3-pexpect # Used by ansible expect role
- python3-psycopg2 # Used by ansible postgres role
- ufw # Firewall
state: present
@@ -154,7 +213,7 @@
loop:
- OpenSSH
- Turnserver
- - WWW
+ - WWW Full
- XMPP
become: true
@@ -185,6 +244,33 @@
- 5281 # XEP-0363
become: true
+ - name: Ensure default nginx config is removed
+ ansible.builtin.file:
+ path: "/etc/nginx/sites-enabled/default"
+ state: absent
+ become: true
+ notify: Restart nginx
+
+ - name: Ensure nginx config is installed
+ ansible.builtin.template:
+ src: "{{ playbook_dir }}/files/nginx_conf.j2"
+ dest: /etc/nginx/sites-available/{{ virtual_host }}
+ owner: root
+ group: root
+ mode: "0644"
+ become: true
+ notify: Restart nginx
+
+ - name: Ensure nginx config is enabled
+ ansible.builtin.file:
+ src: /etc/nginx/sites-available/{{ virtual_host }}
+ dest: /etc/nginx/sites-enabled/{{ virtual_host }}
+ owner: root
+ group: root
+ state: link
+ become: true
+ notify: Restart nginx
+
- name: Ensure turn is configured
ansible.builtin.template:
src: "{{ playbook_dir }}/files/turnserver.conf.j2"
@@ -193,7 +279,7 @@
group: prosody
mode: "0640"
become: true
- notify: Reload coturn
+ notify: Restart coturn
- name: Ensure prosody database is set up
community.postgresql.postgresql_db:
@@ -250,7 +336,7 @@
group: prosody
mode: "0640"
become: true
- notify: Reload prosody
+ notify: Restart prosody
- name: Ensure host-specific prosody configuration is available
ansible.builtin.template:
@@ -260,7 +346,7 @@
group: prosody
mode: "0644"
become: true
- notify: Reload prosody
+ notify: Restart prosody
- name: Ensure host-specific prosody configuration is set
ansible.builtin.file:
@@ -270,7 +356,70 @@
group: prosody
state: link
become: true
- notify: Reload prosody
+ notify: Restart prosody
+
+ - name: Ensure localhost prosody configuration is removed
+ ansible.builtin.file:
+ path: "/etc/prosody/conf.d/localhost.cfg.lua"
+ state: absent
+ become: true
+ notify: Restart prosody
+
+ - name: Ensure localhost prosody configuration is not available
+ ansible.builtin.file:
+ path: "/etc/prosody/conf.avail/localhost.cfg.lua"
+ state: absent
+ become: true
+
+ - name: Ensure example prosody configuration is not available
+ ansible.builtin.file:
+ path: "/etc/prosody/conf.avail/example.com.cfg.lua"
+ state: absent
+ become: true
+
+ - name: Ensure prosody key is installed
+ ansible.builtin.copy:
+ src: "~/.lego/certificates/{{ virtual_host }}.key"
+ dest: "/etc/prosody/certs"
+ mode: "0640"
+ owner: root
+ group: prosody
+ become: true
+ notify: Restart prosody
+
+ - name: Ensure prosody certificate is installed
+ ansible.builtin.copy:
+ src: "~/.lego/certificates/{{ virtual_host }}.crt"
+ dest: "/etc/prosody/certs"
+ mode: "0640"
+ owner: root
+ group: prosody
+ become: true
+ notify: Restart prosody
+
+ - name: Ensure prosody users are registered
+ ansible.builtin.expect:
+ command: "prosodyctl adduser {{ item.name }}@{{ virtual_host }}"
+ responses:
+ (?i)password: "{{ item.password }}"
+ become: true
+ register: adduser_result
+ changed_when:
+ "'That user already exists' not in adduser_result.stdout_lines"
+ failed_when: false
+ no_log: true
+ with_items: "{{ users }}"
+
+ - name: Ensure prosody users have the given password
+ ansible.builtin.expect:
+ command: "prosodyctl passwd {{ item.name }}@{{ virtual_host }}"
+ responses:
+ (?i)password: "{{ item.password }}"
+ become: true
+ register: passwd_result
+ changed_when: true
+ no_log: true
+ with_items: "{{ users }}"
- name: Ensure prosody is enabled
ansible.builtin.service:
@@ -306,20 +455,51 @@
validate: validate-borgmatic-config --config %s
become: true
+ # Vultr adds a custom sshd_config file that enabled password authentication.
+ # I don't want this to be enabled, since I'm already copying the public key.
+ - name: Ensure password authentication is not explicitly enabled
+ ansible.builtin.file:
+ path: "/etc/ssh/sshd_config.d/50-cloud-init.conf"
+ state: absent
+ become: true
+ notify: Restart sshd
+
+ - name: Ensure password based authentication is disabled
+ ansible.builtin.copy:
+ src: "{{ playbook_dir }}/files/50-disable-password-auth.conf"
+ dest: "/etc/ssh/sshd_config.d/50-disable-password-auth.conf"
+ owner: root
+ group: root
+ mode: "0644"
+ become: true
+ notify: Restart sshd
+
handlers:
- - name: Reload prosody
+ - name: Restart prosody
ansible.builtin.service:
name: prosody
- state: reloaded
+ state: restarted
become: true
- - name: Reload coturn
+ - name: Restart coturn
ansible.builtin.service:
name: coturn
state: restarted
become: true
+ - name: Restart sshd
+ ansible.builtin.service:
+ name: sshd
+ state: restarted
+ become: true
+
+ - name: Restart nginx
+ ansible.builtin.service:
+ name: nginx
+ state: restarted
+ become: true
+
vars:
domain_keys: >-
{{- domain.json["keys"]
@@ -327,5 +507,17 @@
| flatten
| select("search", " 13 2 ") -}}
parent_host: "{{ virtual_host.split('.')[1:] | join('.') }}"
- domain_with_ds: "{{ parent_domain.json | map(attribute='name') | first }}"
+ domain_with_ds: >-
+ {{- parent_domain.json
+ | map(attribute='name')
+ | first
+ | default(None) -}}
ds_subname: "{{ virtual_host | regex_replace('.' + domain_with_ds, '') }}"
+ delegate_host: >-
+ {{- virtual_host
+ if delegate_prefix == ""
+ else delegate_prefix + "." + virtual_host -}}
+ tlsa_appended_subdomain: >-
+ {{- ""
+ if delegate_prefix == ""
+ else "." + delegate_prefix -}}