From 1fa53222f39aaf8663a96a36edaf7c7d19ceb0ba Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Fri, 17 Jul 2026 15:13:15 +0200 Subject: [PATCH] ESB-1195 Introdiced minimal CSP policy for the admin-console --- .../apsadmin/jsp/entity/entityFinding.jsp | 2 +- .../aps/jsp/widgets/content_viewer_list.jsp | 2 +- .../inc/userFilter-module-entity-Date.jsp | 4 +- .../agiletec/aps/system/SystemConstants.java | 14 +- .../entando/aps/servlet/StartupListener.java | 11 +- .../aps/managers/baseManagersConfig.xml | 6 + .../aps/keycloakBaseManagersConfig.xml | 6 + .../aps/servlet/ControllerServlet.java | 11 +- .../entando/aps/servlet/CspHeaderFilter.java | 80 +++++++++++ .../aps/servlet/CspNonceGenerator.java | 41 ++++++ .../inc/attributes/front_dateAttribute.jsp | 4 +- .../aps/servlet/CspHeaderFilterTest.java | 124 ++++++++++++++++++ webapp/src/main/conf/systemParams.properties | 12 +- .../webapp/WEB-INF/aps/jsp/models/home.jsp | 4 +- webapp/src/main/webapp/WEB-INF/web.xml | 50 +++++++ .../message/modules/edit/dateAttribute.jsp | 4 +- 16 files changed, 351 insertions(+), 24 deletions(-) create mode 100644 portal-ui/src/main/java/org/entando/entando/aps/servlet/CspHeaderFilter.java create mode 100644 portal-ui/src/main/java/org/entando/entando/aps/servlet/CspNonceGenerator.java create mode 100644 portal-ui/src/test/java/org/entando/entando/aps/servlet/CspHeaderFilterTest.java diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/entityFinding.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/entityFinding.jsp index 4a45517d51..a73614d54d 100644 --- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/entityFinding.jsp +++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/entityFinding.jsp @@ -209,4 +209,4 @@ http://localhost:8080/PortalExample/do/Entity/search.action?entityManagerName=ja - \ No newline at end of file + diff --git a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/content_viewer_list.jsp b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/content_viewer_list.jsp index 159b98a357..c912d55e65 100644 --- a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/content_viewer_list.jsp +++ b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/content_viewer_list.jsp @@ -2,7 +2,7 @@ <%@ taglib prefix="wp" uri="/aps-core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - + diff --git a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/inc/userFilter-module-entity-Date.jsp b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/inc/userFilter-module-entity-Date.jsp index 5e3f8882a2..ed8ba8367d 100644 --- a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/inc/userFilter-module-entity-Date.jsp +++ b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/aps/jsp/widgets/inc/userFilter-module-entity-Date.jsp @@ -43,8 +43,8 @@ jQuery(function($){ }); - - + +
diff --git a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java index 9f02747047..3fef43c1f5 100644 --- a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java +++ b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java @@ -55,9 +55,13 @@ private SystemConstants(){} public static final String PAR_FILEUPLOAD_MAXSIZE = "fileUploadMaxSize"; public static final String PAR_CSP_ENABLED = "cspEnabled"; - + public static final String PAR_CSP_HEADER_EXTRA_CONFIG = "cspHeaderExtraConfig"; + public static final String PAR_CSP_BACKOFFICE_CONFIG = "cspBackofficeConfig"; + + public static final String PAR_CSP_BACKOFFICE_REPORT_ONLY = "cspBackofficeReportOnly"; + /** * Nome parametro extra per requestContext: lingua corrente */ @@ -317,7 +321,15 @@ private SystemConstants(){} public static final String ENTITY_EVENT_CHANNEL = "core-entity"; public static final String CSP_HEADER_ENABLED = "CSP_HEADER_ENABLED"; + public static final String CSP_HEADER_PORTAL_EXTRACONFIG = "CSP_HEADER_PORTAL_EXTRACONFIG"; + /** + * @deprecated use {@link #CSP_HEADER_PORTAL_EXTRACONFIG}, which makes it explicit that the + * variable configures the portal (front-end) policy; kept for backward compatibility. + */ + @Deprecated public static final String CSP_HEADER_EXTRACONFIG = "CSP_HEADER_EXTRACONFIG"; + public static final String CSP_HEADER_BACKOFFICE_CONFIG = "CSP_HEADER_BACKOFFICE_CONFIG"; + public static final String CSP_HEADER_BACKOFFICE_REPORT_ONLY = "CSP_HEADER_BACKOFFICE_REPORT_ONLY"; public static final String ENTANDO_CSRF_PROTECTION = "ENTANDO_CSRF_PROTECTION"; public static final String ENTANDO_CSRF_ALLOWED_DOMAINS = "ENTANDO_CSRF_ALLOWED_DOMAINS"; public static final String SEPARATOR_DOMAINS = ","; diff --git a/engine/src/main/java/org/entando/entando/aps/servlet/StartupListener.java b/engine/src/main/java/org/entando/entando/aps/servlet/StartupListener.java index 75d527ba12..ea9e6eedb7 100644 --- a/engine/src/main/java/org/entando/entando/aps/servlet/StartupListener.java +++ b/engine/src/main/java/org/entando/entando/aps/servlet/StartupListener.java @@ -72,9 +72,16 @@ public void contextInitialized(ServletContextEvent event) { String cspEnabled = System.getenv(SystemConstants.CSP_HEADER_ENABLED); if (StringUtils.isEmpty(cspEnabled) || Boolean.TRUE.toString().equalsIgnoreCase(cspEnabled)) { LOGGER.info("Content Security Policy (CSP) header is enabled"); - String cspExtraConfig = System.getenv(SystemConstants.CSP_HEADER_EXTRACONFIG); + String cspExtraConfig = System.getenv(SystemConstants.CSP_HEADER_PORTAL_EXTRACONFIG); + if (StringUtils.isEmpty(cspExtraConfig)) { + cspExtraConfig = System.getenv(SystemConstants.CSP_HEADER_EXTRACONFIG); + if (!StringUtils.isEmpty(cspExtraConfig)) { + LOGGER.warn("The CSP_HEADER_EXTRACONFIG environment variable is deprecated:" + + " use CSP_HEADER_PORTAL_EXTRACONFIG instead"); + } + } if (!StringUtils.isEmpty(cspExtraConfig)) { - LOGGER.info("Content Security Policy (CSP) extra-config set to: " + cspExtraConfig); + LOGGER.info("Content Security Policy (CSP) portal extra-config set to: " + cspExtraConfig); } } else { LOGGER.warn("Content Security Policy (CSP) header is not enabled"); diff --git a/engine/src/main/resources/spring/aps/managers/baseManagersConfig.xml b/engine/src/main/resources/spring/aps/managers/baseManagersConfig.xml index 4837304832..a320e1c9f4 100644 --- a/engine/src/main/resources/spring/aps/managers/baseManagersConfig.xml +++ b/engine/src/main/resources/spring/aps/managers/baseManagersConfig.xml @@ -118,6 +118,12 @@ ${csp.header.enabled:true} + + ${csp.header.backoffice.config:default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self'} + + + ${csp.header.backoffice.reportOnly:false} + diff --git a/keycloak-plugin/src/main/resources/spring/plugins/keycloak/aps/keycloakBaseManagersConfig.xml b/keycloak-plugin/src/main/resources/spring/plugins/keycloak/aps/keycloakBaseManagersConfig.xml index 4b1b98e5f8..4cf78b22b5 100644 --- a/keycloak-plugin/src/main/resources/spring/plugins/keycloak/aps/keycloakBaseManagersConfig.xml +++ b/keycloak-plugin/src/main/resources/spring/plugins/keycloak/aps/keycloakBaseManagersConfig.xml @@ -51,6 +51,12 @@ ${csp.header.enabled:true} + + ${csp.header.backoffice.config:default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self'} + + + ${csp.header.backoffice.reportOnly:false} + diff --git a/portal-ui/src/main/java/org/entando/entando/aps/servlet/ControllerServlet.java b/portal-ui/src/main/java/org/entando/entando/aps/servlet/ControllerServlet.java index af1af32f0c..90cd5dbbd6 100644 --- a/portal-ui/src/main/java/org/entando/entando/aps/servlet/ControllerServlet.java +++ b/portal-ui/src/main/java/org/entando/entando/aps/servlet/ControllerServlet.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import java.security.SecureRandom; import java.util.List; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; @@ -108,15 +107,7 @@ protected RequestContext initRequestContext(HttpServletRequest request, } public String createSecureRandomString() { - int leftLimit = 48; - int rightLimit = 122; - int targetStringLength = 64; - SecureRandom rand = new SecureRandom(); - return rand.ints(leftLimit, rightLimit + 1) - .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) - .limit(targetStringLength) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); + return CspNonceGenerator.createNonce(); } protected int controlRequest(HttpServletRequest request, diff --git a/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspHeaderFilter.java b/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspHeaderFilter.java new file mode 100644 index 0000000000..d9f28d908e --- /dev/null +++ b/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspHeaderFilter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2026-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.servlet; + +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.baseconfig.ConfigInterface; +import com.agiletec.aps.util.ApsWebApplicationUtils; +import java.io.IOException; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; + +/** + * Applies the Content-Security-Policy header to back-office requests + * (/do/*, /struts/*, /ExtStr2/do/*), which do not route through the + * {@link ControllerServlet} and would otherwise get no CSP at all. + *

+ * The policy is read from the "cspBackofficeConfig" system parameter and honors + * the same "cspEnabled" switch used for the portal. An optional "{nonce}" + * placeholder in the policy is replaced with a per-request secure random token, + * also exposed as the "cspNonceToken" request attribute for use in JSPs. + * When the "cspBackofficeReportOnly" parameter is true, the policy is sent as + * Content-Security-Policy-Report-Only, allowing violation monitoring without + * enforcement during rollout. + */ +public class CspHeaderFilter implements Filter { + + public static final String CSP_HEADER_NAME = "Content-Security-Policy"; + public static final String CSP_REPORT_ONLY_HEADER_NAME = "Content-Security-Policy-Report-Only"; + public static final String NONCE_PLACEHOLDER = "{nonce}"; + public static final String REQUEST_ATTR_CSP_NONCE = "cspNonceToken"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { + this.addCspHeader((HttpServletRequest) request, (HttpServletResponse) response); + } + chain.doFilter(request, response); + } + + protected void addCspHeader(HttpServletRequest request, HttpServletResponse response) { + ConfigInterface configManager = this.getConfigManager(request); + if (!Boolean.parseBoolean(configManager.getParam(SystemConstants.PAR_CSP_ENABLED))) { + return; + } + String policy = configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_CONFIG); + if (StringUtils.isBlank(policy)) { + return; + } + if (policy.contains(NONCE_PLACEHOLDER)) { + String nonce = CspNonceGenerator.createNonce(); + request.setAttribute(REQUEST_ATTR_CSP_NONCE, nonce); + policy = policy.replace(NONCE_PLACEHOLDER, nonce); + } + boolean reportOnly = Boolean.parseBoolean(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_REPORT_ONLY)); + response.setHeader(reportOnly ? CSP_REPORT_ONLY_HEADER_NAME : CSP_HEADER_NAME, policy); + } + + protected ConfigInterface getConfigManager(HttpServletRequest request) { + return (ConfigInterface) ApsWebApplicationUtils.getBean(SystemConstants.BASE_CONFIG_MANAGER, request); + } + +} diff --git a/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspNonceGenerator.java b/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspNonceGenerator.java new file mode 100644 index 0000000000..7093dfe5b0 --- /dev/null +++ b/portal-ui/src/main/java/org/entando/entando/aps/servlet/CspNonceGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2026-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.servlet; + +import java.security.SecureRandom; + +/** + * Generates the per-request secure random tokens used as Content-Security-Policy nonces, + * shared between the portal front-controller and the back-office CSP filter. + */ +public final class CspNonceGenerator { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private CspNonceGenerator() { + // utility class + } + + public static String createNonce() { + int leftLimit = 48; + int rightLimit = 122; + int targetStringLength = 64; + return RANDOM.ints(leftLimit, rightLimit + 1) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + +} diff --git a/portal-ui/src/main/webapp/WEB-INF/aps/jsp/internalservlet/user/inc/attributes/front_dateAttribute.jsp b/portal-ui/src/main/webapp/WEB-INF/aps/jsp/internalservlet/user/inc/attributes/front_dateAttribute.jsp index 7aca86e398..31ea2e3fdb 100644 --- a/portal-ui/src/main/webapp/WEB-INF/aps/jsp/internalservlet/user/inc/attributes/front_dateAttribute.jsp +++ b/portal-ui/src/main/webapp/WEB-INF/aps/jsp/internalservlet/user/inc/attributes/front_dateAttribute.jsp @@ -58,6 +58,6 @@ }); - - + + diff --git a/portal-ui/src/test/java/org/entando/entando/aps/servlet/CspHeaderFilterTest.java b/portal-ui/src/test/java/org/entando/entando/aps/servlet/CspHeaderFilterTest.java new file mode 100644 index 0000000000..39580bd8a8 --- /dev/null +++ b/portal-ui/src/test/java/org/entando/entando/aps/servlet/CspHeaderFilterTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2026-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.servlet; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; + +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.baseconfig.ConfigInterface; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CspHeaderFilterTest { + + private static final String POLICY = "default-src 'self'; object-src 'none'"; + + @Mock + private ConfigInterface configManager; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private FilterChain filterChain; + + private CspHeaderFilter filter; + + @BeforeEach + void setUp() { + this.filter = new CspHeaderFilter() { + @Override + protected ConfigInterface getConfigManager(HttpServletRequest request) { + return configManager; + } + }; + } + + @Test + void shouldSetEnforcedHeaderWhenCspIsEnabled() throws Exception { + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_ENABLED)).thenReturn("true"); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_CONFIG)).thenReturn(POLICY); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_REPORT_ONLY)).thenReturn("false"); + filter.doFilter(request, response, filterChain); + Mockito.verify(response).setHeader(CspHeaderFilter.CSP_HEADER_NAME, POLICY); + Mockito.verify(filterChain).doFilter(request, response); + } + + @Test + void shouldSetReportOnlyHeaderWhenConfigured() throws Exception { + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_ENABLED)).thenReturn("true"); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_CONFIG)).thenReturn(POLICY); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_REPORT_ONLY)).thenReturn("true"); + filter.doFilter(request, response, filterChain); + Mockito.verify(response).setHeader(CspHeaderFilter.CSP_REPORT_ONLY_HEADER_NAME, POLICY); + Mockito.verify(response, never()).setHeader(eq(CspHeaderFilter.CSP_HEADER_NAME), anyString()); + Mockito.verify(filterChain).doFilter(request, response); + } + + @Test + void shouldNotSetHeaderWhenCspIsDisabled() throws Exception { + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_ENABLED)).thenReturn("false"); + filter.doFilter(request, response, filterChain); + Mockito.verify(response, never()).setHeader(anyString(), anyString()); + Mockito.verify(filterChain).doFilter(request, response); + } + + @Test + void shouldNotSetHeaderWhenPolicyIsBlank() throws Exception { + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_ENABLED)).thenReturn("true"); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_CONFIG)).thenReturn(" "); + filter.doFilter(request, response, filterChain); + Mockito.verify(response, never()).setHeader(anyString(), anyString()); + Mockito.verify(filterChain).doFilter(request, response); + } + + @Test + void shouldReplaceNoncePlaceholderAndExposeRequestAttribute() throws Exception { + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_ENABLED)).thenReturn("true"); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_CONFIG)) + .thenReturn("script-src 'nonce-{nonce}'; object-src 'none'"); + Mockito.when(configManager.getParam(SystemConstants.PAR_CSP_BACKOFFICE_REPORT_ONLY)).thenReturn("false"); + filter.doFilter(request, response, filterChain); + ArgumentCaptor nonceCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(request).setAttribute(eq(CspHeaderFilter.REQUEST_ATTR_CSP_NONCE), nonceCaptor.capture()); + String nonce = nonceCaptor.getValue(); + Assertions.assertEquals(64, nonce.length()); + Mockito.verify(response).setHeader(CspHeaderFilter.CSP_HEADER_NAME, + "script-src 'nonce-" + nonce + "'; object-src 'none'"); + } + + @Test + void shouldGenerateDifferentNoncesPerRequest() { + String first = CspNonceGenerator.createNonce(); + String second = CspNonceGenerator.createNonce(); + Assertions.assertNotEquals(first, second); + Assertions.assertTrue(first.matches("[0-9A-Za-z]{64}")); + } + +} diff --git a/webapp/src/main/conf/systemParams.properties b/webapp/src/main/conf/systemParams.properties index d380177547..e1a95d436f 100644 --- a/webapp/src/main/conf/systemParams.properties +++ b/webapp/src/main/conf/systemParams.properties @@ -52,7 +52,17 @@ widgets.internalServlet=formAction ## Security configuration csp.header.enabled=${CSP_HEADER_ENABLED:true} -csp.header.extraConfig=${CSP_HEADER_EXTRACONFIG:'strict-dynamic' https: 'self'; object-src 'none'; base-uri 'self'} +# Appended to "script-src 'nonce-...'" on portal (front-end) responses: the first tokens continue +# the script-src directive, then ";" starts the other directives. +# Prefer the CSP_HEADER_PORTAL_EXTRACONFIG environment variable, which makes it explicit that this +# policy targets portal pages only (the back office is configured by CSP_HEADER_BACKOFFICE_CONFIG). +# CSP_HEADER_EXTRACONFIG is deprecated and kept as a fallback for backward compatibility. +csp.header.extraConfig=${CSP_HEADER_PORTAL_EXTRACONFIG:${CSP_HEADER_EXTRACONFIG:'strict-dynamic' https: 'self'; default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:; worker-src 'self'; child-src 'self' https:; frame-src 'self' https:; report-to default}} +# Full CSP applied by CspHeaderFilter to back-office responses (/do/*, /struts/*, /ExtStr2/do/*). +# An optional "{nonce}" placeholder is replaced with a per-request token (see CspHeaderFilter). +csp.header.backoffice.config=${CSP_HEADER_BACKOFFICE_CONFIG:default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://ajax.googleapis.com/ https://code.jquery.com; style-src 'self' 'unsafe-inline' https://code.jquery.com; img-src 'self' data: https:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self' https:; frame-src 'self' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/} +# When true, the back-office policy is sent as Content-Security-Policy-Report-Only (safe rollout mode). +csp.header.backoffice.reportOnly=${CSP_HEADER_BACKOFFICE_REPORT_ONLY:false} ##---------------------- ## CMS configuration diff --git a/webapp/src/main/webapp/WEB-INF/aps/jsp/models/home.jsp b/webapp/src/main/webapp/WEB-INF/aps/jsp/models/home.jsp index 077e276430..5fe1fa266e 100644 --- a/webapp/src/main/webapp/WEB-INF/aps/jsp/models/home.jsp +++ b/webapp/src/main/webapp/WEB-INF/aps/jsp/models/home.jsp @@ -58,8 +58,8 @@

diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml index db0b3ddc62..5b84510c3e 100644 --- a/webapp/src/main/webapp/WEB-INF/web.xml +++ b/webapp/src/main/webapp/WEB-INF/web.xml @@ -57,6 +57,10 @@ XSSFilter org.entando.entando.aps.servlet.XSSFilter + + CspHeaderFilter + org.entando.entando.aps.servlet.CspHeaderFilter + CharacterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -87,6 +91,48 @@ mdcUserFilter /* + + CspHeaderFilter + /do/* + REQUEST + FORWARD + ERROR + + + CspHeaderFilter + /struts/* + REQUEST + FORWARD + ERROR + + + CspHeaderFilter + /ExtStr2/do/* + REQUEST + FORWARD + ERROR + + + CspHeaderFilter + /error + REQUEST + FORWARD + ERROR + + + CspHeaderFilter + /404.jsp + REQUEST + FORWARD + ERROR + + + CspHeaderFilter + /error.jsp + REQUEST + FORWARD + ERROR + struts2 /do/* @@ -216,6 +262,10 @@ index.jsp adminindex.jsp + + cjs + text/javascript + 404 /404.jsp diff --git a/webdynamicform-plugin/src/main/webapp/WEB-INF/plugins/jpwebdynamicform/aps/jsp/internalservlet/message/modules/edit/dateAttribute.jsp b/webdynamicform-plugin/src/main/webapp/WEB-INF/plugins/jpwebdynamicform/aps/jsp/internalservlet/message/modules/edit/dateAttribute.jsp index 3c1c0d9787..625bd4ecf7 100644 --- a/webdynamicform-plugin/src/main/webapp/WEB-INF/plugins/jpwebdynamicform/aps/jsp/internalservlet/message/modules/edit/dateAttribute.jsp +++ b/webdynamicform-plugin/src/main/webapp/WEB-INF/plugins/jpwebdynamicform/aps/jsp/internalservlet/message/modules/edit/dateAttribute.jsp @@ -58,8 +58,8 @@ }); - - + + \ No newline at end of file