From 0cc4c1460e50e90e108c9c29b99af95626057f3b Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Mon, 28 Sep 2026 19:48:39 -0400 Subject: [PATCH] feat(debyltech-cloud): limit customers to Files, Activity and signing Nothing was group-restricted, so customers saw Dashboard, Photos, Office and the rest, plus Nextcloud's first-run and promo apps. - Disable for everyone: firstrunwizard, recommendations, related_resources, weather_status, survey_client, support, app_api, contactsinteraction, photos. A refused disable now fails the play (occ exits 0 on "can't be disabled"). - Restrict dashboard and office to staff. defaultapp=dashboard,files so staff land on the dashboard and customers fall through to Files. - libresign groups_request_sign pinned to staff and asserted in verify. LibreSign itself is deliberately NOT group-restricted: that also blocks anonymous requests and would break public signing links. - profile.enabled=false; lookup_server="" (lookup_server_connector can't be disabled). - README: what customers can open. Checked as a probe customer: apps=files,activity,libresign,text,viewer, lands in Files, can't request signatures; staff land on Dashboard. Co-Authored-By: Claude Opus 5.5 --- ansible/roles/podman/README.md | 10 ++- .../tasks/containers/debyltech/cloud.yml | 80 +++++++++++++++++++ .../nextcloud/debyltechmail-verify.php.j2 | 8 ++ .../roles/podman/templates/podman-prune.sh.j2 | 9 +++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/ansible/roles/podman/README.md b/ansible/roles/podman/README.md index 78fd7c9..8ed469a 100644 --- a/ansible/roles/podman/README.md +++ b/ansible/roles/podman/README.md @@ -183,8 +183,16 @@ What a customer gets: - no view of other accounts or groups: the share search and contacts menu return nothing +What they can open: Files, Activity, and LibreSign for signing only. +Dashboard and Office are staff-only. Promotional or directory-style apps +(first-run wizard, recommendations, weather, Photos, contacts interaction, +lookup server, ...) are disabled for everyone. Only staff can *request* +signatures. + Signature requests to customers need no account: use LibreSign with their -email address. +email address. LibreSign stays enabled for all accounts on purpose. +Restricting an app to a group also blocks visitors who aren't logged in, +which would break the public signing links. ## Logging diff --git a/ansible/roles/podman/tasks/containers/debyltech/cloud.yml b/ansible/roles/podman/tasks/containers/debyltech/cloud.yml index 4c70eb5..c37d38c 100644 --- a/ansible/roles/podman/tasks/containers/debyltech/cloud.yml +++ b/ansible/roles/podman/tasks/containers/debyltech/cloud.yml @@ -530,6 +530,79 @@ changed_when: "'CHANGED' in debyltech_staff_quota.stdout" loop: "{{ cloud_debyltech_staff_users }}" +# What a customer can reach. Nextcloud had nothing group-restricted, so every +# customer saw Dashboard, Photos, Office and the rest in the app menu. +# +# Disabled outright -- promos, prompts, or directory-ish features a business +# file-and-signing portal has no use for (contactsinteraction silently adds +# whoever shares with you to your address book; app_api only produces a +# setup warning here). lookup_server_connector cannot be disabled (occ refuses) +# -- the empty `lookup_server` system value below switches it off instead. +# +# Restricted to staff: dashboard and office. `defaultapp` below lists +# dashboard first so staff land there and customers fall through to Files. +# +# NOT restricted: libresign. A group restriction is enforced for anonymous +# requests too (AppManager::checkAppForUser returns false for no user), so it +# would break the public signing links sent to outside signers and to any +# customer already logged in. Who may AUTHOR requests is LibreSign's own +# groups_request_sign, pinned to staff below. +- name: disable unneeded apps in debyltech-cloud + become: true + become_user: "{{ podman_user }}" + ansible.builtin.shell: | + set -o pipefail + occ() { podman exec -u www-data debyltech-cloud php occ "$@"; } + if occ app:list --output=json | python3 -c 'import json,sys; sys.exit(0 if sys.argv[1] in json.load(sys.stdin)["enabled"] else 1)' {{ item | quote }}; then + occ app:disable {{ item | quote }} + echo CHANGED + fi + args: + executable: /bin/bash + register: debyltech_app_disable + changed_when: "'CHANGED' in debyltech_app_disable.stdout" + # occ exits 0 even when it refuses ("can't be disabled"). + failed_when: debyltech_app_disable.rc != 0 or "can't be disabled" in debyltech_app_disable.stdout + loop: + - firstrunwizard + - recommendations + - related_resources + - weather_status + - survey_client + - support + - app_api + - contactsinteraction + - photos + +- name: restrict staff-only apps in debyltech-cloud + become: true + become_user: "{{ podman_user }}" + ansible.builtin.shell: | + set -o pipefail + occ() { podman exec -u www-data debyltech-cloud php occ "$@"; } + want={{ [cloud_debyltech_staff_group] | to_json | quote }} + if [ "$(occ config:app:get {{ item | quote }} enabled || true)" != "$want" ]; then + occ app:enable --groups {{ cloud_debyltech_staff_group | quote }} {{ item | quote }} >/dev/null + echo CHANGED + fi + args: + executable: /bin/bash + register: debyltech_app_restrict + changed_when: "'CHANGED' in debyltech_app_restrict.stdout" + loop: + - dashboard + - office + +- name: pin who may request libresign signatures in debyltech-cloud + become: true + become_user: "{{ podman_user }}" + ansible.builtin.command: > + podman exec -u www-data debyltech-cloud + php occ config:app:set libresign groups_request_sign + --value={{ [cloud_debyltech_staff_group] | to_json | quote }} + register: debyltech_request_sign + changed_when: "'is now set to' in debyltech_request_sign.stdout" + # --------------------------------------------------------------------------- # Branding: debyltechmail, cloned from skudakmail. custom_apps is a persisted # bind mount, so only enabling and config need reasserting. @@ -608,6 +681,13 @@ # than Nextcloud's sample Manual/intro video/Readme and Templates folder. - {k: skeletondirectory, v: ""} - {k: templatedirectory, v: ""} + # First ENABLED app wins: staff get the dashboard, and customers -- who + # cannot open it (restricted below) -- fall through to Files. + - {k: defaultapp, v: "dashboard,files"} + # No per-account profile pages. + - {k: profile.enabled, v: "false", t: boolean} + # Never query or publish to the global lookup server (lookup.nextcloud.com). + - {k: lookup_server, v: ""} - {k: loglevel, v: "2", t: integer} - {k: log_rotate_size, v: "10485760", t: integer} - {k: mail_template_class, v: "OCA\\Debyltechmail\\Mail\\DebyltechEMailTemplate"} diff --git a/ansible/roles/podman/templates/nextcloud/debyltechmail-verify.php.j2 b/ansible/roles/podman/templates/nextcloud/debyltechmail-verify.php.j2 index 2479455..639bfae 100644 --- a/ansible/roles/podman/templates/nextcloud/debyltechmail-verify.php.j2 +++ b/ansible/roles/podman/templates/nextcloud/debyltechmail-verify.php.j2 @@ -165,6 +165,14 @@ foreach ($isolation as $key => $want) { } } +// Only staff may AUTHOR signature requests (LibreSign cannot be group- +// restricted without breaking its public signing links). +$requesters = json_decode($appConfig->getValueString('libresign', 'groups_request_sign', ''), true); +if ($requesters !== ['{{ cloud_debyltech_staff_group }}']) { + $failures[] = 'libresign groups_request_sign is ' . json_encode($requesters) + . ' -- expected only the staff group, or customers could send signature requests'; +} + $identDocs = $appConfig->getValueString('libresign', 'identification_documents', ''); if ($identDocs !== '0') { $failures[] = 'libresign identification_documents is "' . $identDocs diff --git a/ansible/roles/podman/templates/podman-prune.sh.j2 b/ansible/roles/podman/templates/podman-prune.sh.j2 index f59431f..ea96cf8 100644 --- a/ansible/roles/podman/templates/podman-prune.sh.j2 +++ b/ansible/roles/podman/templates/podman-prune.sh.j2 @@ -85,6 +85,15 @@ for u in {{ podman_prune_ci_users | join(' ') }}; do # are rebuilt or re-pulled from the registry only when missing, so pruning # them just forces a multi-GB re-download on the next job. prune_user "$u" "{{ podman_prune_ci_until }}" yes --filter "label!={{ podman_prune_ci_keep_label }}" + + # ...but the label is inherited by every build of those images, including the + # one a rebuild supersedes. That copy loses its tag and becomes dangling, and + # the label filter above would keep it forever -- a multi-GB leak per weekly + # rebuild, ESP-IDF alone being several GB. Without -a, `image prune` removes + # only dangling images, so it can ignore the label without touching the live + # tagged ones. + dangling=$(run "$u" image prune -f --filter "until={{ podman_prune_ci_until }}" 2>&1 | tail -1) + log "user=$u dangling_prune=${dangling:-none}" done log "status=ok"