NelmioSecurityBundle icon indicating copy to clipboard operation
NelmioSecurityBundle copied to clipboard

Introduce `DirectiveSetBuilderInterface` to allow runtime modification

Open martijnc opened this issue 1 year ago • 1 comments

This PR introduces the DirectiveSetBuilderInterface and the default implementation ConfigurationDirectiveSetBuilder proposed in #347, to allow for runtime modification of the CSP directive sets. The major changes are:

  • Introduction of DirectiveSetBuilderInterface and ConfigurationDirectiveSetBuilder;
  • The ContentSecurityPolicyListener constructor now takes DirectiveSetBuilderInterface instead of the directive sets directly;
  • Changes to NelmioSecurityExtension to provide the configuration to the ConfigurationDirectiveSetBuilders, which in turn are injected into ContentSecurityPolicyListener (instead of the directive sets).

This adds a layer between the configuration (and the directive sets built from it) and ContentSecurityPolicyListener. This layer provides an integration point for application code to modify the directive sets based on the request (e.g., in a controller or a kernel event listener).

martijnc avatar Jun 01 '24 18:06 martijnc

Sorry but I won't manage to review this one right now, seems solid at first sight but I'd rather give it some more thought.

Seldaek avatar Jul 05 '24 07:07 Seldaek

@Seldaek Any chance this can be looked at? Thanks in advance!

acrobat avatar Feb 20 '25 14:02 acrobat

Thanks - what would be great is if someone can provide some code sample for the docs on how this can be used to configure extra values

Seldaek avatar Mar 11 '25 09:03 Seldaek

I now implemented my CSP exceptions like this:

public function page(ContentSecurityPolicyListener $cspListener) {
   // Allow *.doubleclick.net as extra script-src
    $cspListener->getEnforcement()->setDirective('script-src', $cspListener->getEnforcement()->getDirective('script-src') . ' *.doubleclick.net');

    return $this->render('exampe.html.twig');
}

ContentSecurityPolicyListener::getEnforcement() is now deprecated.

Can an example be provided how to achieve the same behaviour in the new way?

karstennilsen avatar Mar 11 '25 10:03 karstennilsen

The idea is that your app or bundle can now provide its own DirectiveSetBuilderInterface which builds or modifies the directive set. Your implementation can replace or decorate nelmio_security.directive_set_builder.enforce. @.inner would be the default implementation (ConfigurationDirectiveSetBuilder), which holds the directive set that is build from the config. For your case, you could do something like this:

<?php

declare(strict_types=1);

namespace App;

use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSet;
use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSetBuilderInterface;

class EnforcedDirectiveSetBuilder implements DirectiveSetBuilderInterface
{
    private DirectiveSet $enforcedSet;

    public function __construct(
        DirectiveSetBuilderInterface $decorated,
    ) {
        // Get the directive set build from the config.
        $this->enforcedSet = $decorated->buildDirectiveSet();
    }

    public function buildDirectiveSet(): DirectiveSet
    {
        return $this->enforcedSet;
    }

    // or `addScriptSrc`, ....
    public function getDirectiveSet(): DirectiveSet
    {
        return $this->enforcedSet;
    }
}

And register it in the container

    App\EnforcedDirectiveSetBuilder:
        decorates: 'nelmio_security.directive_set_builder.enforce'
        arguments:
            - '@.inner'

In your controller you can then inject EnforcedDirectiveSetBuilder instead and do

$cspEnforcement = $this->enforcedDirectiveSetBuilder->getDirectiveSet();
$cspEnforcement->setDirective('script-src', $cspEnforcement->getDirective('script-src') . ' https://*.some');

The EnforcedDirectiveSetBuilder can do a lot more; you can add methods like addScriptSrc or inject the RequestStack and modify based on the request URI, the matched route, ...

I will look at creating a PR to add some examples to the documentation.

martijnc avatar Mar 12 '25 10:03 martijnc