Solved certreap bugs, implemented caching for pictures, optimized CSP policies (stricter), optimized recaptcha implementation for keycloak, solved mariadb wait bug, solved nextcloud plugin bugs, optimized ignore handling of tasks

This commit is contained in:
Kevin Veen-Birkenbach 2025-05-08 09:51:38 +02:00
parent f71c9e4b31
commit d5f194b2c0
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
19 changed files with 162 additions and 64 deletions

View File

@ -14,5 +14,6 @@ nginx:
html: "/var/www/public_html/" # Path where the static homepage files are stored html: "/var/www/public_html/" # Path where the static homepage files are stored
files: "/var/www/public_files/" # Path where the web accessable files are stored files: "/var/www/public_files/" # Path where the web accessable files are stored
global: "/var/www/global/" # Directory containing files which will be globaly accessable global: "/var/www/global/" # Directory containing files which will be globaly accessable
cache: "/tmp/nginx_cache/" # Directory which nginx uses to cache data
user: "http" # Default nginx user in ArchLinux user: "http" # Default nginx user in ArchLinux
iframe: true # Allows applications to be loaded in iframe iframe: true # Allows applications to be loaded in iframe

View File

@ -24,5 +24,4 @@ galaxy_info:
documentation: "https://github.com/kevinveenbirkenbach/certreap#readme" documentation: "https://github.com/kevinveenbirkenbach/certreap#readme"
dependencies: dependencies:
- systemd-timer
- systemd-notifier - systemd-notifier

View File

@ -2,7 +2,7 @@
include_role: include_role:
name: pkgmgr-install name: pkgmgr-install
vars: vars:
package_name: cleanup-certs package_name: certreap
when: run_once_cleanup_certs is not defined when: run_once_cleanup_certs is not defined
- name: configure cleanup-certs.cymais.service - name: configure cleanup-certs.cymais.service

View File

@ -4,4 +4,4 @@ OnFailure=systemd-notifier.cymais@%n.service
[Service] [Service]
Type=oneshot Type=oneshot
ExecStartPre=/bin/sh -c '/usr/bin/python certreap --force' ExecStart=/bin/sh -c 'certreap --force'

View File

@ -1,4 +1,4 @@
# nginx-domains-cleanup # cleanup-domains
## Description ## Description

View File

