Skip to content

Commit 22230f7

Browse files
committed
feature #34131 [FrameworkBundle] Remove suffix convention when using env vars to override secrets from the vault (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [FrameworkBundle] Remove suffix convention when using env vars to override secrets from the vault | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Right now, env vars that override encrypted secrets must en up with `_SECRET`. This PR removes this convention. It also enforces that only vars defined in the vault can be overriden locally. This means one cannot set a local-only secret. Commits ------- 2ec9647 [FrameworkBundle] Remove suffix convention when using env vars to override secrets from the vault
2 parents 54e1d12 + 2ec9647 commit 22230f7

8 files changed

+54
-47
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ protected function configure()
4545
->setDescription('Decrypts all secrets and stores them in the local vault.')
4646
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces overriding of secrets that already exist in the local vault')
4747
->setHelp(<<<'EOF'
48-
The <info>%command.name%</info> command list decrypts all secrets and stores them in the local vault..
48+
The <info>%command.name%</info> command decrypts all secrets and copies them in the local vault.
4949
5050
<info>%command.full_name%</info>
5151

src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php

+7-19
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
1515
use Symfony\Component\Console\Command\Command;
1616
use Symfony\Component\Console\Input\InputInterface;
17-
use Symfony\Component\Console\Input\InputOption;
1817
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1918
use Symfony\Component\Console\Output\OutputInterface;
2019
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -43,15 +42,10 @@ protected function configure()
4342
{
4443
$this
4544
->setDescription('Encrypts all local secrets to the vault.')
46-
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces overriding of secrets that already exist in the vault')
4745
->setHelp(<<<'EOF'
48-
The <info>%command.name%</info> command list encrypts all local secrets and stores them in the vault..
46+
The <info>%command.name%</info> command encrypts all locally overridden secrets to the vault.
4947
5048
<info>%command.full_name%</info>
51-
52-
When the option <info>--force</info> is provided, secrets that already exist in the vault are overriden.
53-
54-
<info>%command.full_name% --force</info>
5549
EOF
5650
)
5751
;
@@ -67,22 +61,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6761
return 1;
6862
}
6963

70-
$secrets = $this->localVault->list(true);
71-
72-
if (!$input->getOption('force')) {
73-
foreach ($this->vault->list() as $k => $v) {
74-
unset($secrets[$k]);
75-
}
76-
}
64+
foreach ($this->vault->list(true) as $name => $value) {
65+
$localValue = $this->localVault->reveal($name);
7766

78-
foreach ($secrets as $k => $v) {
79-
if (null === $v) {
80-
$io->error($this->localVault->getLastMessage());
67+
if (null !== $localValue && $value !== $localValue) {
68+
$this->vault->seal($name, $localValue);
69+
} elseif (null !== $message = $this->localVault->getLastMessage()) {
70+
$io->error($message);
8171

8272
return 1;
8373
}
84-
85-
$this->vault->seal($k, $v);
8674
}
8775

8876
return 0;

src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8989
}
9090

9191
foreach ($localSecrets ?? [] as $name => $value) {
92-
$rows[$name] = [$name, $rows[$name][1] ?? '', $dump($value)];
92+
if (isset($rows[$name])) {
93+
$rows[$name][] = $dump($value);
94+
}
9395
}
9496

95-
uksort($rows, 'strnatcmp');
96-
9797
if (null !== $this->localVault && null !== $message = $this->localVault->getLastMessage()) {
9898
$io->comment($message);
9999
}

src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8686
return 1;
8787
}
8888

89+
if ($this->localVault === $vault && !\array_key_exists($name, $this->vault->list())) {
90+
$io->error(sprintf('Secret "%s" does not exist in the vault, you cannot override it locally.', $name));
91+
92+
return 1;
93+
}
94+
8995
if (0 < $random = $input->getOption('random') ?? 16) {
9096
$value = strtr(substr(base64_encode(random_bytes($random)), 0, $random), '+/', '-_');
9197
} elseif (!$file = $input->getArgument('file')) {

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

+8-11
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ public function seal(string $name, string $value): void
3636
{
3737
$this->lastMessage = null;
3838
$this->validateName($name);
39-
$k = $name.'_SECRET';
4039
$v = str_replace("'", "'\\''", $value);
4140

4241
$content = file_exists($this->dotenvFile) ? file_get_contents($this->dotenvFile) : '';
43-
$content = preg_replace("/^$k=((\\\\'|'[^']++')++|.*)/m", "$k='$v'", $content, -1, $count);
42+
$content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)/m", "$name='$v'", $content, -1, $count);
4443

4544
if (!$count) {
46-
$content .= "$k='$v'\n";
45+
$content .= "$name='$v'\n";
4746
}
4847

4948
file_put_contents($this->dotenvFile, $content);
@@ -55,8 +54,7 @@ public function reveal(string $name): ?string
5554
{
5655
$this->lastMessage = null;
5756
$this->validateName($name);
58-
$k = $name.'_SECRET';
59-
$v = \is_string($_SERVER[$k] ?? null) ? $_SERVER[$k] : ($_ENV[$k] ?? null);
57+
$v = \is_string($_SERVER[$name] ?? null) ? $_SERVER[$name] : ($_ENV[$name] ?? null);
6058

6159
if (null === $v) {
6260
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile));
@@ -71,10 +69,9 @@ public function remove(string $name): bool
7169
{
7270
$this->lastMessage = null;
7371
$this->validateName($name);
74-
$k = $name.'_SECRET';
7572

7673
$content = file_exists($this->dotenvFile) ? file_get_contents($this->dotenvFile) : '';
77-
$content = preg_replace("/^$k=((\\\\'|'[^']++')++|.*)\n?/m", '', $content, -1, $count);
74+
$content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)\n?/m", '', $content, -1, $count);
7875

7976
if ($count) {
8077
file_put_contents($this->dotenvFile, $content);
@@ -94,14 +91,14 @@ public function list(bool $reveal = false): array
9491
$secrets = [];
9592

9693
foreach ($_ENV as $k => $v) {
97-
if (preg_match('/^(\w+)_SECRET$/D', $k, $m)) {
98-
$secrets[$m[1]] = $reveal ? $v : null;
94+
if (preg_match('/^\w+$/D', $k)) {
95+
$secrets[$k] = $reveal ? $v : null;
9996
}
10097
}
10198

10299
foreach ($_SERVER as $k => $v) {
103-
if (\is_string($v) && preg_match('/^(\w+)_SECRET$/D', $k, $m)) {
104-
$secrets[$m[1]] = $reveal ? $v : null;
100+
if (\is_string($v) && preg_match('/^\w+$/D', $k)) {
101+
$secrets[$k] = $reveal ? $v : null;
105102
}
106103
}
107104

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ public static function getProvidedTypes()
4444
/**
4545
* {@inheritdoc}
4646
*/
47-
public function getEnv($prefix, $name, \Closure $getEnv)
47+
public function getEnv($prefix, $name, \Closure $getEnv): string
4848
{
49-
if (null !== $this->localVault && null !== $secret = $this->localVault->reveal($name)) {
49+
if (null !== $this->localVault && null !== ($secret = $this->localVault->reveal($name)) && \array_key_exists($name, $this->vault->list())) {
5050
return $secret;
5151
}
5252

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

+22-6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ public function seal(string $name, string $value): void
8787
$this->validateName($name);
8888
$this->loadKeys();
8989
$this->export($name.'.'.substr_replace(md5($name), '.sodium', -26), sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey)));
90+
91+
$list = $this->list();
92+
$list[$name] = null;
93+
uksort($list, 'strnatcmp');
94+
file_put_contents($this->pathPrefix.'sodium.list', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
95+
9096
$this->lastMessage = sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
9197
}
9298

@@ -123,6 +129,10 @@ public function remove(string $name): bool
123129
return false;
124130
}
125131

132+
$list = $this->list();
133+
unset($list[$name]);
134+
file_put_contents($this->pathPrefix.'sodium.list', sprintf("<?php\n\nreturn %s;\n", var_export($list, true), LOCK_EX));
135+
126136
$this->lastMessage = sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
127137

128138
return @unlink($file) || !file_exists($file);
@@ -131,13 +141,19 @@ public function remove(string $name): bool
131141
public function list(bool $reveal = false): array
132142
{
133143
$this->lastMessage = null;
134-
$secrets = [];
135-
$regexp = sprintf('{^%s(\w++)\.[0-9a-f]{6}\.sodium$}D', preg_quote(basename($this->pathPrefix)));
136144

137-
foreach (scandir(\dirname($this->pathPrefix)) as $name) {
138-
if (preg_match($regexp, $name, $m)) {
139-
$secrets[$m[1]] = $reveal ? $this->reveal($m[1]) : null;
140-
}
145+
if (!file_exists($file = $this->pathPrefix.'sodium.list')) {
146+
return [];
147+
}
148+
149+
$secrets = include $file;
150+
151+
if (!$reveal) {
152+
return $secrets;
153+
}
154+
155+
foreach ($secrets as $name => $value) {
156+
$secrets[$name] = $this->reveal($name);
141157
}
142158

143159
return $secrets;

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ public function testEncryptAndDecrypt()
3737

3838
$vault->seal('foo', $plain);
3939

40-
unset($_SERVER['foo_SECRET'], $_ENV['foo_SECRET']);
40+
unset($_SERVER['foo'], $_ENV['foo']);
4141
(new Dotenv(false))->load($this->envFile);
4242

4343
$decrypted = $vault->reveal('foo');
4444
$this->assertSame($plain, $decrypted);
4545

46-
$this->assertSame(['foo' => null], $vault->list());
47-
$this->assertSame(['foo' => $plain], $vault->list(true));
46+
$this->assertSame(['foo' => null], array_intersect_key($vault->list(), ['foo' => 123]));
47+
$this->assertSame(['foo' => $plain], array_intersect_key($vault->list(true), ['foo' => 123]));
4848

4949
$this->assertTrue($vault->remove('foo'));
5050
$this->assertFalse($vault->remove('foo'));
5151

52-
unset($_SERVER['foo_SECRET'], $_ENV['foo_SECRET']);
52+
unset($_SERVER['foo'], $_ENV['foo']);
5353
(new Dotenv(false))->load($this->envFile);
5454

55-
$this->assertSame([], $vault->list());
55+
$this->assertArrayNotHasKey('foo', $vault->list());
5656
}
5757
}

0 commit comments

Comments
 (0)