From cfb228cada0082cb005d1351fcd5dbd68c2e90c7 Mon Sep 17 00:00:00 2001 From: Thomas Kleinendorst Date: Sun, 6 Oct 2024 17:45:04 +0200 Subject: [PATCH] Add secure postgres deployment --- playbook.yml | 3 +- roles/actual/tasks/main.yml | 2 +- roles/changedetection/tasks/main.yml | 2 +- roles/monitoring/tasks/main.yml | 2 +- roles/nginx/templates/cloudflare.ini.j2 | 2 +- roles/nginx/vars/main/vault.yml | 9 ---- roles/pi-hole/tasks/main.yml | 2 +- roles/podman-container/tasks/main.yml | 12 ++--- roles/podman-container/vars/main/defaults.yml | 2 + .../files/ensure_certificate_setup.sh | 30 +++++++++++ roles/postgres/tasks/main.yml | 53 +++++++++++++++++++ roles/postgres/vars/main/defaults.yml | 3 ++ roles/postgres/vars/main/vault.yml | 11 ++++ roles/wedding/tasks/main.yml | 6 +-- roles/wedding/vars/main/defaults.yml | 2 +- 15 files changed, 115 insertions(+), 26 deletions(-) delete mode 100644 roles/nginx/vars/main/vault.yml create mode 100644 roles/postgres/files/ensure_certificate_setup.sh create mode 100644 roles/postgres/tasks/main.yml create mode 100644 roles/postgres/vars/main/defaults.yml create mode 100644 roles/postgres/vars/main/vault.yml diff --git a/playbook.yml b/playbook.yml index 594b3c8..845b881 100644 --- a/playbook.yml +++ b/playbook.yml @@ -15,13 +15,14 @@ - role: basic-intalls - role: user - role: cloudflare-ddns + - role: cloudflared - role: nginx - role: actual - role: changedetection - role: pi-hole - role: monitoring + - role: postgres - role: wedding - - role: cloudflared vars: # devsec.hardening.ssh_hardening vars: ssh_client_port: 22 # Default, but duplicated here for documentation purpose. Not changed because its only accessible via LAN. diff --git a/roles/actual/tasks/main.yml b/roles/actual/tasks/main.yml index a962fb9..7e7f30e 100644 --- a/roles/actual/tasks/main.yml +++ b/roles/actual/tasks/main.yml @@ -17,7 +17,7 @@ podman_container_tag: "{{ actual_version }}" podman_container_publish: - 127.0.0.1:5006:5006 - podman_container_volumes: + podman_simple_container_volumes: - name: actual_data mnt: /data - name: Include simple-reverse-proxy role diff --git a/roles/changedetection/tasks/main.yml b/roles/changedetection/tasks/main.yml index 9b507a7..73350fb 100644 --- a/roles/changedetection/tasks/main.yml +++ b/roles/changedetection/tasks/main.yml @@ -17,7 +17,7 @@ podman_container_tag: "{{ changedetection_version }}" podman_container_publish: - 127.0.0.1:5000:5000 - podman_container_volumes: + podman_simple_container_volumes: - name: changedetection_data mnt: /datastore - name: Include simple-reverse-proxy role diff --git a/roles/monitoring/tasks/main.yml b/roles/monitoring/tasks/main.yml index efe299e..787d598 100644 --- a/roles/monitoring/tasks/main.yml +++ b/roles/monitoring/tasks/main.yml @@ -41,7 +41,7 @@ GF_INSTALL_PLUGINS: "grafana-clock-panel 2.1.7" podman_container_publish: - 127.0.0.1:3000:3000 - podman_container_volumes: + podman_simple_container_volumes: - name: grafana_storage mnt: /var/lib/grafana - name: Include simple-reverse-proxy role - Grafana diff --git a/roles/nginx/templates/cloudflare.ini.j2 b/roles/nginx/templates/cloudflare.ini.j2 index 1f5e507..cb10413 100644 --- a/roles/nginx/templates/cloudflare.ini.j2 +++ b/roles/nginx/templates/cloudflare.ini.j2 @@ -1,2 +1,2 @@ # Cloudflare API token used by Certbot -dns_cloudflare_api_token = {{ dns_cloudflare_api_token }} +dns_cloudflare_api_token = {{ dns_cloudflare_token }} diff --git a/roles/nginx/vars/main/vault.yml b/roles/nginx/vars/main/vault.yml deleted file mode 100644 index d1c0a19..0000000 --- a/roles/nginx/vars/main/vault.yml +++ /dev/null @@ -1,9 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -35613135623165306639373939396435656431326134336466636666393637333532623036303831 -6534646334633731313838323138303261663536376330640a376538653563353365336634346338 -34663031643265623838396239383164303865346332366361313839386533363530336361373930 -6438313861353563630a343738383365656531313137613361323636653635393232343738633433 -63356634323264623134313565386362663131313963373433306636383661373930323262353663 -64393433393639346166666433396363313465373032343239633939343830303465633564353130 -37333437643064346233633863346632393266633435396433396563653737386233346231303061 -37623138386233303764 diff --git a/roles/pi-hole/tasks/main.yml b/roles/pi-hole/tasks/main.yml index 1fc8b60..7a757b7 100644 --- a/roles/pi-hole/tasks/main.yml +++ b/roles/pi-hole/tasks/main.yml @@ -19,7 +19,7 @@ - 127.0.0.1:5053:53/tcp - 127.0.0.1:5053:53/udp - 127.0.0.1:8080:80 - podman_container_volumes: + podman_simple_container_volumes: - name: etc-pihole mnt: /etc/pihole - name: etc-dnsmasq.d diff --git a/roles/podman-container/tasks/main.yml b/roles/podman-container/tasks/main.yml index df77d2d..cbac370 100644 --- a/roles/podman-container/tasks/main.yml +++ b/roles/podman-container/tasks/main.yml @@ -18,7 +18,7 @@ path: "/home/{{ container_user }}/{{ item.name }}" state: directory mode: '0700' - loop: "{{ podman_container_volumes }}" + loop: "{{ podman_simple_container_volumes }}" loop_control: label: "{{ item.name }}" index_var: index @@ -54,23 +54,21 @@ notify: Reload systemd (daemon-reload) - name: Flush handlers ansible.builtin.meta: flush_handlers - - name: Define empty volume array - ansible.builtin.set_fact: - volumes: [] - name: Map volumes to Podman accepted list ansible.builtin.set_fact: - volumes: "{{ volumes + ['/home/' + container_user + '/' + item.name + ':' + item.mnt] }}" - with_items: "{{ podman_container_volumes }}" + podman_container_volumes: "{{ podman_container_volumes + ['/home/' + container_user + '/' + item.name + ':' + item.mnt] }}" + with_items: "{{ podman_simple_container_volumes }}" - name: Start the container containers.podman.podman_container: name: "{{ podman_container_name }}" image: "{{ podman_container_image }}:{{ podman_container_tag }}" restart_policy: always user: root # Still isolated from host system 👍 + command: "{{ podman_container_command }}" hostname: "{{ ansible_facts['hostname'] }}" publish: "{{ podman_container_publish }}" env: "{{ podman_container_env }}" - volumes: "{{ volumes }}" + volumes: "{{ podman_container_volumes }}" state: stopped # For more information on the systemd startup service, see: https://linuxhandbook.com/autostart-podman-containers/ generate_systemd: diff --git a/roles/podman-container/vars/main/defaults.yml b/roles/podman-container/vars/main/defaults.yml index 23c031c..8246023 100644 --- a/roles/podman-container/vars/main/defaults.yml +++ b/roles/podman-container/vars/main/defaults.yml @@ -1,2 +1,4 @@ --- podman_container_env: {} +podman_container_volumes: [] +podman_container_command: [] diff --git a/roles/postgres/files/ensure_certificate_setup.sh b/roles/postgres/files/ensure_certificate_setup.sh new file mode 100644 index 0000000..8c6184a --- /dev/null +++ b/roles/postgres/files/ensure_certificate_setup.sh @@ -0,0 +1,30 @@ +#!/bin/bash +echo "Running as $(whoami)..." + +target_user='postgres' +# This user shouldn't be mapped to postgres on the host but rather to postgres on the container. +# This user has host uid: 558821 (in container it's uid: 70). This number is resolved by getting the start +# of the subuid range for this user and then than adding 70 (-1) to it (since we know that that is the uid +# of the postgres user within the container). +target_path_subuid_start="$(su $target_user -c 'grep $USER /etc/subuid | cut -d ":" -f 2')" +target_host_postgres_id=$(($target_path_subuid_start + 70 - 1)) + +certsPath="/home/$target_user/certs" + +if [[ ! -e "$certsPath" ]]; then + echo "Certs directory doesn't exist, creating certs directory: $certsPath..." + mkdir "$certsPath" +fi + +echo "Copying certificates..." +cert_files='/etc/letsencrypt/live/postgres.kleinendorst.info/fullchain.pem /etc/letsencrypt/live/postgres.kleinendorst.info/privkey.pem' +for srcPath in $cert_files; do + echo "Copying: $srcPath to $certsPath..." + cp -L "$srcPath" "$certsPath" + + newFileName="$certsPath/$(basename $srcPath)" + echo "Setting permissions for: $newFileName to uid: $target_host_postgres_id..." + + chown "$target_host_postgres_id:$target_host_postgres_id" "$newFileName" + chmod 0600 "$newFileName" +done diff --git a/roles/postgres/tasks/main.yml b/roles/postgres/tasks/main.yml new file mode 100644 index 0000000..0301f87 --- /dev/null +++ b/roles/postgres/tasks/main.yml @@ -0,0 +1,53 @@ +--- +- name: Include user role + ansible.builtin.include_role: + name: user + vars: + user_username: "{{ postgres_unix_username }}" + user_password: "{{ postgres_unix_password }}" +- name: Install ensure_certificate_setup.sh + become: true + ansible.builtin.copy: + src: ensure_certificate_setup.sh + dest: "/root/.bin/" + mode: '0700' + owner: root +- name: Create certificates for PostgreSQL (postgres.kleinendorst.info) + become: true + ansible.builtin.command: + cmd: >- + /snap/bin/certbot certonly + --dns-cloudflare + --dns-cloudflare-propagation-seconds 120 + --dns-cloudflare-credentials '/root/.secrets/certbot/cloudflare.ini' + --deploy-hook '/root/.bin/ensure_certificate_setup.sh' + --agree-tos -m {{ administration_email }} + -d postgres.kleinendorst.info + creates: "/etc/letsencrypt/live/postgres.kleinendorst.info" +- name: Create the postgres container + ansible.builtin.include_role: + name: podman-container + apply: + become: true + become_user: "{{ postgres_unix_username }}" + vars: + podman_container_name: postgres + podman_container_image: docker.io/postgres + podman_container_tag: "{{ postgres_version }}" + podman_container_publish: + - 0.0.0.0:5432:5432 + podman_container_volumes: + - "/home/{{ postgres_unix_username }}/certs/fullchain.pem:/var/lib/postgresql/fullchain.pem:ro" + - "/home/{{ postgres_unix_username }}/certs/privkey.pem:/var/lib/postgresql/privkey.pem:ro" + podman_simple_container_volumes: + - name: postgres_data + mnt: /var/lib/postgresql/data + podman_container_command: + - -c + - ssl=on + - -c + - ssl_cert_file=/var/lib/postgresql/fullchain.pem + - -c + - ssl_key_file=/var/lib/postgresql/privkey.pem + podman_container_env: + POSTGRES_PASSWORD: "{{ postgres_password }}" diff --git a/roles/postgres/vars/main/defaults.yml b/roles/postgres/vars/main/defaults.yml new file mode 100644 index 0000000..19cbd7e --- /dev/null +++ b/roles/postgres/vars/main/defaults.yml @@ -0,0 +1,3 @@ +--- +postgres_unix_username: postgres +postgres_version: 17-alpine diff --git a/roles/postgres/vars/main/vault.yml b/roles/postgres/vars/main/vault.yml new file mode 100644 index 0000000..3079433 --- /dev/null +++ b/roles/postgres/vars/main/vault.yml @@ -0,0 +1,11 @@ +$ANSIBLE_VAULT;1.1;AES256 +33656630396365636165633936316636323163303463643436303933636263326666313933366662 +3437333064663362666632383137323839326431333966350a653633376662626134333730313430 +33646337396530616230313062313737343639666234353262356436636364336463643430303438 +6639346363663231360a386663363632316361613238666465626238666436303561653265666431 +33316538613366316663303666306263386433373838343061363865313833303037653330343631 +61653438333361323234666662373965636464346132613339623436343262316636346363643830 +61626238616561663139323530373933623938633666373637376636353134303638613165643866 +34343036653365303630643333326165623334353038653961313731336538633830363732616137 +35343762613738383536653833646263326638663034326638323639343365633863343238356264 +3239656338663861343337333866306636353764363433636437 diff --git a/roles/wedding/tasks/main.yml b/roles/wedding/tasks/main.yml index 4e27f5d..77152e3 100644 --- a/roles/wedding/tasks/main.yml +++ b/roles/wedding/tasks/main.yml @@ -25,14 +25,14 @@ podman_container_tag: "{{ wedding_version }}" podman_container_publish: - 127.0.0.1:3001:3000 - podman_container_volumes: [] + podman_simple_container_volumes: [] podman_container_env: - DATABASE_HOST: 'localhost' # TODO: Needs to be fixed later... + DATABASE_HOST: 'postgres.kleinendorst.info' DATABASE_PORT: 5432 DATABASE_DBNAME: wedding DATABASE_USER: "{{ postgres.user }}" DATABASE_PASSWORD: "{{ postgres.password }}" SESSION_SECRET: "{{ wedding_env.secret }}" - # NODE_ENV: production # TODO: Enable when ready for secure cookie testing... + NODE_ENV: production WEDDING_FULL_ACCESS_CODE: "{{ wedding_env.full_access_code }}" WEDDING_NIGHT_ACCESS_CODE: "{{ wedding_env.night_access_code }}" diff --git a/roles/wedding/vars/main/defaults.yml b/roles/wedding/vars/main/defaults.yml index b8f7287..f2fb48e 100644 --- a/roles/wedding/vars/main/defaults.yml +++ b/roles/wedding/vars/main/defaults.yml @@ -1,3 +1,3 @@ --- wedding_username: wedding -wedding_version: 0.0.3 +wedding_version: 0.0.5