Skip to content

Commit 57e9b81

Browse files
feature #34295 [DI][FrameworkBundle] add EnvVarLoaderInterface - remove SecretEnvVarProcessor (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [DI][FrameworkBundle] add EnvVarLoaderInterface - remove SecretEnvVarProcessor | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - This PR allows encrypting any env vars - not only those using the `%env(secret:<...>)%` processor (and the processor is removed actually). It does so by introducing a new `EnvVarLoaderInterface` (and a corresponding `container.env_var_loader` tag), which are objects that should return a list of key/value pairs that will be accessible via the regular `%env(FOO)%` syntax. The PR fixes a few issues found meanwhile. One is especially important: files in the vault should end with `.php` to protect against inadvertant exposures of the document root. Commits ------- ba2148f [DI][FrameworkBundle] add EnvVarLoaderInterface - remove SecretEnvVarProcessor
2 parents 585c0df + ba2148f commit 57e9b81

File tree

11 files changed

+137
-97
lines changed

11 files changed

+137
-97
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use Symfony\Component\DependencyInjection\ContainerBuilder;
5050
use Symfony\Component\DependencyInjection\ContainerInterface;
5151
use Symfony\Component\DependencyInjection\Definition;
52+
use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
5253
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
5354
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
5455
use Symfony\Component\DependencyInjection\Exception\LogicException;
@@ -376,6 +377,8 @@ public function load(array $configs, ContainerBuilder $container)
376377
->addTag('console.command');
377378
$container->registerForAutoconfiguration(ResourceCheckerInterface::class)
378379
->addTag('config_cache.resource_checker');
380+
$container->registerForAutoconfiguration(EnvVarLoaderInterface::class)
381+
->addTag('container.env_var_loader');
379382
$container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
380383
->addTag('container.env_var_processor');
381384
$container->registerForAutoconfiguration(ServiceLocator::class)

src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<services>
88
<service id="secrets.vault" class="Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault">
9+
<tag name="container.env_var_loader" />
910
<argument>%kernel.project_dir%/config/secrets/%kernel.environment%</argument>
1011
<argument type="service" id="secrets.decryption_key" on-invalid="ignore" />
1112
</service>
@@ -31,11 +32,5 @@
3132
<service id="secrets.local_vault" class="Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault">
3233
<argument>%kernel.project_dir%/.env.local</argument>
3334
</service>
34-
35-
<service id="secrets.env_var_processor" class="Symfony\Bundle\FrameworkBundle\Secrets\SecretEnvVarProcessor">
36-
<argument type="service" id="secrets.vault" />
37-
<argument type="service" id="secrets.local_vault" on-invalid="ignore" />
38-
<tag name="container.env_var_processor" />
39-
</service>
4035
</services>
4136
</container>

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,11 @@
121121
<argument type="service" id="request_stack" />
122122
<tag name="kernel.event_subscriber" />
123123
</service>
124+
125+
<service id="container.env_var_processor" class="Symfony\Component\DependencyInjection\EnvVarProcessor">
126+
<tag name="container.env_var_processor" />
127+
<argument type="service" id="service_container" />
128+
<argument type="tagged_iterator" tag="container.env_var_loader" />
129+
</service>
124130
</services>
125131
</container>

src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function reveal(string $name): ?string
5454
{
5555
$this->lastMessage = null;
5656
$this->validateName($name);
57-
$v = \is_string($_SERVER[$name] ?? null) ? $_SERVER[$name] : ($_ENV[$name] ?? null);
57+
$v = \is_string($_SERVER[$name] ?? null) && 0 !== strpos($name, 'HTTP_') ? $_SERVER[$name] : ($_ENV[$name] ?? null);
5858

5959
if (null === $v) {
6060
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile));

src/Symfony/Bundle/FrameworkBundle/Secrets/SecretEnvVarProcessor.php

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Secrets;
1313

14+
use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
15+
1416
/**
1517
* @author Tobias Schultze <http://tobion.de>
1618
* @author Jérémy Derussé <jeremy@derusse.com>
1719
* @author Nicolas Grekas <p@tchwork.com>
1820
*
1921
* @internal
2022
*/
21-
class SodiumVault extends AbstractVault
23+
class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
2224
{
2325
private $encryptionKey;
2426
private $decryptionKey;
@@ -56,8 +58,8 @@ public function generateKeys(bool $override = false): bool
5658
// ignore failures to load keys
5759
}
5860

