From cc3f5d75ea02142594018944d17a3fd87f5deed7 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Mon, 19 May 2025 10:05:30 +0200 Subject: [PATCH] Optimized CSP --- filter_plugins/csp_filters.py | 10 ++++++++ roles/docker-keycloak/vars/configuration.yml | 2 +- roles/docker-peertube/vars/configuration.yml | 2 +- roles/docker-pgadmin/vars/configuration.yml | 11 ++++++++- .../docker-phpmyadmin/vars/configuration.yml | 8 ++++++- tests/unit/test_csp_filters.py | 24 +++++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/filter_plugins/csp_filters.py b/filter_plugins/csp_filters.py index c417155c..a0382c01 100644 --- a/filter_plugins/csp_filters.py +++ b/filter_plugins/csp_filters.py @@ -120,6 +120,16 @@ class FilterModule(object): ): tokens.append('https://www.google.com') + # Enable loading via ancestors + if ( + self.is_feature_enabled(applications, 'portfolio_iframe', application_id) + and directive == 'frame-ancestors' + ): + domain = domains.get(application_id) # e.g. "sub.example.com" or "example.com" + # Extract the second-level + top-level domain and prefix with "*." + sld_tld = ".".join(domain.split(".")[-2:]) # yields "example.com" + tokens.append(f"*.{sld_tld}") # yields "*.example.com" + # whitelist tokens += self.get_csp_whitelist(applications, application_id, directive) diff --git a/roles/docker-keycloak/vars/configuration.yml b/roles/docker-keycloak/vars/configuration.yml index ae28a135..734bb1f2 100644 --- a/roles/docker-keycloak/vars/configuration.yml +++ b/roles/docker-keycloak/vars/configuration.yml @@ -6,7 +6,7 @@ import_realm: True # If True realm will b credentials: features: matomo: true - css: true + css: false portfolio_iframe: true ldap: true central_database: true diff --git a/roles/docker-peertube/vars/configuration.yml b/roles/docker-peertube/vars/configuration.yml index 93bffacf..1636ae6d 100644 --- a/roles/docker-peertube/vars/configuration.yml +++ b/roles/docker-peertube/vars/configuration.yml @@ -1,7 +1,7 @@ version: "bookworm" features: matomo: true - css: true + css: false portfolio_iframe: false central_database: true csp: diff --git a/roles/docker-pgadmin/vars/configuration.yml b/roles/docker-pgadmin/vars/configuration.yml index 4444254a..57c3d5b9 100644 --- a/roles/docker-pgadmin/vars/configuration.yml +++ b/roles/docker-pgadmin/vars/configuration.yml @@ -12,4 +12,13 @@ features: css: true portfolio_iframe: false central_database: true - oauth2: true \ No newline at end of file + oauth2: true +csp: + flags: + style-src: + unsafe-inline: true + script-src: + unsafe-inline: true + whitelist: + font-src: + - "data:" \ No newline at end of file diff --git a/roles/docker-phpmyadmin/vars/configuration.yml b/roles/docker-phpmyadmin/vars/configuration.yml index 903723b1..d20e4314 100644 --- a/roles/docker-phpmyadmin/vars/configuration.yml +++ b/roles/docker-phpmyadmin/vars/configuration.yml @@ -9,4 +9,10 @@ features: portfolio_iframe: false central_database: true oauth2: true -hostname: central-mariadb \ No newline at end of file +hostname: central-mariadb +csp: + flags: + style-src: + unsafe-inline: true + script-src: + unsafe-inline: true \ No newline at end of file diff --git a/tests/unit/test_csp_filters.py b/tests/unit/test_csp_filters.py index a03da385..41c9c868 100644 --- a/tests/unit/test_csp_filters.py +++ b/tests/unit/test_csp_filters.py @@ -167,5 +167,29 @@ class TestCspFilters(unittest.TestCase): ) self.assertNotIn("https://www.google.com", header_disabled) + def test_build_csp_header_frame_ancestors(self): + """ + frame-ancestors should include the wildcarded SLD+TLD when + 'portfolio_iframe' is enabled, and omit it when disabled. + """ + # Ensure feature enabled and domain set + self.apps['app1']['features']['portfolio_iframe'] = True + # simulate a subdomain for the application + self.domains['app1'] = 'sub.domain-example.com' + + header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') + # Expect '*.domain-example.com' in the frame-ancestors directive + self.assertRegex( + header, + r"frame-ancestors\s+'self'\s+\*\.domain-example\.com;" + ) + + # Now disable the feature and rebuild + self.apps['app1']['features']['portfolio_iframe'] = False + header_no = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') + # Should no longer contain the wildcarded sld.tld + self.assertNotIn("*.domain-example.com", header_no) + + if __name__ == '__main__': unittest.main() \ No newline at end of file