Skip to content

Commit

Permalink
[Admin] Security - Add handler to enable Content Security Policy (#11447
Browse files Browse the repository at this point in the history
)

* [Admin] Security - Add handler to enable Content Security Policy

* [Admin] Security - Add handler to enable Content Security Policy

* [Admin] Security - Add handler to enable Content Security Policy - fix document preview

* [Admin] Security - Add handler to enable Content Security Policy - add docs

* [Admin] Security - Add handler to enable Content Security Policy - review changes

* [Admin] Security - Add handler to enable Content Security Policy - review changes

* [Admin] Security - Add handler to enable Content Security Policy - review changes

* [Admin] Security - Add handler to enable Content Security Policy - review changes

* added missing config node + micro optimizations

Co-authored-by: Bernhard Rusch <bernhard.rusch@elements.at>
  • Loading branch information
dvesh3 and brusch committed Mar 14, 2022
1 parent 86f6caf commit 6e0922c
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 15 deletions.
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\AdminBundle\DependencyInjection\Compiler;

use Pimcore\Bundle\AdminBundle\Security\ContentSecurityPolicyHandler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* @internal
*/
final class ContentSecurityPolicyUrlsPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition(ContentSecurityPolicyHandler::class);


$config = $container->getParameter('pimcore_admin.config');

if (count($config['admin_csp_header']['additional_urls'])) {
foreach ($config['admin_csp_header']['additional_urls'] as $additionalUrlsKey => $additionalUrlsArr) {
$definition->addMethodCall('addAllowedUrls', [$additionalUrlsKey, $additionalUrlsArr]);
}
}
}
}
37 changes: 37 additions & 0 deletions bundles/AdminBundle/DependencyInjection/Configuration.php
Expand Up @@ -15,6 +15,7 @@

namespace Pimcore\Bundle\AdminBundle\DependencyInjection;

use Pimcore\Bundle\AdminBundle\Security\ContentSecurityPolicyHandler;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand Down Expand Up @@ -54,6 +55,42 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->arrayNode('admin_csp_header')
->canBeEnabled()
->info('Can be used to enable or disable the Content Security Policy headers.')
->children()
->arrayNode('additional_urls')
->addDefaultsIfNotSet()
->normalizeKeys(false)
->children()
->arrayNode(ContentSecurityPolicyHandler::DEFAULT_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::IMG_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::SCRIPT_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::STYLE_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::CONNECT_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::FONT_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::MEDIA_OPT)
->scalarPrototype()->end()
->end()
->arrayNode(ContentSecurityPolicyHandler::FRAME_OPT)
->scalarPrototype()->end()
->end()
->end()
->end()
->end()
->end()
->scalarNode('custom_admin_path_identifier')
->defaultNull()
->validate()
Expand Down
83 changes: 83 additions & 0 deletions bundles/AdminBundle/EventListener/AdminSecurityListener.php
@@ -0,0 +1,83 @@
<?php

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\AdminBundle\EventListener;

use Pimcore\Bundle\AdminBundle\Security\ContentSecurityPolicyHandler;
use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
use Pimcore\Config;
use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
use Pimcore\Http\RequestHelper;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* @internal
*/
class AdminSecurityListener implements EventSubscriberInterface
{
use PimcoreContextAwareTrait;

/**
* @param ContentSecurityPolicyHandler $contentSecurityPolicyHandler
*/
public function __construct(
protected RequestHelper $requestHelper,
protected ContentSecurityPolicyHandler $contentSecurityPolicyHandler,
protected Config $config
)
{
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}

public function onKernelResponse(ResponseEvent $event)
{
if (!$this->config['admin_csp_header']['enabled']) {
return;
}

$request = $event->getRequest();

if (!$event->isMainRequest()) {
return;
}

if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_ADMIN)) {
return;
}

if ($this->requestHelper->isFrontendRequestByAdmin($request)) {
return;
}

$response = $event->getResponse();

// set CSP header with random nonce string to the response
$response->headers->set("Content-Security-Policy", $this->contentSecurityPolicyHandler->getCspHeader());
}

}


2 changes: 2 additions & 0 deletions bundles/AdminBundle/PimcoreAdminBundle.php
Expand Up @@ -15,6 +15,7 @@

namespace Pimcore\Bundle\AdminBundle;

use Pimcore\Bundle\AdminBundle\DependencyInjection\Compiler\ContentSecurityPolicyUrlsPass;
use Pimcore\Bundle\AdminBundle\DependencyInjection\Compiler\GDPRDataProviderPass;
use Pimcore\Bundle\AdminBundle\DependencyInjection\Compiler\ImportExportLocatorsPass;
use Pimcore\Bundle\AdminBundle\DependencyInjection\Compiler\SerializerPass;
Expand Down Expand Up @@ -44,6 +45,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new GDPRDataProviderPass());
$container->addCompilerPass(new ImportExportLocatorsPass());
$container->addCompilerPass(new TranslationServicesPass());
$container->addCompilerPass(new ContentSecurityPolicyUrlsPass());

/** @var SecurityExtension $extension */
$extension = $container->getExtension('security');
Expand Down
1 change: 1 addition & 0 deletions bundles/AdminBundle/Resources/config/event_listeners.yaml
Expand Up @@ -8,6 +8,7 @@ services:
# SECURITY
#