59-
if ('' !== $this->decryptionKey && !file_exists($this->pathPrefix.'sodium.encrypt.public')) {
60-
$this->export('sodium.encrypt.public', $this->encryptionKey);
61+
if ('' !== $this->decryptionKey && !file_exists($this->pathPrefix.'encrypt.public.php')) {
62+
$this->export('encrypt.public', $this->encryptionKey);
6163
}
6264

6365
if (!$override && null !== $this->encryptionKey) {
@@ -69,10 +71,10 @@ public function generateKeys(bool $override = false): bool
6971
$this->decryptionKey = sodium_crypto_box_keypair();
7072
$this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey);
7173

72-
$this->export('sodium.encrypt.public', $this->encryptionKey);
73-
$this->export('sodium.decrypt.private', $this->decryptionKey);
74+
$this->export('encrypt.public', $this->encryptionKey);
75+
$this->export('decrypt.private', $this->decryptionKey);
7476

75-
$this->lastMessage = sprintf('Sodium keys have been generated at "%s*.{public,private}".', $this->getPrettyPath($this->pathPrefix));
77+
$this->lastMessage = sprintf('Sodium keys have been generated at "%s*.public/private.php".', $this->getPrettyPath($this->pathPrefix));
7678

7779
return true;
7880
}
@@ -82,12 +84,12 @@ public function seal(string $name, string $value): void
8284
$this->lastMessage = null;
8385
$this->validateName($name);
8486
$this->loadKeys();
85-
$this->export($name.'.'.substr_replace(md5($name), '.sodium', -26), sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey)));
87+
$this->export($name.'.'.substr(md5($name), 0, 6), sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey)));
8688

8789
$list = $this->list();
8890
$list[$name] = null;
8991
uksort($list, 'strnatcmp');
90-
file_put_contents($this->pathPrefix.'sodium.list', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
92+
file_put_contents($this->pathPrefix.'list.php', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
9193

9294
$this->lastMessage = sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
9395
}
@@ -97,7 +99,7 @@ public function reveal(string $name): ?string
9799
$this->lastMessage = null;
98100
$this->validateName($name);
99101

100-
if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.sodium', -26))) {
102+
if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.php', -26))) {
101103
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
102104

103105
return null;
@@ -131,15 +133,15 @@ public function remove(string $name): bool
131133
$this->lastMessage = null;
132134
$this->validateName($name);
133135

134-
if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.sodium', -26))) {
136+
if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.php', -26))) {
135137
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
136138

137139
return false;
138140
}
139141

