mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-10-09 18:28:10 +02:00
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:
@@ -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)
|
||||
|
@@ -6,10 +6,10 @@
|
||||
include_role:
|
||||
name: sys-util-csp-cert
|
||||
|
||||
- name: "Copy nginx config to '{{ FRONT_PROXY_DOMAIN_CONF_DST }}'"
|
||||
- name: "Copy nginx config to '{{ front_proxy_domain_conf_dst }}'"
|
||||
template:
|
||||
src: "{{ FRONT_PROXY_DOMAIN_CONF_SRC }}"
|
||||
dest: "{{ FRONT_PROXY_DOMAIN_CONF_DST }}"
|
||||
src: "{{ front_proxy_domain_conf_src }}"
|
||||
dest: "{{ front_proxy_domain_conf_dst }}"
|
||||
register: nginx_conf
|
||||
notify: restart openresty
|
||||
|
||||
@@ -29,3 +29,6 @@
|
||||
- site_check.status is defined
|
||||
- not site_check.status in [200,301,302]
|
||||
when: not nginx_conf.changed
|
||||
|
||||
- name: "Restart Webserver for '{{ front_proxy_domain_conf_dst }}'"
|
||||
meta: flush_handlers
|
@@ -1,2 +1,2 @@
|
||||
FRONT_PROXY_DOMAIN_CONF_DST: "{{ [ NGINX.DIRECTORIES.HTTP.SERVERS, domain ~ '.conf'] | path_join }}"
|
||||
FRONT_PROXY_DOMAIN_CONF_SRC: "roles/sys-svc-proxy/templates/vhost/{{ vhost_flavour }}.conf.j2"
|
||||
front_proxy_domain_conf_dst: "{{ [ NGINX.DIRECTORIES.HTTP.SERVERS, domain ~ '.conf'] | path_join }}"
|
||||
front_proxy_domain_conf_src: "roles/sys-svc-proxy/templates/vhost/{{ vhost_flavour }}.conf.j2"
|
||||
|
@@ -0,0 +1,25 @@
|
||||
# Configure CORS headers dynamically based on role variables.
|
||||
# If no variable is defined, defaults are applied (e.g. same-origin).
|
||||
# Discussion: https://chat.openai.com/share/2671b961-c1b0-472d-bae2-2804d0455e8a
|
||||
|
||||
{# Access-Control-Allow-Origin #}
|
||||
{% if aca_origin is defined %}
|
||||
add_header 'Access-Control-Allow-Origin' {{ aca_origin }};
|
||||
{% else %}
|
||||
add_header 'Access-Control-Allow-Origin' $scheme://$host always;
|
||||
{% endif %}
|
||||
|
||||
{# Access-Control-Allow-Credentials #}
|
||||
{% if aca_credentials is defined %}
|
||||
add_header 'Access-Control-Allow-Credentials' {{ aca_credentials }};
|
||||
{% endif %}
|
||||
|
||||
{# Access-Control-Allow-Methods #}
|
||||
{% if aca_methods is defined %}
|
||||
add_header 'Access-Control-Allow-Methods' {{ aca_methods }};
|
||||
{% endif %}
|
||||
|
||||
{# Access-Control-Allow-Headers #}
|
||||
{% if aca_headers is defined %}
|
||||
add_header 'Access-Control-Allow-Headers' {{ aca_headers }};
|
||||
{% endif %}
|
@@ -58,5 +58,3 @@ server
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -13,6 +13,11 @@ http
|
||||
|
||||
default_type text/html;
|
||||
|
||||
{# Ensure caches (browsers, proxies, CDNs) treat responses as dependent on the Origin header
|
||||
to prevent cross-domain cache poisoning issues.
|
||||
Discussion: https://chat.openai.com/share/2671b961-c1b0-472d-bae2-2804d0455e8a #}
|
||||
add_header 'Vary' 'Origin' always;
|
||||
|
||||
{# caching #}
|
||||
proxy_cache_path {{ NGINX.DIRECTORIES.CACHE.GENERAL }} levels=1:2 keys_zone=cache:20m max_size=20g inactive=14d use_temp_path=off;
|
||||
proxy_cache_path {{ NGINX.DIRECTORIES.CACHE.IMAGE }} levels=1:2 keys_zone=imgcache:10m inactive=60m use_temp_path=off;
|
||||
|
@@ -2,17 +2,17 @@
|
||||
# Exposes a same-origin /config to avoid CORS when the social-app fetches config.
|
||||
location = /config {
|
||||
proxy_pass {{ BLUESKY_CONFIG_UPSTREAM_URL }};
|
||||
# Nur Hostname extrahieren:
|
||||
|
||||
{# Just extract hostname #}
|
||||
set $up_host "{{ BLUESKY_CONFIG_UPSTREAM_URL | regex_replace('^https?://', '') | regex_replace('/.*$', '') }}";
|
||||
proxy_set_header Host $up_host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
# Make response clearly same-origin for browsers
|
||||
{# Access Control Allow Configurations #}
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Origin $scheme://$host always;
|
||||
add_header Vary Origin always;
|
||||
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %}
|
||||
}
|
||||
|
||||
location = /ipcc {
|
||||
@@ -23,7 +23,7 @@ location = /ipcc {
|
||||
proxy_set_header Connection "";
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
{# Access Control Allow Configurations #}
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Origin $scheme://$host always;
|
||||
add_header Vary Origin always;
|
||||
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %}
|
||||
}
|
||||
|
@@ -1,3 +1,12 @@
|
||||
- name: "Load brand logos role for '{{ application_id }}'"
|
||||
include_role:
|
||||
name: web-svc-simpleicons
|
||||
vars:
|
||||
flush_handlers: true
|
||||
when:
|
||||
- run_once_web_svc_simpleicons is not defined
|
||||
- DESKTOP_SIMPLEICONS_ENABLED | bool
|
||||
|
||||
- name: "Validate configuration"
|
||||
include_tasks: "02_validate.yml"
|
||||
when: MODE_ASSERT | bool
|
||||
@@ -24,25 +33,16 @@
|
||||
set_fact:
|
||||
portfolio_cards: "{{ lookup('docker_cards', 'roles') }}"
|
||||
|
||||
- name: "Load images for applications feature simpleicons is enabled "
|
||||
- name: "Load Desktop Brand logos"
|
||||
set_fact:
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(domains, WEB_PROTOCOL) }}"
|
||||
when:
|
||||
- (applications | get_app_conf(application_id, 'features.simpleicons', False))
|
||||
when: DESKTOP_SIMPLEICONS_ENABLED | bool
|
||||
changed_when: false
|
||||
|
||||
- name: Group docker cards
|
||||
set_fact:
|
||||
portfolio_menu_data: "{{ lookup('docker_cards_grouped', portfolio_cards, portfolio_menu_categories) }}"
|
||||
|
||||
- name: Debug portfolio data
|
||||
debug:
|
||||
msg:
|
||||
portfolio_cards: "{{ portfolio_cards }}"
|
||||
portfolio_menu_categories: "{{ portfolio_menu_categories}}"
|
||||
portfolio_menu_data: "{{ portfolio_menu_data }}"
|
||||
service_provider: "{{ service_provider }}"
|
||||
when: MODE_DEBUG | bool
|
||||
|
||||
- name: Copy host-specific config.yaml if it exists
|
||||
template:
|
||||
src: "{{ DESKTOP_CONFIG_INV_PATH }}"
|
||||
|
@@ -10,6 +10,9 @@ docker_pull_git_repository: true
|
||||
|
||||
# Desktop
|
||||
|
||||
## Simpleicons
|
||||
DESKTOP_SIMPLEICONS_ENABLED: "{{ applications | get_app_conf(application_id, 'features.simpleicons') }}"
|
||||
|
||||
## Javascript
|
||||
DESKTOP_JS_CDN_URL: "{{ domains | get_url('web-svc-cdn', WEB_PROTOCOL) }}"
|
||||
DESKTOP_JS_FILES: ['iframe.js','oidc.js']
|
||||
|
@@ -31,4 +31,4 @@ server:
|
||||
unsafe-eval: true
|
||||
domains:
|
||||
canonical:
|
||||
- "kanban.project.{{ PRIMARY_DOMAIN }}"
|
||||
- "taiga.kanban.{{ PRIMARY_DOMAIN }}"
|
||||
|
@@ -1,8 +1,6 @@
|
||||
|
||||
credentials: {}
|
||||
docker:
|
||||
images: {} # @todo Move under services
|
||||
versions: {} # @todo Move under services
|
||||
services:
|
||||
redis:
|
||||
enabled: false # Enable Redis
|
||||
|
@@ -21,6 +21,11 @@
|
||||
- name: "load docker, proxy for '{{ application_id }}'"
|
||||
include_role:
|
||||
name: sys-stk-full-stateless
|
||||
vars:
|
||||
aca_origin: "'{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always"
|
||||
aca_credentials: "'true' always"
|
||||
aca_methods: "'GET, OPTIONS' always"
|
||||
aca_headers: "'Accept, Authorization' always"
|
||||
|
||||
- name: Create symbolic link from .env file to repository
|
||||
file:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
location = /logout {
|
||||
# Proxy to the logout service
|
||||
{# Proxy to the logout service #}
|
||||
proxy_pass http://127.0.0.1:{{ ports.localhost.http['web-svc-logout'] }}/logout;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -7,18 +7,15 @@ location = /logout {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# CORS headers – allow your central page to call this
|
||||
add_header 'Access-Control-Allow-Origin' '{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always;
|
||||
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Accept, Authorization' always;
|
||||
{# CORS headers – allow your central page to call this #}
|
||||
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %}
|
||||
|
||||
# Disable caching absolutely
|
||||
{# Disable caching absolutely #}
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
||||
add_header Pragma "no-cache" always;
|
||||
add_header Expires "0" always;
|
||||
|
||||
# Handle preflight
|
||||
{# Handle preflight #}
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
|
||||
credentials: {}
|
||||
docker:
|
||||
images: {} # @todo Move under services
|
||||
versions: {} # @todo Move under services
|
||||
services:
|
||||
redis:
|
||||
enabled: false # Enable Redis
|
||||
|
@@ -3,6 +3,10 @@
|
||||
- name: "load docker, proxy for '{{ application_id }}'"
|
||||
include_role:
|
||||
name: sys-stk-full-stateless
|
||||
vars:
|
||||
aca_origin: "* always"
|
||||
aca_methods: "'GET, OPTIONS' always"
|
||||
aca_headers: "'Accept, Authorization, Content-Type' always"
|
||||
|
||||
- name: "Copy '{{ application_id }}' files"
|
||||
template:
|
||||
|
Reference in New Issue
Block a user