Skip to content

Commit 5829921

Browse files
committed
secrets commands.
1 parent e88f05a commit 5829921

7 files changed

Lines changed: 1028 additions & 1 deletion

File tree

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"minimum-stability": "stable",
1313
"require": {
1414
"php": "^8.4",
15-
"neuron-php/application": "0.8.*"
15+
"neuron-php/application": "0.8.*",
16+
"neuron-php/data": "0.7.*",
17+
"symfony/yaml": "^6.4"
1618
},
1719
"require-dev": {
1820
"phpunit/phpunit": "^9.0",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace Neuron\Cli\Commands\Secrets;
4+
5+
use Neuron\Cli\Commands\Command;
6+
use Neuron\Data\Settings\SecretManager;
7+
8+
/**
9+
* Edit encrypted secrets command
10+
*
11+
* Opens encrypted credentials in an editor for secure editing.
12+
* Automatically re-encrypts the file when the editor is closed.
13+
*
14+
* Usage:
15+
* neuron secrets:edit # Edit default secrets
16+
* neuron secrets:edit --env=production # Edit production secrets
17+
* neuron secrets:edit --editor="code --wait" # Use VS Code
18+
*
19+
* @package Neuron\Cli\Commands\Secrets
20+
*/
21+
class EditCommand extends Command
22+
{
23+
private SecretManager $secretManager;
24+
25+
/**
26+
* @inheritDoc
27+
*/
28+
public function getName(): string
29+
{
30+
return 'secrets:edit';
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function getDescription(): string
37+
{
38+
return 'Edit encrypted secrets file';
39+
}
40+
41+
/**
42+
* @inheritDoc
43+
*/
44+
public function configure(): void
45+
{
46+
$this->addOption( 'env', 'e', true, 'Environment to edit (default: base secrets)' );
47+
$this->addOption( 'editor', null, true, 'Editor to use (default: vi)' );
48+
$this->addOption( 'config', 'c', true, 'Config directory path (default: config)' );
49+
}
50+
51+
/**
52+
* @inheritDoc
53+
*/
54+
public function execute(): int
55+
{
56+
$configPath = $this->input->getOption( 'config', 'config' );
57+
$env = $this->input->getOption( 'env' );
58+
$editor = $this->input->getOption( 'editor' ) ?? $_ENV['EDITOR'] ?? 'vi';
59+
60+
// Determine paths based on environment
61+
if( $env )
62+
{
63+
$credentialsPath = $configPath . '/secrets/' . $env . '.yml.enc';
64+
$keyPath = $configPath . '/secrets/' . $env . '.key';
65+
$this->output->info( "Editing {$env} environment secrets..." );
66+
}
67+
else
68+
{
69+
$credentialsPath = $configPath . '/secrets.yml.enc';
70+
$keyPath = $configPath . '/master.key';
71+
$this->output->info( "Editing base secrets..." );
72+
}
73+
74+
// Create SecretManager
75+
$this->secretManager = new SecretManager();
76+
77+
try
78+
{
79+
// Ensure key exists
80+
if( !file_exists( $keyPath ) )
81+
{
82+
$this->output->warning( "Key file not found at: {$keyPath}" );
83+
$this->output->info( "Generating new encryption key..." );
84+
85+
$key = $this->secretManager->generateKey( $keyPath );
86+
$this->output->success( "Generated new key at: {$keyPath}" );
87+
$this->output->warning( "IMPORTANT: Add {$keyPath} to .gitignore!" );
88+
}
89+
90+
// Edit the secrets
91+
$result = $this->secretManager->edit( $credentialsPath, $keyPath, $editor );
92+
93+
if( $result )
94+
{
95+
$this->output->success( "Secrets saved to: {$credentialsPath}" );
96+
97+
// First time setup reminder
98+
if( !$env && !file_exists( $configPath . '/.gitignore' ) )
99+
{
100+
$this->output->newLine();
101+
$this->output->warning( "Remember to:" );
102+
$this->output->write( "1. Add {$keyPath} to .gitignore" );
103+
$this->output->write( "2. Commit {$credentialsPath} to version control" );
104+
$this->output->write( "3. Share {$keyPath} securely with your team" );
105+
}
106+
}
107+
else
108+
{
109+
$this->output->error( "Failed to save secrets" );
110+
return 1;
111+
}
112+
}
113+
catch( \Exception $e )
114+
{
115+
$this->output->error( "Error editing secrets: " . $e->getMessage() );
116+
117+
if( $this->input->hasOption( 'verbose' ) )
118+
{
119+
$this->output->write( $e->getTraceAsString() );
120+
}
121+
122+
return 1;
123+
}
124+
125+
return 0;
126+
}
127+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
namespace Neuron\Cli\Commands\Secrets\Key;
4+
5+
use Neuron\Cli\Commands\Command;
6+
use Neuron\Data\Settings\SecretManager;
7+
8+
/**
9+
* Generate encryption key command
10+
*
11+
* Generates a new encryption key for securing credentials.
12+
* Keys are cryptographically secure random values.
13+
*
14+
* Usage:
15+
* neuron secrets:key:generate # Generate master key
16+
* neuron secrets:key:generate --env=production # Generate production key
17+
* neuron secrets:key:generate --force # Overwrite existing key
18+
*
19+
* @package Neuron\Cli\Commands\Secrets\Key
20+
*/
21+
class GenerateCommand extends Command
22+
{
23+
private SecretManager $secretManager;
24+
25+
/**
26+
* @inheritDoc
27+
*/
28+
public function getName(): string
29+
{
30+
return 'secrets:key:generate';
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function getDescription(): string
37+
{
38+
return 'Generate a new encryption key for secrets';
39+
}
40+
41+
/**
42+
* @inheritDoc
43+
*/
44+
public function configure(): void
45+
{
46+
$this->addOption( 'env', 'e', true, 'Environment for the key (default: master key)' );
47+
$this->addOption( 'config', 'c', true, 'Config directory path (default: config)' );
48+
$this->addOption( 'force', 'f', false, 'Overwrite existing key file' );
49+
$this->addOption( 'show', 's', false, 'Display the generated key' );
50+
}
51+
52+
/**
53+
* @inheritDoc
54+
*/
55+
public function execute(): int
56+
{
57+
$configPath = $this->input->getOption( 'config', 'config' );
58+
$env = $this->input->getOption( 'env' );
59+
$force = $this->input->hasOption( 'force' );
60+
$show = $this->input->hasOption( 'show' );
61+
62+
// Determine key path based on environment
63+
if( $env )
64+
{
65+
$keyPath = $configPath . '/secrets/' . $env . '.key';
66+
$keyName = $env . ' environment key';
67+
68+
// Ensure directory exists
69+
$dir = dirname( $keyPath );
70+
if( !is_dir( $dir ) )
71+
{
72+
if( !mkdir( $dir, 0755, true ) )
73+
{
74+
$this->output->error( "Failed to create directory: {$dir}" );
75+
return 1;
76+
}
77+
}
78+
}
79+
else
80+
{
81+
$keyPath = $configPath . '/master.key';
82+
$keyName = 'master key';
83+
}
84+
85+
// Check if key already exists
86+
if( file_exists( $keyPath ) && !$force )
87+
{
88+
$this->output->error( "Key file already exists: {$keyPath}" );
89+
$this->output->info( "Use --force to overwrite the existing key." );
90+
$this->output->warning( "WARNING: Overwriting will make existing encrypted files unreadable!" );
91+
return 1;
92+
}
93+
94+
// Warn about overwriting
95+
if( file_exists( $keyPath ) && $force )
96+
{
97+
$this->output->warning( "You are about to overwrite an existing key!" );
98+
$this->output->warning( "This will make any files encrypted with the old key unreadable." );
99+
100+
if( !$this->confirm( "Are you absolutely sure you want to continue?" ) )
101+
{
102+
$this->output->info( "Operation cancelled." );
103+
return 0;
104+
}
105+
}
106+
107+
// Create SecretManager and generate key
108+
$this->secretManager = new SecretManager();
109+
110+
try
111+
{
112+
$key = $this->secretManager->generateKey( $keyPath, $force );
113+
114+
$this->output->success( "Generated {$keyName} at: {$keyPath}" );
115+
116+
// Show the key if requested
117+
if( $show )
118+
{
119+
$this->output->newLine();
120+
$this->output->section( "Generated Key" );
121+
$this->output->write( $key );
122+
$this->output->newLine();
123+
$this->output->warning( "This key is shown only once. Store it securely!" );
124+
}
125+
126+
// Display instructions
127+
$this->output->newLine();
128+
$this->output->info( "Next steps:" );
129+
$this->output->write( "1. Add {$keyPath} to .gitignore (NEVER commit this file)" );
130+
$this->output->write( "2. Share this key securely with your team" );
131+
$this->output->write( "3. Use 'neuron secrets:edit" . ($env ? " --env={$env}" : "") . "' to add secrets" );
132+
133+
// Environment variable alternative
134+
$envVar = 'NEURON_' . strtoupper(
135+
str_replace( ['/', '.', '-'], '_', basename( $keyPath, '.key' ) )
136+
) . '_KEY';
137+
$this->output->newLine();
138+
$this->output->info( "Alternative: Set the key as an environment variable:" );
139+
$this->output->write( "export {$envVar}={$key}" );
140+
}
141+
catch( \Exception $e )
142+
{
143+
$this->output->error( "Error generating key: " . $e->getMessage() );
144+
145+
if( $this->input->hasOption( 'verbose' ) )
146+
{
147+
$this->output->write( $e->getTraceAsString() );
148+
}
149+
150+
return 1;
151+
}
152+
153+
return 0;
154+
}
155+
}

0 commit comments

Comments
 (0)