CORS/CSP hardening & centralization

- Add reusable Nginx include: roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2
  (dynamic ACAO/credentials/methods/headers via role vars)
- Set global 'Vary: Origin' in nginx.conf.j2 to prevent cache poisoning
- CSP: allow Simple Icons via connect-src when feature is enabled
- Front proxy: rename vars to lowercase + flush handlers after config deploy
- Desktop: gate & load Simple Icons role; inject brand logos when enabled
- Bluesky + Logout: replace inline CORS with centralized include
- Simpleicons: public CORS (ACAO='*', no credentials), keep GET/OPTIONS, allow headers
- Taiga: adjust canonical domain to taiga.kanban.{{ PRIMARY_DOMAIN }}
- LibreTranslate: remove unused images/versions keys

Fixes: https://open.project.infinito.nexus/projects/cymais/work_packages/342/activity
Discussion: https://chatgpt.com/share/68da5e27-ffd4-800f-91a3-0ef103058d44
This commit is contained in:
2025-09-29 12:23:58 +02:00
parent c06d1c4d17
commit aa19a97ed6
15 changed files with 89 additions and 48 deletions

View File

@@ -158,26 +158,31 @@ class FilterModule(object):
for directive in directives:
tokens = ["'self'"]
# 1) Load flags (includes defaults from get_csp_flags)
# Load flags (includes defaults from get_csp_flags)
flags = self.get_csp_flags(applications, application_id, directive)
tokens += flags
# 2) Allow fetching from internal CDN by default for selected directives
# Allow fetching from internal CDN by default for selected directives
if directive in ['script-src-elem', 'connect-src', 'style-src-elem']:
tokens.append(get_url(domains, 'web-svc-cdn', web_protocol))
# 3) Matomo integration if feature is enabled
# Matomo integration if feature is enabled
if directive in ['script-src-elem', 'connect-src']:
if self.is_feature_enabled(applications, matomo_feature_name, application_id):
tokens.append(get_url(domains, 'web-app-matomo', web_protocol))
# 4) ReCaptcha integration (scripts + frames) if feature is enabled
# Simpleicons integration if feature is enabled
if directive in ['connect-src']:
if self.is_feature_enabled(applications, 'simpleicons', application_id):
tokens.append(get_url(domains, 'web-svc-simpleicons', web_protocol))
# ReCaptcha integration (scripts + frames) if feature is enabled
if self.is_feature_enabled(applications, 'recaptcha', application_id):
if directive in ['script-src-elem', 'frame-src']:
tokens.append('https://www.gstatic.com')
tokens.append('https://www.google.com')
# 5) Frame ancestors handling (desktop + logout support)
# Frame ancestors handling (desktop + logout support)
if directive == 'frame-ancestors':
if self.is_feature_enabled(applications, 'desktop', application_id):
# Allow being embedded by the desktop app domain (and potentially its parent)
@@ -189,10 +194,10 @@ class FilterModule(object):
tokens.append(get_url(domains, 'web-svc-logout', web_protocol))
tokens.append(get_url(domains, 'web-app-keycloak', web_protocol))
# 6) Custom whitelist entries
# Custom whitelist entries
tokens += self.get_csp_whitelist(applications, application_id, directive)
# 7) Add inline content hashes ONLY if final tokens do NOT include 'unsafe-inline'
# Add inline content hashes ONLY if final tokens do NOT include 'unsafe-inline'
# (Check tokens, not flags, to include defaults and later modifications.)
if "'unsafe-inline'" not in tokens:
for snippet in self.get_csp_inline_content(applications, application_id, directive):
@@ -201,7 +206,7 @@ class FilterModule(object):
# Append directive
parts.append(f"{directive} {' '.join(tokens)};")
# 8) Static img-src directive (kept permissive for data/blob and any host)
# Static img-src directive (kept permissive for data/blob and any host)
parts.append("img-src * data: blob:;")
return ' '.join(parts)