Skip to content

Commit

Permalink
feature #56985 [FrameworkBundle] Derivate kernel.secret from the de…
Browse files Browse the repository at this point in the history
…cryption secret when its env var is not defined (nicolas-grekas)

This PR was merged into the 7.2 branch.

Discussion
----------

[FrameworkBundle] Derivate `kernel.secret` from the decryption secret when its env var is not defined

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | #38021
| License       | MIT

I'm pursuing the goal of making `APP_SECRET` empty in the default recipe. See symfony/recipes#1314 for background.

At the moment, `kernel.secret` is used for remember-be, login-links and ESI. This means that when you start a project, you don't need it. But once you do enable those features, you'll get an "APP_SECRET env var not found" error message.

I think we can live with this error and the related DX. We need good doc of course.
Still, in order to make DX a bit smoother, I propose to derivate APP_SECRET from SYMFONY_DECRYPTION_SECRET when it's set.

This is what this PR does.

Of course, we should also document that creating a separate `APP_SECRET` is likely a good idea.
FTR, here is how one can trivially generate a value for APP_SECRET and put it in the vault, thus fixing #38021:

```sh
symfony console secrets:set APP_SECRET --random
```

Commits
-------

4749871a29 [FrameworkBundle] Derivate kernel.secret from the decryption secret when its env var is not defined
  • Loading branch information
fabpot committed May 25, 2024
2 parents 0297437 + 59f4fb5 commit 9e8bcf1
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
7 changes: 5 additions & 2 deletions DependencyInjection/FrameworkExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);

Expand Down Expand Up @@ -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');
Expand All @@ -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']) {
Expand Down
1 change: 1 addition & 0 deletions Resources/config/secrets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion Secrets/SodiumVault.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
9 changes: 9 additions & 0 deletions Tests/Secrets/SodiumVaultTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
}

0 comments on commit 9e8bcf1

Please sign in to comment.