addArgument('old_key', InputArgument::OPTIONAL); $this->addOption('generate-new-key'); $this->addOption('forgot-key'); } /** * Execute the console command. * * @return int */ public function handle(): int { $new = config('app.key'); $cipher = config('app.cipher'); $this->validate([ 'generate-new-key' => [ 'exclude_unless:old_key,null', 'boolean', ], 'forgot-key' => [ 'exclude_unless:old_key,null', 'boolean', ], 'old_key' => [ 'exclude_if:generate-new-key,true', 'exclude_if:forgot-key,true', 'required', 'starts_with:base64:', Rule::notIn([$new]), ], ]); // check for cached config if (is_file(base_path('bootstrap/cache/config.php'))) { Artisan::call('config:clear'); // clear config cache $this->warn(trans('commands.key:rotate.cleared-cache')); return 0; } if ($this->option('forgot-key')) { $this->line(trans('commands.key:rotate.current_key', ['key' => $new])); $this->error(trans('commands.key:rotate.backup_key')); $this->newLine(); $this->error(trans('commands.key:rotate.destroy')); if ($this->confirm(trans('commands.key:rotate.destroy_confirm'))) { \App\Models\Config::where('config_name', 'validation.encryption.test')->delete(); return 0; } return 1; } $old = $this->argument('old_key'); if ($this->option('generate-new-key')) { $old = $new; // use key in env as existing key $new = 'base64:' . base64_encode( Encrypter::generateKey($this->laravel['config']['app.cipher']) ); } $this->line(trans('commands.key:rotate.old_key', ['key' => $old])); $this->line(trans('commands.key:rotate.new_key', ['key' => $new])); $this->error(trans('commands.key:rotate.backup_keys')); $this->newLine(); // init encrypters $this->decrypt = $this->createEncrypter($old, $cipher); $this->encrypt = $this->createEncrypter($new, $cipher); $this->line(trans('commands.key:rotate.backups')); if (! $this->confirm(trans('commands.key:rotate.confirm'))) { return 1; } $success = $this->rekeyConfigData('validation.encryption.test'); if (! $success) { $this->line(trans('commands.key:rotate.old_key', ['key' => $old])); $this->line(trans('commands.key:rotate.new_key', ['key' => $new])); $this->error(trans('commands.key:rotate.failed')); return 1; } $this->info(trans('commands.key:rotate.success')); if ($this->option('generate-new-key') && $this->confirm(trans('commands.key:rotate.save_key'))) { EnvHelper::writeEnv([ 'OLD_APP_KEY' => $old, 'APP_KEY' => $new, ], ['OLD_APP_KEY', 'APP_KEY']); } return 0; } private function createEncrypter(string $key, string $cipher): Encrypter { return new Encrypter(base64_decode(Str::after($key, 'base64:')), $cipher); } private function rekeyConfigData(string $key): bool { if (! \LibreNMS\Config::has($key)) { return true; } try { $data = $this->decrypt->decryptString(\LibreNMS\Config::get($key)); \LibreNMS\Config::persist($key, $this->encrypt->encryptString($data)); return true; } catch (DecryptException $e) { try { $this->encrypt->decryptString(\LibreNMS\Config::get($key)); return true; // already rotated } catch (DecryptException $e) { $this->warn(trans('commands.key:rotate.decrypt-failed', ['item' => $key])); return false; } } } }