@ -2666,6 +2666,17 @@
"autheticatorFlow": false, "autheticatorFlow": false,
"userSetupAllowed": false "userSetupAllowed": false
}, },
{%- if applications | is_feature_enabled('recaptcha', application_id) %}
{
"authenticatorConfig": "Google reCaptcha",
"authenticator": "registration-recaptcha-action",
"authenticatorFlow": false,
"requirement": "REQUIRED",
"priority": 60,
"autheticatorFlow": false,
"userSetupAllowed": false
},
{%- else %}
{ {
"authenticator": "registration-recaptcha-action", "authenticator": "registration-recaptcha-action",
"authenticatorFlow": false, "authenticatorFlow": false,
@ -2674,6 +2685,7 @@
"autheticatorFlow": false, "autheticatorFlow": false,
"userSetupAllowed": false "userSetupAllowed": false
}, },
{%- endif %}
{ {
"authenticator": "registration-terms-and-conditions", "authenticator": "registration-terms-and-conditions",
"authenticatorFlow": false, "authenticatorFlow": false,
@ -2746,6 +2758,19 @@
} }
], ],
"authenticatorConfig": [ "authenticatorConfig": [
{%- if applications | is_feature_enabled('recaptcha',application_id) %}
{
"id": "c6dcf381-7e39-4f7f-8d1f-631faec31b56",
"alias": "Google reCaptcha",
"config": {
"action": "register",
"useRecaptchaNet": "false",
"recaptcha.v3": "true",
"secret.key": "{{ applications[application_id].credentials.recaptcha.secret_key }}",
"site.key": "{{ applications[application_id].credentials.recaptcha.website_key }}"
}
},
{%- endif %}
{ {
"id": "3e40f95e-d9a7-405d-b393-398bfc54c2e8", "id": "3e40f95e-d9a7-405d-b393-398bfc54c2e8",
"alias": "create unique user config", "alias": "create unique user config",

View File

@ -36,10 +36,11 @@
state: present state: present
when: run_once_docker_mariadb is not defined when: run_once_docker_mariadb is not defined
- name: Wait for MariaDB inside the container to respond - name: Wait until the MariaDB container is healthy
shell: docker exec central-mariadb mysqladmin ping -h localhost --silent community.docker.docker_container_info:
register: mysql_ping name: central-mariadb
until: mysql_ping.rc == 0 register: db_info
until: db_info.containers[0].State.Health.Status == "healthy"
retries: 30 retries: 30
delay: 5 delay: 5
when: when:

View File

@ -16,46 +16,59 @@
when: not (plugin_value.enabled | bool) when: not (plugin_value.enabled | bool)
- name: install {{ plugin_key }} nextcloud plugin - name: install {{ plugin_key }} nextcloud plugin
command: "{{nextcloud_docker_exec_occ}} app:install {{ plugin_key }}" command: "{{ nextcloud_docker_exec_occ }} app:install {{ plugin_key }}"
register: install_result register: install_result
failed_when: install_result.rc != 0 and ("already installed" not in install_result.stdout) failed_when: >
changed_when: install_result.rc == 0 and ("already installed" not in install_result.stdout) install_result.rc != 0
and
("already installed" not in install_result.stdout)
and
("not compatible with this version of the server" not in install_result.stdout)
changed_when: >
install_result.rc == 0
and
("already installed" not in install_result.stdout)
when: plugin_value.enabled | bool when: plugin_value.enabled | bool
- name: enable {{plugin_key}} nextcloud plugin - block:
command: "{{nextcloud_docker_exec_occ}} app:enable {{plugin_key}}" - name: enable {{plugin_key}} nextcloud plugin
register: enable_result command: "{{nextcloud_docker_exec_occ}} app:enable {{plugin_key}}"
changed_when: enable_result.rc == 0 and ("already enabled" not in enable_result.stdout) register: enable_result
when: plugin_value.enabled | bool changed_when: enable_result.rc == 0 and ("already enabled" not in enable_result.stdout)
- name: Check if {{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml exists - name: Check if {{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml exists
stat: stat:
path: "{{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml" path: "{{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml"
delegate_to: localhost delegate_to: localhost
become: false become: false
register: plugin_vars_file register: plugin_vars_file
- name: "Load {{ plugin_key }} configuration variables"
include_vars:
file: "{{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml"
when: plugin_vars_file.stat.exists
- name: "Load {{ plugin_key }} configuration variables" - name: "Set {{ item.configkey }} for {{ item.appid }}"
include_vars: loop: "{{ plugin_configuration }}"
file: "{{nextcloud_control_node_plugin_vars_directory}}{{ plugin_key }}.yml" command: >
when: plugin_vars_file.stat.exists {{ nextcloud_docker_exec_occ }} config:app:set {{ item.appid }} {{ item.configkey }} --value '{{ item.configvalue | to_json if item.configvalue is mapping else item.configvalue }}'
register: config_set_result
changed_when: (config_set_result.stdout is defined) and ("Config value were not updated" not in config_set_result.stdout)
when: plugin_vars_file.stat.exists
- name: "Set {{ item.configkey }} for {{ item.appid }}" - name: Check if {{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml exists
loop: "{{ plugin_configuration }}" stat:
command: > path: "{{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml"
{{ nextcloud_docker_exec_occ }} config:app:set {{ item.appid }} {{ item.configkey }} --value '{{ item.configvalue | to_json if item.configvalue is mapping else item.configvalue }}' delegate_to: localhost
register: config_set_result become: false
changed_when: (config_set_result.stdout is defined) and ("Config value were not updated" not in config_set_result.stdout) register: plugin_tasks_file
when: plugin_vars_file.stat.exists
- name: Check if {{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml exists - name: "include {{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml"
stat: include_tasks: "{{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml"
path: "{{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml" when: plugin_tasks_file.stat.exists
delegate_to: localhost when:
become: false - plugin_value.enabled | bool
register: plugin_tasks_file - install_result is defined
- >
- name: "include {{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml" install_result.rc == 0
include_tasks: "{{nextcloud_control_node_plugin_tasks_directory}}{{ plugin_key }}.yml" or "already installed" in install_result.stdout
when: plugin_tasks_file.stat.exists

View File

@ -0,0 +1,23 @@
- name: Remove (Cleanup) NGINX cache directory contents
become: true
file:
path: "{{ nginx.directories.cache }}"
state: absent
when:
- mode_cleanup | bool
- run_once_nginx_reverse_proxy is not defined
- name: Ensure NGINX cache directory exists
become: true
file:
path: "{{ nginx.directories.cache }}"
state: directory
owner: http
group: http
mode: '0755'
when: run_once_nginx_reverse_proxy is not defined
- name: run the nginx_reverse_proxy tasks once
set_fact:
run_once_nginx_reverse_proxy: true
when: run_once_nginx_reverse_proxy is not defined

View File

@ -3,6 +3,13 @@
{# default-src: Fallback for all other directives if not explicitly defined #} {# default-src: Fallback for all other directives if not explicitly defined #}
{%- set csp_parts = csp_parts + ["default-src 'self';"] %} {%- set csp_parts = csp_parts + ["default-src 'self';"] %}
{# connect-src: Controls where fetch(), XHR, WebSocket etc. can connect to #}
{%- set connect_src = "connect-src 'self' https://ka-f.fontawesome.com" %}
{%- if applications | is_feature_enabled('matomo', application_id) | bool %}
{%- set connect_src = connect_src + " " + web_protocol + "://" + domains.matomo %}
{%- endif %}
{%- set csp_parts = csp_parts + [connect_src + ";"] %}
{# frame-ancestors: Restricts which origins can embed this site in a frame or iframe #} {# frame-ancestors: Restricts which origins can embed this site in a frame or iframe #}
{%- set frame_ancestors = "frame-ancestors 'self'" %} {%- set frame_ancestors = "frame-ancestors 'self'" %}
{%- if applications | is_feature_enabled('iframe', application_id) | bool %} {%- if applications | is_feature_enabled('iframe', application_id) | bool %}
@ -13,21 +20,22 @@
{# frame-src: Controls which URLs can be embedded as iframes #} {# frame-src: Controls which URLs can be embedded as iframes #}
{%- set frame_src = "frame-src 'self'" %} {%- set frame_src = "frame-src 'self'" %}
{%- if applications | is_feature_enabled('recaptcha', application_id) | bool %} {%- if applications | is_feature_enabled('recaptcha', application_id) | bool %}
{%- set frame_src = frame_src + " https://www.google.com https://www.recaptcha.net" %} {%- set frame_src = frame_src + " https://www.google.com" %}
{%- endif %} {%- endif %}
{%- set csp_parts = csp_parts + [frame_src + ";"] %} {%- set csp_parts = csp_parts + [frame_src + ";"] %}
{# img-src: Allow images from own domain and files deliverer. Also from Matomo if enabled. #} {# img-src: Allow images. Prevent tracking by caching on server and client side. #}
{%- set img_src = "img-src 'self' " + web_protocol + "://" + domains.file_server %} {%- set img_src = "img-src * data: blob:"%}
{%- if applications | is_feature_enabled('matomo', application_id) | bool %}
{%- set img_src = img_src + " " + web_protocol + "://" + domains.matomo %}
{%- endif %}
{%- set csp_parts = csp_parts + [img_src + ";"] %} {%- set csp_parts = csp_parts + [img_src + ";"] %}
{# script-src: Allow JavaScript from self, FontAwesome, jsDelivr, and Matomo if enabled #} {# script-src: Allow JavaScript from self, FontAwesome, jsDelivr, and Matomo if enabled #}
{%- set script_src = "script-src 'self' 'unsafe-inline'" %} {# unsafe eval is set for sphinx #}
{%- set script_src = "script-src 'self' 'unsafe-eval' 'unsafe-inline'" %}
{%- if applications | is_feature_enabled('matomo', application_id) | bool %} {%- if applications | is_feature_enabled('matomo', application_id) | bool %}
{%- set script_src = script_src + " " + domains.matomo %} {%- set script_src = script_src + " " + web_protocol + "://" + domains.matomo %}
{%- endif %}
{%- if applications | is_feature_enabled('recaptcha', application_id) | bool %}
{%- set script_src = script_src + " https://www.google.com" %}
{%- endif %} {%- endif %}
{%- set script_src = script_src + " https://kit.fontawesome.com https://cdn.jsdelivr.net" %} {%- set script_src = script_src + " https://kit.fontawesome.com https://cdn.jsdelivr.net" %}
{%- set csp_parts = csp_parts + [script_src + ";"] %} {%- set csp_parts = csp_parts + [script_src + ";"] %}
@ -36,4 +44,10 @@
{%- set style_src = "style-src 'self' 'unsafe-inline' https://kit.fontawesome.com https://cdn.jsdelivr.net" %} {%- set style_src = "style-src 'self' 'unsafe-inline' https://kit.fontawesome.com https://cdn.jsdelivr.net" %}
{%- set csp_parts = csp_parts + [style_src + ";"] %} {%- set csp_parts = csp_parts + [style_src + ";"] %}
{# font-src: Allow font-src from self, FontAwesome, jsDelivr and inline styles #}
{%- set font_src = "font-src 'self' https://kit.fontawesome.com https://cdn.jsdelivr.net" %}
{%- set csp_parts = csp_parts + [font_src + ";"] %}
add_header Content-Security-Policy "{{ csp_parts | join(' ') }}" always; add_header Content-Security-Policy "{{ csp_parts | join(' ') }}" always;
# Oppress header send by proxied application
proxy_hide_header Content-Security-Policy;

View File

@ -31,3 +31,6 @@ location {{location | default("/")}}
proxy_read_timeout 900s; proxy_read_timeout 900s;
send_timeout 900s; send_timeout 900s;
} }
# Load caching
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_cache.conf.j2' %}

View File

@ -0,0 +1,18 @@
proxy_cache_path {{ nginx.directories.cache }} levels=1:2 keys_zone=imgcache:10m inactive=60m use_temp_path=off;
{%- if location is defined %}
location ~* ^{{ location }}.*\.(jpg|jpeg|png|gif|webp|ico|svg)$ {
{%- else %}
location ~* \.(jpg|jpeg|png|gif|webp|ico|svg)$ {
{%- endif %}
# Cache in browser
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
# Cache on reverse proxy side
proxy_pass http://127.0.0.1:{{http_port}}{{location | default("/")}};
proxy_cache imgcache;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
add_header X-Proxy-Cache $upstream_cache_status;
}

View File

@ -1,4 +1,4 @@
dependencies: dependencies:
- nginx - nginx
- nginx-domains-cleanup - cleanup-domains
- letsencrypt - letsencrypt

View File

@ -21,9 +21,9 @@
- name: "Check if yay is installed" - name: "Check if yay is installed"
command: which yay command: which yay
ignore_errors: yes
register: yay_installed register: yay_installed
changed_when: false changed_when: false
failed_when: false
- name: "Update with yay" - name: "Update with yay"
include_role: include_role:
@ -32,9 +32,9 @@
- name: "Check if pip is installed" - name: "Check if pip is installed"
command: which pip command: which pip
ignore_errors: yes
register: pip_installed register: pip_installed
changed_when: false changed_when: false
failed_when: false
- name: "Update with pip" - name: "Update with pip"
include_role: include_role:
@ -43,7 +43,7 @@
- name: "Check if pkgmgr command is available" - name: "Check if pkgmgr command is available"
command: "which pkgmgr" command: "which pkgmgr"
register: pkgmgr_available register: pkgmgr_available
ignore_errors: yes failed_when: false
- name: "Update all repositories using pkgmgr" - name: "Update all repositories using pkgmgr"
include_role: include_role:

View File

@ -1,13 +1,14 @@
{% macro render_features(options) %} {% macro render_features(options) %}
features: features:
{%- set feature_map = { {%- set feature_map = {
'matomo': 'Enables Matomo tracking', 'matomo': 'Enables Matomo tracking',
'css': 'Enables custom CSS styling', 'css': 'Enables custom CSS styling',
'iframe': 'Allows embedding via iframe on landing page', 'iframe': 'Allows embedding via iframe on landing page',
'ldap': 'Enables LDAP integration and networking', 'ldap': 'Enables LDAP integration and networking',
'oidc': 'Enables OpenID Connect (OIDC) authentication', 'oidc': 'Enables OpenID Connect (OIDC) authentication',
'oauth2': 'Enables OAuth2 proxy integration', 'oauth2': 'Enables OAuth2 proxy integration',
'database': 'Enables use of central database' 'database': 'Enables use of central database',
'recaptcha': 'Enables recaptcha functionality'
} %} } %}
{%- for key, comment in feature_map.items() %} {%- for key, comment in feature_map.items() %}
{%- if key in options %} {%- if key in options %}