symfony: run-time json encoded environment variable string does not work in security.yml

Symfony Version 3.4, PHP 7, Apache 2.4

What we are trying to do?

We are trying to restrict a route (/anon/foo) to a set of IPs via Symfony’s security access_control. The IPs will be read as a runtime environment variable from Apache. To group the IPs together, we will use a json encoded string containing multiple IPs and then have symfony decode them using Symfony’s Json environment variable processor.

STEP 1: Set up an environment variable as a json encoded string in Apache as below:

<VirtualHost *:443>
    ...
    SetEnv whitelisted_ip "[\"127.0.0.1\", \"127.0.0.2\"]
</VirtualHost>

STEP 2: Set up your application’s parameter.yml to decode that environment variable as below:

    whitelisted_ips_for_access_control: '%env(json:whitelisted_ip)%'

Step 3: Observe the parameter in \Symfony\Component\HttpKernel\Kernel::handle after $this->boot(); The values have been decoded into an array perfectly.

        $this->boot();
        $foo = $this->getContainer()->getParameter( 'whitelisted_ips_for_access_control');

Step 4: Set security.yml so that the route is only accessibly via certain IPs as below:

security:
...
    firewalls:
        anon_area:
            pattern: ^/anon
            anonymous: true
...
    access_control:
        - { path: ^/anon/foo, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: '%whitelisted_ips_for_access_control%' }
        - { path: ^/anon/foo, roles: ROLE_NO_ACCESS}

Step 5: Clear cache and observe the container file var/cache/test/ContainerP59owun/getSecurity_AccessMapService.php. Notice that the variable has been passed to RequestMatcher as an array array(0 => $this->getEnv('json:whitelisted_ip'))

<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;

// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the private 'security.access_map' shared service.

$this->services['security.access_map'] = $instance = new \Symfony\Component\Security\Http\AccessMap();

$a = new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo');

$instance->add(new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo', NULL, array(), array(0 => $this->getEnv('json:whitelisted_ip'))), array(0 => 'IS_AUTHENTICATED_ANONYMOUSLY'), NULL);

Step 6: Go back to your application’s parameters.yml that you changed in Step 2. Now change that parameter as below:

    whitelisted_ips_for_access_control: ["127.0.01", "127.0.02"]

Step 7: Clear cache again and observe the same container file Tests/_app/var/cache/test/ContainerAjyqbl9/getSecurity_AccessMapService.php

<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;

$this->services['security.access_map'] = $instance = new \Symfony\Component\Security\Http\AccessMap();

$a = new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo');

$instance->add(new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo', NULL, array(), $this->parameters['whitelisted_ips_for_access_control']), array(0 => 'IS_AUTHENTICATED_ANONYMOUSLY'), NULL);

Step 8: Compare the following two lines from Step 7 and Step 5

with parameter set as whitelisted_ips_for_access_control: '%env(json:whitelisted_ip)%'

$instance->add(new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo', NULL, array(), array(0 => $this->getEnv('json:whitelisted_ip'))), array(0 => 'IS_AUTHENTICATED_ANONYMOUSLY'), NULL);

with parameter set as whitelisted_ips_for_access_control: ["127.0.01", "127.0.02"]

$instance->add(new \Symfony\Component\HttpFoundation\RequestMatcher('^/anon/foo', NULL, array(), $this->parameters['whitelisted_ips_for_access_control']), array(0 => 'IS_AUTHENTICATED_ANONYMOUSLY'), NULL);

When the parameter is being read at runtime, a ContextErrorException is thrown at vendor\symfony\symfony\src\Symfony\Component\HttpFoundation\IpUtils.php (line 66) since the run time environment variable was wrapped in an array rather than be passed directly.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 4
  • Comments: 19 (6 by maintainers)

Most upvoted comments

Still a issue.

Not every config value can be set using an environment variable. For example, if the value is used at compile time to influence how the container is built (like that’s the case here), an environment variable cannot be used (see the more detailed explanations in #45868 for example).

That’s because of our logic to support both string and arrays in this config setting, which does not detect that the string is actually an env placeholder there (not sure we can do it currently) and wraps it in an array.

Yes. We need to do something here, esp. if beforeNormalization() is converting envs to array/non-strings making the value incompatible too early.

Any normalizer is allowed to modify/concat the env string (this is the only case we where we “leak” its placeholder value).

but we can make the ExprBuilder aware of placeholder values, so e.g. in closures you could ask for a 2nd arg bool $isEnv or so. Will look into this 👍

The csv processsor is also affected by this problem I’m afraid

What about a new list: prefix to use, which will validate to be a flat array of scalar values ([1,2,3,...]). And thus can be made compatible with prototyped array nodes like security.access_control.N.ips (e.g. would also solve the trusted hosts node).

Alternatively and maybe more pragmatic; make the json: prefix compatible here. Kinda like we did for envs vs. cannotBeEmpty() in #26799