mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-09 11:47:14 +02:00
Refactor CSP filters to use get_url for domain resolution and update tests to check CSP directives order-independently. See: https://chatgpt.com/share/68b49e5c-6774-800f-9d8e-a3f980799c08
This commit is contained in:
@@ -5,6 +5,7 @@ import sys, os
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
from module_utils.config_utils import get_app_conf
|
from module_utils.config_utils import get_app_conf
|
||||||
|
from module_utils.get_url import get_url
|
||||||
|
|
||||||
class FilterModule(object):
|
class FilterModule(object):
|
||||||
"""
|
"""
|
||||||
@@ -131,17 +132,14 @@ class FilterModule(object):
|
|||||||
flags = self.get_csp_flags(applications, application_id, directive)
|
flags = self.get_csp_flags(applications, application_id, directive)
|
||||||
tokens += flags
|
tokens += flags
|
||||||
|
|
||||||
|
if directive in [ 'script-src-elem', 'connect-src', 'style-src-elem' ]:
|
||||||
|
# Allow fetching from internal CDN as default for all applications
|
||||||
|
tokens.append(get_url(domains,'web-svc-cdn',web_protocol))
|
||||||
|
|
||||||
if directive in ['script-src-elem', 'connect-src']:
|
if directive in ['script-src-elem', 'connect-src']:
|
||||||
# Matomo integration
|
# Matomo integration
|
||||||
if self.is_feature_enabled(applications, matomo_feature_name, application_id):
|
if self.is_feature_enabled(applications, matomo_feature_name, application_id):
|
||||||
matomo_domain = domains.get('web-app-matomo')[0]
|
tokens.append(get_url(domains,'web-app-matomo',web_protocol))
|
||||||
if matomo_domain:
|
|
||||||
tokens.append(f"{web_protocol}://{matomo_domain}")
|
|
||||||
|
|
||||||
# Allow fetching from internal CDN as default for all applications
|
|
||||||
domain = domains.get('web-svc-cdn')[0]
|
|
||||||
tokens.append(f"{web_protocol}://{domain}")
|
|
||||||
|
|
||||||
# ReCaptcha integration: allow loading scripts from Google if feature enabled
|
# ReCaptcha integration: allow loading scripts from Google if feature enabled
|
||||||
if self.is_feature_enabled(applications, 'recaptcha', application_id):
|
if self.is_feature_enabled(applications, 'recaptcha', application_id):
|
||||||
@@ -159,12 +157,10 @@ class FilterModule(object):
|
|||||||
if self.is_feature_enabled(applications, 'logout', application_id):
|
if self.is_feature_enabled(applications, 'logout', application_id):
|
||||||
|
|
||||||
# Allow logout via infinito logout proxy
|
# Allow logout via infinito logout proxy
|
||||||
domain = domains.get('web-svc-logout')[0]
|
tokens.append(get_url(domains,'web-svc-logout',web_protocol))
|
||||||
tokens.append(f"{web_protocol}://{domain}")
|
|
||||||
|
|
||||||
# Allow logout via keycloak app
|
# Allow logout via keycloak app
|
||||||
domain = domains.get('web-app-keycloak')[0]
|
tokens.append(get_url(domains,'web-app-keycloak',web_protocol))
|
||||||
tokens.append(f"{web_protocol}://{domain}")
|
|
||||||
|
|
||||||
# whitelist
|
# whitelist
|
||||||
tokens += self.get_csp_whitelist(applications, application_id, directive)
|
tokens += self.get_csp_whitelist(applications, application_id, directive)
|
||||||
|
@@ -55,6 +55,28 @@ class TestCspFilters(unittest.TestCase):
|
|||||||
'web-svc-cdn': ['cdn.example.org'],
|
'web-svc-cdn': ['cdn.example.org'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Helpers -------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_directive_tokens(self, header: str, directive: str):
|
||||||
|
"""
|
||||||
|
Extract tokens (as a list of strings) for a given directive from a CSP header.
|
||||||
|
Example: for "connect-src 'self' https://a https://b;" -> ["'self'", "https://a", "https://b"]
|
||||||
|
Returns [] if not found.
|
||||||
|
"""
|
||||||
|
for part in header.split(';'):
|
||||||
|
part = part.strip()
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
|
if part.startswith(directive + ' '):
|
||||||
|
# remove directive name, split remainder by spaces
|
||||||
|
remainder = part[len(directive):].strip()
|
||||||
|
return [tok for tok in remainder.split(' ') if tok]
|
||||||
|
if part == directive: # unlikely, but guard
|
||||||
|
return []
|
||||||
|
return []
|
||||||
|
|
||||||
|
# --- Tests ---------------------------------------------------------------
|
||||||
|
|
||||||
def test_get_csp_whitelist_list(self):
|
def test_get_csp_whitelist_list(self):
|
||||||
result = self.filter.get_csp_whitelist(self.apps, 'app1', 'script-src-elem')
|
result = self.filter.get_csp_whitelist(self.apps, 'app1', 'script-src-elem')
|
||||||
self.assertEqual(result, ['https://cdn.example.com'])
|
self.assertEqual(result, ['https://cdn.example.com'])
|
||||||
@@ -85,18 +107,23 @@ class TestCspFilters(unittest.TestCase):
|
|||||||
header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https')
|
header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https')
|
||||||
# Ensure core directives are present
|
# Ensure core directives are present
|
||||||
self.assertIn("default-src 'self';", header)
|
self.assertIn("default-src 'self';", header)
|
||||||
|
|
||||||
# script-src-elem should include 'self', Matomo, internes CDN und explizite Whitelist-CDN
|
# script-src-elem should include 'self', Matomo, internes CDN und explizite Whitelist-CDN
|
||||||
self.assertIn("script-src-elem 'self'", header)
|
self.assertIn("script-src-elem 'self'", header)
|
||||||
self.assertIn("https://matomo.example.org", header)
|
self.assertIn("https://matomo.example.org", header)
|
||||||
self.assertIn("https://cdn.example.org", header) # internes CDN
|
self.assertIn("https://cdn.example.org", header) # internes CDN
|
||||||
self.assertIn("https://cdn.example.com", header) # Whitelist
|
self.assertIn("https://cdn.example.com", header) # Whitelist
|
||||||
|
|
||||||
# script-src directive should include unsafe-eval
|
# script-src directive should include unsafe-eval
|
||||||
self.assertIn("script-src 'self' 'unsafe-eval'", header)
|
self.assertIn("script-src 'self' 'unsafe-eval'", header)
|
||||||
# connect-src directive
|
|
||||||
self.assertIn(
|
# connect-src directive (reihenfolgeunabhängig prüfen)
|
||||||
"connect-src 'self' https://matomo.example.org https://cdn.example.org https://api.example.com;",
|
tokens = self._get_directive_tokens(header, "connect-src")
|
||||||
header
|
self.assertIn("'self'", tokens)
|
||||||
)
|
self.assertIn("https://matomo.example.org", tokens)
|
||||||
|
self.assertIn("https://cdn.example.org", tokens)
|
||||||
|
self.assertIn("https://api.example.com", tokens)
|
||||||
|
|
||||||
# ends with img-src
|
# ends with img-src
|
||||||
self.assertTrue(header.strip().endswith('img-src * data: blob:;'))
|
self.assertTrue(header.strip().endswith('img-src * data: blob:;'))
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user