Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"minimum-stability": "stable",
"require": {
"php": "^8.4",
"neuron-php/application": "0.8.*"
"neuron-php/application": "0.8.*",
"neuron-php/data": "0.7.*",
"symfony/yaml": "^6.4"
Comment thread
ljonesfl marked this conversation as resolved.
},
"require-dev": {
"phpunit/phpunit": "^9.0",
Expand Down
139 changes: 139 additions & 0 deletions src/Cli/Commands/Secrets/EditCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace Neuron\Cli\Commands\Secrets;

use Neuron\Cli\Commands\Command;
use Neuron\Data\Settings\SecretManager;

/**
* Edit encrypted secrets command
*
* Opens encrypted credentials in an editor for secure editing.
* Automatically re-encrypts the file when the editor is closed.
*
* Usage:
* neuron secrets:edit # Edit default secrets
* neuron secrets:edit --env=production # Edit production secrets
* neuron secrets:edit --editor="code --wait" # Use VS Code
*
* @package Neuron\Cli\Commands\Secrets
*/
class EditCommand extends Command
{
private SecretManager $secretManager;

/**
* @inheritDoc
*/
public function getName(): string
{
return 'secrets:edit';
}

/**
* @inheritDoc
*/
public function getDescription(): string
{
return 'Edit encrypted secrets file';
}

/**
* @inheritDoc
*/
public function configure(): void
{
$this->addOption( 'env', 'e', true, 'Environment to edit (default: base secrets)' );
$this->addOption( 'editor', null, true, 'Editor to use (default: vi)' );
$this->addOption( 'config', 'c', true, 'Config directory path (default: config)' );
$this->addOption( 'verbose', 'v', false, 'Verbose output' );
}

/**
* @inheritDoc
*/
public function execute(): int
{
$configPath = $this->input->getOption( 'config', 'config' );
$env = $this->input->getOption( 'env' );
$editor = $this->input->getOption( 'editor' ) ?? $_ENV['EDITOR'] ?? 'vi';

// Determine paths based on environment
if( $env )
{
$credentialsPath = $configPath . '/secrets/' . $env . '.yml.enc';
$keyPath = $configPath . '/secrets/' . $env . '.key';
$this->output->info( "Editing {$env} environment secrets..." );
}
else
{
$credentialsPath = $configPath . '/secrets.yml.enc';
$keyPath = $configPath . '/master.key';
$this->output->info( "Editing base secrets..." );
}

// Create SecretManager
$this->secretManager = new SecretManager();

try
{
// Ensure key exists
if( !file_exists( $keyPath ) )
{
$this->output->warning( "Key file not found at: {$keyPath}" );
$this->output->info( "Generating new encryption key..." );

// Ensure directory exists
$dir = dirname( $keyPath );
if( !is_dir( $dir ) )
{
if( !mkdir( $dir, 0755, true ) )
{
$this->output->error( "Failed to create directory: {$dir}" );
return 1;
}
}

$key = $this->secretManager->generateKey( $keyPath );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
$this->output->success( "Generated new key at: {$keyPath}" );
$this->output->warning( "IMPORTANT: Add {$keyPath} to .gitignore!" );
}
Comment thread
cursor[bot] marked this conversation as resolved.

// Edit the secrets
$result = $this->secretManager->edit( $credentialsPath, $keyPath, $editor );

if( $result )
{
$this->output->success( "Secrets saved to: {$credentialsPath}" );

// First time setup reminder
if( !$env && !file_exists( $configPath . '/.gitignore' ) )
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
{
$this->output->newLine();
$this->output->warning( "Remember to:" );
$this->output->write( "1. Add {$keyPath} to .gitignore" );
$this->output->write( "2. Commit {$credentialsPath} to version control" );
$this->output->write( "3. Share {$keyPath} securely with your team" );
}
}
else
{
$this->output->error( "Failed to save secrets" );
return 1;
}
}
catch( \Exception $e )
{
$this->output->error( "Error editing secrets: " . $e->getMessage() );

if( $this->input->hasOption( 'verbose' ) )
{
$this->output->write( $e->getTraceAsString() );
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return 1;
}

return 0;
}
}
156 changes: 156 additions & 0 deletions src/Cli/Commands/Secrets/Key/GenerateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

namespace Neuron\Cli\Commands\Secrets\Key;

use Neuron\Cli\Commands\Command;
use Neuron\Data\Settings\SecretManager;

/**
* Generate encryption key command
*
* Generates a new encryption key for securing credentials.
* Keys are cryptographically secure random values.
*
* Usage:
* neuron secrets:key:generate # Generate master key
* neuron secrets:key:generate --env=production # Generate production key
* neuron secrets:key:generate --force # Overwrite existing key
*
* @package Neuron\Cli\Commands\Secrets\Key
*/
class GenerateCommand extends Command
{
private SecretManager $secretManager;

/**
* @inheritDoc
*/
public function getName(): string
{
return 'secrets:key:generate';
}

/**
* @inheritDoc
*/
public function getDescription(): string
{
return 'Generate a new encryption key for secrets';
}

/**
* @inheritDoc
*/
public function configure(): void
{
$this->addOption( 'env', 'e', true, 'Environment for the key (default: master key)' );
$this->addOption( 'config', 'c', true, 'Config directory path (default: config)' );
$this->addOption( 'force', 'f', false, 'Overwrite existing key file' );
$this->addOption( 'show', 's', false, 'Display the generated key' );
$this->addOption( 'verbose', 'v', false, 'Verbose output' );
}

/**
* @inheritDoc
*/
public function execute(): int
{
$configPath = $this->input->getOption( 'config', 'config' );
$env = $this->input->getOption( 'env' );
$force = $this->input->hasOption( 'force' );
$show = $this->input->hasOption( 'show' );

// Determine key path based on environment
if( $env )
{
$keyPath = $configPath . '/secrets/' . $env . '.key';
$keyName = $env . ' environment key';

// Ensure directory exists
$dir = dirname( $keyPath );
if( !is_dir( $dir ) )
{
if( !mkdir( $dir, 0755, true ) )
{
$this->output->error( "Failed to create directory: {$dir}" );
return 1;
}
}
}
else
{
$keyPath = $configPath . '/master.key';
$keyName = 'master key';
}
Comment thread
cursor[bot] marked this conversation as resolved.

// Check if key already exists
if( file_exists( $keyPath ) && !$force )
{
$this->output->error( "Key file already exists: {$keyPath}" );
$this->output->info( "Use --force to overwrite the existing key." );
$this->output->warning( "WARNING: Overwriting will make existing encrypted files unreadable!" );
return 1;
}

// Warn about overwriting
if( file_exists( $keyPath ) && $force )
{
$this->output->warning( "You are about to overwrite an existing key!" );
$this->output->warning( "This will make any files encrypted with the old key unreadable." );

if( !$this->confirm( "Are you absolutely sure you want to continue?" ) )
{
$this->output->info( "Operation cancelled." );
return 0;
}
}

// Create SecretManager and generate key
$this->secretManager = new SecretManager();

try
{
$key = $this->secretManager->generateKey( $keyPath, $force );

$this->output->success( "Generated {$keyName} at: {$keyPath}" );

// Show the key if requested
if( $show )
{
$this->output->newLine();
$this->output->section( "Generated Key" );
$this->output->write( $key );
$this->output->newLine();
$this->output->warning( "This key is shown only once. Store it securely!" );
}

// Display instructions
$this->output->newLine();
$this->output->info( "Next steps:" );
$this->output->write( "1. Add {$keyPath} to .gitignore (NEVER commit this file)" );
$this->output->write( "2. Share this key securely with your team" );
$this->output->write( "3. Use 'neuron secrets:edit" . ($env ? " --env={$env}" : "") . "' to add secrets" );

// Environment variable alternative
$envVar = 'NEURON_' . strtoupper(
str_replace( ['/', '.', '-'], '_', basename( $keyPath, '.key' ) )
) . '_KEY';
$this->output->newLine();
$this->output->info( "Alternative: Set the key as an environment variable:" );
$this->output->write( "export {$envVar}={$key}" );
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}
catch( \Exception $e )
{
$this->output->error( "Error generating key: " . $e->getMessage() );

if( $this->input->hasOption( 'verbose' ) )
{
$this->output->write( $e->getTraceAsString() );
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return 1;
}

return 0;
}
}
Loading
Loading