From b90e4c2170af528a09d1a041311a092ac87c8b03 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 20:51:02 +0100 Subject: [PATCH 1/8] Add role to deploy MongoDB as quadlet on lovelace --- ansible/playbook.yml | 3 +- ansible/roles/mongodb/README.md | 5 +++ ansible/roles/mongodb/handlers/main.yml | 7 ++++ ansible/roles/mongodb/meta/main.yml | 3 ++ ansible/roles/mongodb/tasks/main.yml | 26 ++++++++++++++ .../mongodb/templates/mongodb.container.j2 | 36 +++++++++++++++++++ .../roles/mongodb/templates/mongodb.image.j2 | 10 ++++++ .../roles/mongodb/templates/mongodb.volume.j2 | 8 +++++ 8 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 ansible/roles/mongodb/README.md create mode 100644 ansible/roles/mongodb/handlers/main.yml create mode 100644 ansible/roles/mongodb/meta/main.yml create mode 100644 ansible/roles/mongodb/tasks/main.yml create mode 100644 ansible/roles/mongodb/templates/mongodb.container.j2 create mode 100644 ansible/roles/mongodb/templates/mongodb.image.j2 create mode 100644 ansible/roles/mongodb/templates/mongodb.volume.j2 diff --git a/ansible/playbook.yml b/ansible/playbook.yml index 976752e2..f2859eb0 100644 --- a/ansible/playbook.yml +++ b/ansible/playbook.yml @@ -62,11 +62,12 @@ roles: - git-mirrors -- name: Deploy our PostgreSQL database hosts +- name: Deploy our database hosts hosts: databases roles: - postgres - prometheus-postgres-exporter + - mongodb - name: Deploy our LDAP server environment to the LDAP host hosts: ldap diff --git a/ansible/roles/mongodb/README.md b/ansible/roles/mongodb/README.md new file mode 100644 index 00000000..e47da129 --- /dev/null +++ b/ansible/roles/mongodb/README.md @@ -0,0 +1,5 @@ +# mongodb + +This role deploys MongoDB using podman quadlets. + +The container starts as root, but drops privileges after startup. diff --git a/ansible/roles/mongodb/handlers/main.yml b/ansible/roles/mongodb/handlers/main.yml new file mode 100644 index 00000000..965e59c6 --- /dev/null +++ b/ansible/roles/mongodb/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart mongodb + ansible.builtin.service: + name: mongodb + state: restarted + tags: + - role::mongodb diff --git a/ansible/roles/mongodb/meta/main.yml b/ansible/roles/mongodb/meta/main.yml new file mode 100644 index 00000000..60b471bc --- /dev/null +++ b/ansible/roles/mongodb/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - podman diff --git a/ansible/roles/mongodb/tasks/main.yml b/ansible/roles/mongodb/tasks/main.yml new file mode 100644 index 00000000..2a9fd26d --- /dev/null +++ b/ansible/roles/mongodb/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: Template quadlet services + ansible.builtin.template: + src: mongodb.{{ item }}.j2 + dest: /etc/containers/systemd/mongodb.{{ item }} + owner: root + group: root + mode: 0o444 + register: mongodb_units + tags: + - role::mongodb + notify: + - Restart mongodb + loop: + - container + - image + - volume + +- name: Start and enable the quadlet + ansible.builtin.service: + name: mongodb.service + daemon_reload: "{{ mongodb_units is changed }}" + state: started + enabled: true + tags: + - role::mongodb diff --git a/ansible/roles/mongodb/templates/mongodb.container.j2 b/ansible/roles/mongodb/templates/mongodb.container.j2 new file mode 100644 index 00000000..febd0906 --- /dev/null +++ b/ansible/roles/mongodb/templates/mongodb.container.j2 @@ -0,0 +1,36 @@ +# {{ ansible_managed }} + +[Unit] +Description = Mongo NoSQL (NonSensical Query Language) Server + +[Container] +Image = mongodb.image +Pull = missing +Volume = mongodb.volume:/data/db +NoNewPrivileges = true +PublishPort = 27017:27017 + +[Service] +# Resource control +CPUQuota = 20% +MemoryMax = 900M +TasksMax = 200 + +# Sandboxing +NoNewPrivileges = true +PrivateDevices = true +ProtectHome = true +ProtectKernelLogs = true +LockPersonality = true +MemoryDenyWriteExecute = true +ProtectKernelModules = true +ProtectSystem = true +PrivateMounts = true +RestrictRealtime = true +RestrictSUIDSGID = true +UMask = 0077 + +[Install] +WantedBy = network-online.target + +# vim: ft=dosini.jinja2: diff --git a/ansible/roles/mongodb/templates/mongodb.image.j2 b/ansible/roles/mongodb/templates/mongodb.image.j2 new file mode 100644 index 00000000..b602ee3b --- /dev/null +++ b/ansible/roles/mongodb/templates/mongodb.image.j2 @@ -0,0 +1,10 @@ +# {{ ansible_managed }} + +[Unit] +Description = Mongo NoSQL (NonSensical Query Language) Server image +After = network-online.target + +[Image] +Image = docker.io/library/mongo:4.4 + +# vim: ft=dosini.jinja2: diff --git a/ansible/roles/mongodb/templates/mongodb.volume.j2 b/ansible/roles/mongodb/templates/mongodb.volume.j2 new file mode 100644 index 00000000..66e94a7d --- /dev/null +++ b/ansible/roles/mongodb/templates/mongodb.volume.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +[Unit] +Description = Mongo NoSQL (NonSensical Query Language) volume + +[Volume] + +# vim: ft=dosini.jinja2: From cb603be1fc4d8095ec56c2a16be95b1f4e5571a6 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 21:05:47 +0100 Subject: [PATCH 2/8] Accept MongoDB connections --- ansible/group_vars/all/nftables.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ansible/group_vars/all/nftables.yml b/ansible/group_vars/all/nftables.yml index d931d465..ab5b0028 100644 --- a/ansible/group_vars/all/nftables.yml +++ b/ansible/group_vars/all/nftables.yml @@ -86,8 +86,12 @@ nftables_configuration: | {% if "databases" in group_names %} # PostgreSQL connections iifname {{ ansible_default_ipv4.interface }} ip saddr @possible_lke_ipv4_addrs tcp dport postgresql ct state new accept + # MongoDB connections + iifname {{ ansible_default_ipv4.interface }} ip saddr @possible_lke_ipv4_addrs tcp dport 27017 ct state new accept {% if ansible_default_ipv6 is defined %} + # And now via IPv6 iifname {{ ansible_default_ipv6.interface }} ip6 saddr @possible_lke_ipv6_addrs tcp dport postgresql ct state new accept + iifname {{ ansible_default_ipv6.interface }} ip6 saddr @possible_lke_ipv6_addrs tcp dport 27017 ct state new accept {% endif %} {% endif %} From 9cdfc730fb15ef46019cae665927b82373679365 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 21:48:59 +0100 Subject: [PATCH 3/8] Configure MongoDB SSL --- ansible/roles/mongodb/meta/main.yml | 2 + ansible/roles/mongodb/tasks/main.yml | 65 ++++++++++++++++++- .../mongodb/templates/mongodb.container.j2 | 7 ++ ansible/roles/mongodb/vars/main.yml | 4 ++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 ansible/roles/mongodb/vars/main.yml diff --git a/ansible/roles/mongodb/meta/main.yml b/ansible/roles/mongodb/meta/main.yml index 60b471bc..6638062f 100644 --- a/ansible/roles/mongodb/meta/main.yml +++ b/ansible/roles/mongodb/meta/main.yml @@ -1,3 +1,5 @@ --- dependencies: + - certbot - podman + - systemd diff --git a/ansible/roles/mongodb/tasks/main.yml b/ansible/roles/mongodb/tasks/main.yml index 2a9fd26d..77e4f564 100644 --- a/ansible/roles/mongodb/tasks/main.yml +++ b/ansible/roles/mongodb/tasks/main.yml @@ -10,16 +10,79 @@ tags: - role::mongodb notify: + - Reload the systemd daemon - Restart mongodb loop: - container - image - volume +- name: Create secrets directory + ansible.builtin.file: + path: /etc/opt/mongodb + state: directory + owner: "{{ mongodb_uid }}" + group: "{{ mongodb_gid }}" + mode: 0o700 + tags: + - role::mongodb + +- name: Create TLS key file + ansible.builtin.shell: > + umask 0077 + + cat + /etc/letsencrypt/live/{{ ansible_fqdn }}/fullchain.pem + /etc/letsencrypt/live/{{ ansible_fqdn }}/privkey.pem + > /etc/opt/mongodb/fullchain-plus-key.pem + + chown "{{ mongodb_uid }}:{{ mongodb_gid }}" /etc/opt/mongodb/fullchain-plus-key.pem + args: + creates: /etc/opt/mongodb/fullchain-plus-key.pem + tags: + - role::mongodb + +- name: Copy TLS fullchain + ansible.builtin.copy: + remote_src: true + src: /etc/letsencrypt/live/{{ ansible_fqdn }}/fullchain.pem + dest: /etc/opt/mongodb/fullchain.pem + owner: "{{ mongodb_uid }}" + group: "{{ mongodb_gid }}" + tags: + - role::mongodb + +- name: Create letsencrypt post-renewal hook + ansible.builtin.copy: + content: | + #!/bin/sh + + set -ex + + umask 0077 + + cat \ + /etc/letsencrypt/live/{{ ansible_fqdn }}/fullchain.pem \ + /etc/letsencrypt/live/{{ ansible_fqdn }}/privkey.pem \ + > /etc/opt/mongodb/fullchain-plus-key.pem + + chown "{{ mongodb_uid }}:{{ mongodb_gid }}" /etc/opt/mongodb/fullchain-plus-key.pem + + cp /etc/letsencrypt/live/{{ ansible_fqdn }}/fullchain.pem /etc/opt/mongodb/fullchain.pem + + # XXX: kill -HUP $PID OK? + systemctl restart mongodb + dest: /etc/letsencrypt/renewal-hooks/deploy/update-mongodb-cert + owner: root + group: root + mode: 0o744 + tags: + - role::mongodb + - name: Start and enable the quadlet ansible.builtin.service: name: mongodb.service - daemon_reload: "{{ mongodb_units is changed }}" + daemon_reload: true # "{{ mongodb_units is changed }}" state: started enabled: true tags: diff --git a/ansible/roles/mongodb/templates/mongodb.container.j2 b/ansible/roles/mongodb/templates/mongodb.container.j2 index febd0906..acb97b00 100644 --- a/ansible/roles/mongodb/templates/mongodb.container.j2 +++ b/ansible/roles/mongodb/templates/mongodb.container.j2 @@ -7,8 +7,15 @@ Description = Mongo NoSQL (NonSensical Query Language) Server Image = mongodb.image Pull = missing Volume = mongodb.volume:/data/db +# XXX: role should to have a dependency for the lovelace cert +Volume = /etc/opt/mongodb:/certs:ro NoNewPrivileges = true PublishPort = 27017:27017 +Exec = \ + --auth \ + --tlsMode requireTLS \ + --tlsCertificateKeyFile /certs/fullchain-plus-key.pem \ + --tlsCAFile /certs/fullchain.pem [Service] # Resource control diff --git a/ansible/roles/mongodb/vars/main.yml b/ansible/roles/mongodb/vars/main.yml new file mode 100644 index 00000000..d4a79bb3 --- /dev/null +++ b/ansible/roles/mongodb/vars/main.yml @@ -0,0 +1,4 @@ +--- +# MongoDB UID in the container +mongodb_uid: 999 +mongodb_gid: 999 From 31bb2e062c6bc9c2ca48ff5bde396da7e1f526b9 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 21:49:21 +0100 Subject: [PATCH 4/8] Accept forwarding for in-container networking --- ansible/group_vars/all/nftables.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/group_vars/all/nftables.yml b/ansible/group_vars/all/nftables.yml index ab5b0028..18dae1f0 100644 --- a/ansible/group_vars/all/nftables.yml +++ b/ansible/group_vars/all/nftables.yml @@ -120,7 +120,8 @@ nftables_configuration: | chain forward { type filter hook forward priority 0 - policy drop + # podman + policy accept ct state invalid drop ct state established,related accept From 9d1ce550253bec7808c0d8c86601f3b0c92d1cc0 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 22:03:35 +0100 Subject: [PATCH 5/8] Do not raise unrealistic expectations --- ansible/roles/mongodb/templates/mongodb.container.j2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/roles/mongodb/templates/mongodb.container.j2 b/ansible/roles/mongodb/templates/mongodb.container.j2 index acb97b00..9a9a8fb6 100644 --- a/ansible/roles/mongodb/templates/mongodb.container.j2 +++ b/ansible/roles/mongodb/templates/mongodb.container.j2 @@ -15,7 +15,8 @@ Exec = \ --auth \ --tlsMode requireTLS \ --tlsCertificateKeyFile /certs/fullchain-plus-key.pem \ - --tlsCAFile /certs/fullchain.pem + --tlsCAFile /certs/fullchain.pem \ + --tlsAllowConnectionsWithoutCertificates [Service] # Resource control From 66c9291036223bec7bf99d34d4c884db083f058f Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 22:06:06 +0100 Subject: [PATCH 6/8] Add troubleshooting section for MongoDB docs --- ansible/roles/mongodb/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ansible/roles/mongodb/README.md b/ansible/roles/mongodb/README.md index e47da129..eab08740 100644 --- a/ansible/roles/mongodb/README.md +++ b/ansible/roles/mongodb/README.md @@ -3,3 +3,27 @@ This role deploys MongoDB using podman quadlets. The container starts as root, but drops privileges after startup. + + +## Troubleshooting + +- **MongoDB refuses connections** + + Verify that: + + 1. Your outbound IP is within the whitelisted LKE ranges in `/etc/nftables` - + the `nftables` Ansible role may be re-run to fetch them freshly + + 2. Your outbound IP is not banned by `fail2ban`, see `fail2ban-client banned` + + 3. You are using a sane piece of software that does not randomly appear to not + bind on ports properly: + + > I encountered the same problem for an half an hour or so but it was not + > an issue after that. I changed nothing in my code or my firewall + > settings. Just waited a bit and the program worked as usual. So I think + > this may be something that is wrong with the Mongodb servers or it may be + > due to a slow network connection. Maybe check the uri and just waiting + > for a while. It worked for me. + + When in doubt, `sudo systemctl restart mongodb` From b2ec1265209b9e450c8de6807bbcfddbfede933f Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 16 Nov 2025 22:15:22 +0100 Subject: [PATCH 7/8] Update blackbox MongoDB connection string Already deployed to the cluster. --- .../namespaces/databases/blackbox/blackbox-configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/namespaces/databases/blackbox/blackbox-configmap.yaml b/kubernetes/namespaces/databases/blackbox/blackbox-configmap.yaml index c3a06d76..86609d14 100644 --- a/kubernetes/namespaces/databases/blackbox/blackbox-configmap.yaml +++ b/kubernetes/namespaces/databases/blackbox/blackbox-configmap.yaml @@ -8,7 +8,7 @@ data: databases: mongodb: main_mongodb: - connection_string: mongodb://{{ MONGO_INITDB_ROOT_USERNAME }}:{{ MONGO_INITDB_ROOT_PASSWORD }}@mongodb.databases.svc.cluster.local:27017 + connection_string: mongodb://{{ MONGO_INITDB_ROOT_USERNAME }}:{{ MONGO_INITDB_ROOT_PASSWORD }}@lovelace.box.pydis.wtf:27017 postgres: lovelace_postgres: From e6b6390984753ec785e70bb966e1f751c82d0aad Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 16 Nov 2025 21:16:26 +0000 Subject: [PATCH 8/8] Update modmail & forms secrets to use new mongodb conn string --- ansible/roles/mongodb/tasks/main.yml | 7 ++++--- .../forms/forms-backend/secrets.yaml | Bin 943 -> 939 bytes kubernetes/namespaces/modmail/secrets.yaml | Bin 1005 -> 1002 bytes 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ansible/roles/mongodb/tasks/main.yml b/ansible/roles/mongodb/tasks/main.yml index 77e4f564..fbee6f31 100644 --- a/ansible/roles/mongodb/tasks/main.yml +++ b/ansible/roles/mongodb/tasks/main.yml @@ -5,7 +5,7 @@ dest: /etc/containers/systemd/mongodb.{{ item }} owner: root group: root - mode: 0o444 + mode: "0444" register: mongodb_units tags: - role::mongodb @@ -23,7 +23,7 @@ state: directory owner: "{{ mongodb_uid }}" group: "{{ mongodb_gid }}" - mode: 0o700 + mode: "0700" tags: - role::mongodb @@ -49,6 +49,7 @@ dest: /etc/opt/mongodb/fullchain.pem owner: "{{ mongodb_uid }}" group: "{{ mongodb_gid }}" + mode: "0400" tags: - role::mongodb @@ -75,7 +76,7 @@ dest: /etc/letsencrypt/renewal-hooks/deploy/update-mongodb-cert owner: root group: root - mode: 0o744 + mode: "0744" tags: - role::mongodb diff --git a/kubernetes/namespaces/forms/forms-backend/secrets.yaml b/kubernetes/namespaces/forms/forms-backend/secrets.yaml index 99a6d0a33b4c026548b3d17b75285833fd42ab1a..fcef65ab6a50ee8f8ad41da137d72dcee75bac82 100644 GIT binary patch literal 939 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