140142
$list = $this->list();
141143
unset($list[$name]);
142-
file_put_contents($this->pathPrefix.'sodium.list', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
144+
file_put_contents($this->pathPrefix.'list.php', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
143145

144146
$this->lastMessage = sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
145147

@@ -150,7 +152,7 @@ public function list(bool $reveal = false): array
150152
{
151153
$this->lastMessage = null;
152154

153-
if (!file_exists($file = $this->pathPrefix.'sodium.list')) {
155+
if (!file_exists($file = $this->pathPrefix.'list.php')) {
154156
return [];
155157
}
156158

@@ -167,6 +169,11 @@ public function list(bool $reveal = false): array
167169
return $secrets;
168170
}
169171

172+
public function loadEnvVars(): array
173+
{
174+
return $this->list(true);
175+
}
176+
170177
private function loadKeys(): void
171178
{
172179
if (!\function_exists('sodium_crypto_box_seal')) {
@@ -177,12 +184,12 @@ private function loadKeys(): void
177184
return;
178185
}
179186

180-
if (file_exists($this->pathPrefix.'sodium.decrypt.private')) {
181-
$this->decryptionKey = (string) include $this->pathPrefix.'sodium.decrypt.private';
187+
if (file_exists($this->pathPrefix.'decrypt.private.php')) {
188+
$this->decryptionKey = (string) include $this->pathPrefix.'decrypt.private.php';
182189
}
183190

184-
if (file_exists($this->pathPrefix.'sodium.encrypt.public')) {
185-
$this->encryptionKey = (string) include $this->pathPrefix.'sodium.encrypt.public';
191+
if (file_exists($this->pathPrefix.'encrypt.public.php')) {
192+
$this->encryptionKey = (string) include $this->pathPrefix.'encrypt.public.php';
186193
} elseif ('' !== $this->decryptionKey) {
187194
$this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey);
188195
} else {
@@ -196,7 +203,7 @@ private function export(string $file, string $data): void
196203
$data = str_replace('%', '\x', rawurlencode($data));
197204
$data = sprintf("<?php // %s on %s\n\nreturn \"%s\";\n", $name, date('r'), $data);
198205

199-
if (false === file_put_contents($this->pathPrefix.$file, $data, LOCK_EX)) {
206+
if (false === file_put_contents($this->pathPrefix.$file.'.php', $data, LOCK_EX)) {
200207
$e = error_get_last();
201208
throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? E_USER_WARNING);
202209
}

src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ public function testGenerateKeys()
2626
$vault = new SodiumVault($this->secretsDir);
2727

2828
$this->assertTrue($vault->generateKeys());
29-
$this->assertFileExists($this->secretsDir.'/test.sodium.encrypt.public');
30-
$this->assertFileExists($this->secretsDir.'/test.sodium.decrypt.private');
29+
$this->assertFileExists($this->secretsDir.'/test.encrypt.public.php');
30+
$this->assertFileExists($this->secretsDir.'/test.decrypt.private.php');
3131

32-
$encKey = file_get_contents($this->secretsDir.'/test.sodium.encrypt.public');
33-
$decKey = file_get_contents($this->secretsDir.'/test.sodium.decrypt.private');
32+
$encKey = file_get_contents($this->secretsDir.'/test.encrypt.public.php');
33+
$decKey = file_get_contents($this->secretsDir.'/test.decrypt.private.php');
3434

3535
$this->assertFalse($vault->generateKeys());
36-
$this->assertStringEqualsFile($this->secretsDir.'/test.sodium.encrypt.public', $encKey);
37-
$this->assertStringEqualsFile($this->secretsDir.'/test.sodium.decrypt.private', $decKey);
36+
$this->assertStringEqualsFile($this->secretsDir.'/test.encrypt.public.php', $encKey);
37+
$this->assertStringEqualsFile($this->secretsDir.'/test.decrypt.private.php', $decKey);
3838

3939
$this->assertTrue($vault->generateKeys(true));
40-
$this->assertStringNotEqualsFile($this->secretsDir.'/test.sodium.encrypt.public', $encKey);
41-
$this->assertStringNotEqualsFile($this->secretsDir.'/test.sodium.decrypt.private', $decKey);
40+
$this->assertStringNotEqualsFile($this->secretsDir.'/test.encrypt.public.php', $encKey);
41+
$this->assertStringNotEqualsFile($this->secretsDir.'/test.decrypt.private.php', $decKey);
4242
}
4343

4444
public function testEncryptAndDecrypt()

src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar
188188
$checkFunction = sprintf('is_%s', $parameter->getType()->getName());
189189

190190
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
191-
throw new InvalidParameterTypeException($this->currentId, \gettype($value), $parameter);
191+
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? \get_class($value) : \gettype($value), $parameter);
192192
}
193193
}
194194
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection;
13+
14+
/**
15+
* EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
interface EnvVarLoaderInterface
20+
{
21+
/**
22+
* @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax
23+
*/
24+
public function loadEnvVars(): array;
25+
}

src/Symfony/Component/DependencyInjection/EnvVarProcessor.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection;
1313

1414
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
15+
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
1516
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1617

1718
/**
@@ -20,10 +21,17 @@
2021
class EnvVarProcessor implements EnvVarProcessorInterface
2122
{
2223
private $container;
24+
private $loaders;
25+
private $loadedVars = [];
2326

24-
public function __construct(ContainerInterface $container)
27+
/**
28+
* @param EnvVarLoaderInterface[] $loaders
29+
*/
30+
public function __construct(ContainerInterface $container, \Traversable $loaders = null)
2531
{
2632
$this->container = $container;
33+
$this->loaders = new \IteratorIterator($loaders ?? new \ArrayIterator());
34+
$this->loaders = $this->loaders->getInnerIterator();
2735
}
2836

2937
/**
@@ -127,12 +135,31 @@ public function getEnv($prefix, $name, \Closure $getEnv)
127135
} elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
128136
$env = $_SERVER[$name];
129137
} elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues
130-
if (!$this->container->hasParameter("env($name)")) {
131-
throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
138+
foreach ($this->loadedVars as $vars) {
139+
if (false !== $env = ($vars[$name] ?? false)) {
140+
break;
141+
}
132142
}
133143

134-
if (null === $env = $this->container->getParameter("env($name)")) {
135-
return null;
144+
try {
145+
while ((false === $env || null === $env) && $this->loaders->valid()) {
146+
$loader = $this->loaders->current();
147+
$this->loaders->next();
148+
$this->loadedVars[] = $vars = $loader->loadEnvVars();
149+
$env = $vars[$name] ?? false;
150+
}
151+
} catch (ParameterCircularReferenceException $e) {
152+
// skip loaders that need an env var that is not defined
153+
}
154+
155+
if (false === $env || null === $env) {
156+
if (!$this->container->hasParameter("env($name)")) {
157+
throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
158+
}
159+
160+
if (null === $env = $this->container->getParameter("env($name)")) {
161+
return null;
162+
}
136163
}
137164
}
138165

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy