diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 8309ce3e13ada..370786e613fa5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add support for setting `headers` with `Symfony\Bundle\FrameworkBundle\Controller\TemplateController` + * Derivate `kernel.secret` from the decryption secret when its env var is not defined 7.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 8786d04bd8da7..5586e6653f62a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -372,7 +372,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerDebugConfiguration($config['php_errors'], $container, $loader); $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); - $this->registerSecretsConfiguration($config['secrets'], $container, $loader); + $this->registerSecretsConfiguration($config['secrets'], $container, $loader, $config['secret'] ?? null); $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); @@ -1755,7 +1755,7 @@ private function registerPropertyAccessConfiguration(array $config, ContainerBui ; } - private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, ?string $secret): void { if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); @@ -1771,6 +1771,9 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c $loader->load('secrets.php'); + $container->resolveEnvPlaceholders($secret, null, $usedEnvs); + $secretEnvVar = 1 === \count($usedEnvs ?? []) ? substr(key($usedEnvs), 1 + (strrpos(key($usedEnvs), ':') ?: -1)) : null; + $container->getDefinition('secrets.vault')->replaceArgument(2, $secretEnvVar); $container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']); if ($config['local_dotenv_file']) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php index 8192f2f065c6f..a82f397b822d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php @@ -21,6 +21,7 @@ ->args([ abstract_arg('Secret dir, set in FrameworkExtension'), service('secrets.decryption_key')->ignoreOnInvalid(), + abstract_arg('Secret env var, set in FrameworkExtension'), ]) ->set('secrets.env_var_loader', StaticEnvVarLoader::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php index dcf79869f6cf5..f09c3b3af3301 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php @@ -26,16 +26,18 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface private string|\Stringable|null $decryptionKey = null; private string $pathPrefix; private ?string $secretsDir; + private ?string $derivedSecretEnvVar; /** * @param $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault * or null to store generated keys in the provided $secretsDir */ - public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable|null $decryptionKey = null) + public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable|null $decryptionKey = null, ?string $derivedSecretEnvVar = null) { $this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.'; $this->decryptionKey = $decryptionKey; $this->secretsDir = $secretsDir; + $this->derivedSecretEnvVar = $derivedSecretEnvVar; } public function generateKeys(bool $override = false): bool @@ -177,6 +179,11 @@ public function loadEnvVars(): array $envs[$name] = LazyString::fromCallable($reveal, $name); } + if ($this->derivedSecretEnvVar && !\array_key_exists($this->derivedSecretEnvVar, $envs)) { + $decryptionKey = $this->decryptionKey; + $envs[$this->derivedSecretEnvVar] = LazyString::fromCallable(static fn () => base64_encode(hash('sha256', $decryptionKey, true))); + } + return $envs; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php index 96d5dcea132a5..e31e8364f142d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php @@ -75,4 +75,13 @@ public function testEncryptAndDecrypt() $this->assertSame([], $vault->list()); } + + public function testDerivedSecretEnvVar() + { + $vault = new SodiumVault($this->secretsDir, null, 'MY_SECRET'); + $vault->generateKeys(); + $vault->seal('FOO', 'bar'); + + $this->assertSame(['FOO', 'MY_SECRET'], array_keys($vault->loadEnvVars())); + } }