From a55246263b0779657e1b13de2fe861c748bbd5ce Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:46:42 +0200 Subject: [PATCH 1/7] implement "enabled" apps --- roles/ppm/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/ppm/tasks/main.yml b/roles/ppm/tasks/main.yml index 8a52276..e0bd451 100644 --- a/roles/ppm/tasks/main.yml +++ b/roles/ppm/tasks/main.yml @@ -7,7 +7,7 @@ loop_control: loop_var: ppm_app label: "{{ ppm_app.user }}" - when: ppm_app.on_server == inventory_hostname + when: ppm_app.on_server == inventory_hostname and ppm_app.enabled | default(true) - name: Arrange firewall ansible.builtin.import_tasks: firewall.yml From b021bb2620b17098c5459d543a511feed038d1e0 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:47:51 +0200 Subject: [PATCH 2/7] allow defining the home directory The idea here is that we can have a backup server that should run on a slow huge disk, and that the rest should run on your very fast nvme's. To differentiate, you can specify where the homedir should be --- roles/ppm/tasks/oneapp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/ppm/tasks/oneapp.yml b/roles/ppm/tasks/oneapp.yml index 976e9eb..bb64216 100644 --- a/roles/ppm/tasks/oneapp.yml +++ b/roles/ppm/tasks/oneapp.yml @@ -2,6 +2,7 @@ ansible.builtin.user: name: "{{ ppm_app.user }}" shell: /bin/bash + home: "{{ ppm_app.homedir | default(omit) }}" register: ppm_app_user # Enabling linger will make systemd start the user-systemd for this user at bootup time From 5fda82d0d36efaf19a6a7e5aba3338d80d88c08b Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:48:14 +0200 Subject: [PATCH 3/7] when the setup changed something, show output in changed color --- roles/ppm/tasks/oneapp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/ppm/tasks/oneapp.yml b/roles/ppm/tasks/oneapp.yml index bb64216..fe89ffe 100644 --- a/roles/ppm/tasks/oneapp.yml +++ b/roles/ppm/tasks/oneapp.yml @@ -57,3 +57,5 @@ - name: Show ppm output ansible.builtin.debug: var: ppm_setupstart + # Also mark it when the actual command changed, so it is easy to find... + changed_when: "'No changes have been made, everything was already ok' not in ppm_setupstart.stdout" From 0a32383de311d8986869cd914a750968a188e6b7 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:48:55 +0200 Subject: [PATCH 4/7] add ssh key option for apps This way we can push ssh keys that can pull/push to repositories. This is very handy if we wish to edit/maintain the appinfo repository right on the server where it is hosted.... --- roles/ppm/tasks/oneapp.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/roles/ppm/tasks/oneapp.yml b/roles/ppm/tasks/oneapp.yml index fe89ffe..ccfa54c 100644 --- a/roles/ppm/tasks/oneapp.yml +++ b/roles/ppm/tasks/oneapp.yml @@ -19,6 +19,23 @@ line: "export XDG_RUNTIME_DIR=/run/user/$(id -u)" regexp: ^export XDG_RUNTIME_DIR= +- name: "Ensure ssh configuration directory for user {{ ppm_app.user }}" + ansible.builtin.file: + state: directory + mode: "0700" + path: "{{ ppm_app_user.home }}/.ssh" + owner: "{{ ppm_app_user.name }}" + group: "{{ ppm_app_user.group }}" + +- name: "Place ssh key for user {{ ppm_app.user }}" + ansible.builtin.copy: + src: "{{ ppm_app.sshkey }}" + mode: "0600" + dest: "{{ ppm_app_user.home }}/.ssh/id_rsa" + owner: "{{ ppm_app_user.name }}" + group: "{{ ppm_app_user.group }}" + when: ppm_app.sshkey is defined + - name: "Place configuration ({{ ppm_app.user }})" ansible.builtin.copy: content: "{{ ppm_app.appconfig | dict2items | selectattr('key', 'ne', 'code') | items2dict | to_nice_yaml }}" From dc99b61ea86a785fb5fe77c9d1478bf58edf4fa6 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:49:10 +0200 Subject: [PATCH 5/7] the zabbix scripts I use use netcat --- roles/ppm/tasks/ppminstall.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/ppm/tasks/ppminstall.yml b/roles/ppm/tasks/ppminstall.yml index 022f3e8..69e4ba2 100644 --- a/roles/ppm/tasks/ppminstall.yml +++ b/roles/ppm/tasks/ppminstall.yml @@ -21,6 +21,8 @@ - git # Yeah we should use nftables, patches welcome. For now, we install iptables - iptables + # This is used in our zabbix scripts + - netcat-openbsd - name: Create state directory ansible.builtin.file: From 4d8c4e5f486520fb3989790c7dd24c787ce3cf2b Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:52:03 +0200 Subject: [PATCH 6/7] Cleanup firewall + make it more strict - Do not put too many empty newlines in rendered file - Also write redirect rules in the output chain. This will allow us to use the port from the same machine. Note that redirect does rewrite the destination to localhost if used on the same machine, so your application should also listen to localhost - made the file a bit clearer with better comments - Add the explicit device if we can for redirects --- roles/ppm/files/ppmfirewall | 49 ++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/roles/ppm/files/ppmfirewall b/roles/ppm/files/ppmfirewall index 1f63bd8..a91cf0a 100644 --- a/roles/ppm/files/ppmfirewall +++ b/roles/ppm/files/ppmfirewall @@ -3,20 +3,45 @@ # PPM Firewall {% for app in otherapps -%} +# App {{ app }} {%- if "firewall" in otherapps[app]["imports"] -%} {%- set oneapp = otherapps[app]["imports"]["firewall"] %} - -{% for redirect in oneapp.redirect %} +{%- for redirect in oneapp.redirect %} +{%- if redirect.version == "ipv4" %} +{%- if redirect.ip is defined %} +# Redirect {{ redirect.ip }}:{{ redirect.from }} to {{ redirect.to }} ({{ redirect.proto | default('tcp') }}) for {{ app }} +inputinterface=$(ip -o -4 addr show | awk '$3 == "inet" && index($4,"{{ redirect.ip }}/")==1 {print "-i " $2}') +iptables -A INPUT -d {{ redirect.ip }} $inputinterface -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT +iptables -t nat -A PREROUTING $inputinterface -d {{ redirect.ip }} -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +iptables -t nat -A OUTPUT -d {{ redirect.ip }} -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +{%- else %} # Redirect {{ redirect.from }} to {{ redirect.to }} ({{ redirect.proto | default('tcp') }}) for {{ app }} -iptables -A INPUT -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT -ip6tables -A INPUT -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT -iptables -t nat -A PREROUTING -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} -ip6tables -t nat -A PREROUTING -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} -{% endfor %} -{% for openport in oneapp.open %} +iptables -A INPUT -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT +iptables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +iptables -t nat -A OUTPUT -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +{%- endif %} +{%- else %}{# ipv6 #} +{%- if redirect.ip is defined %} +# Redirect {{ redirect.ip }}:{{ redirect.from }} to {{ redirect.to }} ({{ redirect.proto | default('tcp') }}) for {{ app }} +inputinterface=$(ip -o -6 addr show | awk '$3 == "inet6" && index($4,"{{ redirect.ip }}/")==1 {print "-i " $2}') +ip6tables -A INPUT -d {{ redirect.ip }} $inputinterface -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT +ip6tables -t nat -A PREROUTING -d {{ redirect.ip }} $inputinterface -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +ip6tables -t nat -A OUTPUT -d {{ redirect.ip }} -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +{%- else %} +# Redirect {{ redirect.from }} to {{ redirect.to }} ({{ redirect.proto | default('tcp') }}) for {{ app }} +ip6tables -A INPUT -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.to }} -j ACCEPT +ip6tables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +ip6tables -t nat -A OUTPUT -m addrtype --dst-type LOCAL -p {{ redirect.proto | default('tcp') }} --dport {{ redirect.from }} -j REDIRECT --to-ports {{ redirect.to }} +{%- endif %} +{%- endif %} +{%- endfor %} +{%- for openport in oneapp.open %} # Open port {{ openport.port }}/{{ openport.proto | default('tcp') }} for app {{ app }} -iptables -A INPUT -p {{ openport.proto | default('tcp') }} --dport {{ openport.port }} -j ACCEPT -ip6tables -A INPUT -p {{ openport.proto | default('tcp') }} --dport {{ openport.port }} -j ACCEPT -{% endfor %} -{% endif %} +{%- if redirect.version == "ipv4" %} +iptables -A INPUT {% if firewall_bindservices_ipv4 is defined %}-s {{firewall_bindservices_ipv4 }}{% endif %} -p {{ openport.proto | default('tcp') }} --dport {{ openport.port }} -j ACCEPT +{%- else %} +ip6tables -A INPUT {% if firewall_bindservices_ipv6 is defined %}-s {{firewall_bindservices_ipv6 }}{% endif %} -p {{ openport.proto | default('tcp') }} --dport {{ openport.port }} -j ACCEPT +{%- endif %} +{%- endfor %} +{%- endif %} {% endfor %} From c34b3c1d0f3f911b35fbe4adbe3c81a7942ed7a2 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 15 Aug 2025 22:58:22 +0200 Subject: [PATCH 7/7] add passt as dependency --- roles/ppm/tasks/ppminstall.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/ppm/tasks/ppminstall.yml b/roles/ppm/tasks/ppminstall.yml index 69e4ba2..3ece19d 100644 --- a/roles/ppm/tasks/ppminstall.yml +++ b/roles/ppm/tasks/ppminstall.yml @@ -10,6 +10,8 @@ - netavark # Required for rootless networking - slirp4netns + # Required for rootless networking - for trixie and above + - passt # podman-compose is also used in many apps we can install - podman-compose # Restic is currently the only supported backup system