Pimcore\Bundle\AdminBundle\EventListener\AdminSecurityListener: ~
Pimcore\Bundle\AdminBundle\EventListener\BruteforceProtectionListener: ~

Pimcore\Bundle\AdminBundle\EventListener\AdminAuthenticationDoubleCheckListener:
Expand Down
7 changes: 7 additions & 0 deletions bundles/AdminBundle/Resources/config/security_services.yaml
Expand Up @@ -65,6 +65,13 @@ services:
tags:
- { name: monolog.logger, channel: security }

Pimcore\Bundle\AdminBundle\Security\ContentSecurityPolicyHandler:
public: true
calls:
- [ setLogger, [ '@logger' ] ]
tags:
- { name: monolog.logger, channel: security }

# user checker checking admin users for validity
Pimcore\Bundle\AdminBundle\Security\User\UserChecker: ~

Expand Up @@ -31,8 +31,6 @@ pimcore.document.pages.preview = Class.create({

if (this.layout == null) {

var iframeOnLoad = "pimcore.globalmanager.get('document_" + this.page.id + "').preview.iFrameLoaded()";

// preview switcher only for pages not for emails
var tbar = [];
if(this.page.getType() == "page") {
Expand Down Expand Up @@ -124,9 +122,16 @@ pimcore.document.pages.preview = Class.create({
scrollable: false,
bodyStyle: "background:#323232;",
bodyCls: "pimcore_overflow_scrolling",
html: '<iframe src="about:blank" onload="' + iframeOnLoad + '" frameborder="0" ' +
html: '<iframe src="about:blank" frameborder="0" ' +
'style="width: 100%;background: #fff;" id="' + this.iframeName + '" ' +
'name="' + this.iframeName + '"></iframe>'
'name="' + this.iframeName + '"></iframe>',
listeners: {
afterrender: function () {
Ext.get(this.getIframe()).on('load', function () {
this.iFrameLoaded();
}.bind(this));
}.bind(this)
}
});

this.timeSlider = Ext.create('Ext.slider.Single', {
Expand Down
20 changes: 10 additions & 10 deletions bundles/AdminBundle/Resources/views/Admin/Index/index.html.twig
Expand Up @@ -88,7 +88,7 @@
<title>{{ settings.hostname }} :: Pimcore</title>
<script>
<script {{ pimcore_csp.getNonceHtmlAttribute()|raw }}>
var pimcore = {}; // namespace
// hide symfony toolbar by default
Expand All @@ -98,8 +98,8 @@
}
</script>
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="{{ path('fos_js_routing_js', {'callback' : 'fos.Router.setData'}) }}"></script>
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
<script src="{{ path('fos_js_routing_js', {'callback' : 'fos.Router.setData'}) }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
</head>
<body class="pimcore_version_10" data-app-env="{{ app.environment }}">
Expand Down Expand Up @@ -698,17 +698,17 @@

<!-- some javascript -->
{# pimcore constants #}
<script>
<script {{ pimcore_csp.getNonceHtmlAttribute()|raw }}>
pimcore.settings = {{(settings|json_encode(constant('JSON_PRETTY_PRINT'))|raw)}};
</script>

<script src="{{ path('pimcore_admin_misc_jsontranslationssystem', {'language': language, '_dc': settings.build }) }}"></script>
<script src="{{ path('pimcore_admin_user_getcurrentuser', {'_dc': settings.build }) }}"></script>
<script src="{{ path('pimcore_admin_misc_availablelanguages', {'_dc': settings.build }) }}"></script>
<script src="{{ path('pimcore_admin_misc_jsontranslationssystem', {'language': language, '_dc': settings.build }) }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
<script src="{{ path('pimcore_admin_user_getcurrentuser', {'_dc': settings.build }) }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
<script src="{{ path('pimcore_admin_misc_availablelanguages', {'_dc': settings.build }) }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>

<!-- library scripts -->
{% for scriptUrl in scriptLibs %}
<script src="/bundles/pimcoreadmin/js/{{ scriptUrl }}?_dc={{ settings.build }}"></script>
<script src="/bundles/pimcoreadmin/js/{{ scriptUrl }}?_dc={{ settings.build }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
{% endfor %}

<!-- internal scripts -->
Expand All @@ -734,7 +734,7 @@
<!-- bundle scripts -->
{% if settings.disableMinifyJs %}
{% for pluginJsPath in pluginJsPaths %}
<script src="{{ pluginJsPath }}?_dc={{ pluginDcValue }}"></script>
<script src="{{ pluginJsPath }}?_dc={{ pluginDcValue }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
{% endfor %}
{% else %}
{{ pimcore_minimize_scripts(pluginJsPaths)|raw }}
Expand All @@ -745,6 +745,6 @@
{% endfor %}

{# MUST BE THE LAST LINE #}
<script src="/bundles/pimcoreadmin/js/pimcore/startup.js?_dc={{ settings.build }}"></script>
<script src="/bundles/pimcoreadmin/js/pimcore/startup.js?_dc={{ settings.build }}" {{ pimcore_csp.getNonceHtmlAttribute()|raw }}></script>
</body>
</html>

0 comments on commit 6e0922c

Please sign in to comment.