Issues (2963)

app/Console/Commands/KeyRotate.php (1 issue)

1
<?php
2
3
namespace App\Console\Commands;
4
5
use App\Console\LnmsCommand;
6
use Artisan;
7
use Illuminate\Contracts\Encryption\DecryptException;
8
use Illuminate\Encryption\Encrypter;
9
use Illuminate\Support\Str;
10
use Illuminate\Validation\Rule;
11
use LibreNMS\Util\EnvHelper;
12
use Symfony\Component\Console\Input\InputArgument;
13
14
class KeyRotate extends LnmsCommand
15
{
16
    /**
17
     * The name and signature of the console command.
18
     *
19
     * @var string
20
     */
21
    protected $signature = 'key:rotate';
22
23
    /**
24
     * @var Encrypter
25
     */
26
    private $decrypt;
27
    /**
28
     * @var Encrypter
29
     */
30
    private $encrypt;
31
32
    /**
33
     * Create a new command instance.
34
     *
35
     * @return void
36
     */
37
    public function __construct()
38
    {
39
        parent::__construct();
40
        $this->addArgument('old_key', InputArgument::OPTIONAL);
41
        $this->addOption('generate-new-key');
42
    }
43
44
    /**
45
     * Execute the console command.
46
     *
47
     * @return int
48
     */
49
    public function handle(): int
50
    {
51
        $new = config('app.key');
52
        $cipher = config('app.cipher');
53
54
        $this->validate([
55
            'generate-new-key' => [
56
                'exclude_unless:old_key,null',
57
                'boolean',
58
            ],
59
            'old_key' => [
60
                'exclude_if:generate-new-key,true',
61
                'required',
62
                'starts_with:base64:',
63
                Rule::notIn([$new]),
64
            ],
65
        ]);
66
67
        // check for cached config
68
        if (is_file(base_path('bootstrap/cache/config.php'))) {
69
            Artisan::call('config:clear'); // clear config cache
70
            $this->warn(trans('commands.key:rotate.cleared-cache'));
71
72
            return 0;
73
        }
74
75
        $old = $this->argument('old_key');
76
        if ($this->option('generate-new-key')) {
77
            $old = $new; // use key in env as existing key
78
            $new = 'base64:' . base64_encode(
79
                    Encrypter::generateKey($this->laravel['config']['app.cipher'])
80
                );
81
        }
82
83
        $this->line(trans('commands.key:rotate.old_key', ['key' => $old]));
84
        $this->line(trans('commands.key:rotate.new_key', ['key' => $new]));
85
        $this->error(trans('commands.key:rotate.backup_keys'));
86
        $this->newLine();
87
88
        // init encrypters
89
        $this->decrypt = $this->createEncrypter($old, $cipher);
90
        $this->encrypt = $this->createEncrypter($new, $cipher);
91
92
        $this->line(trans('commands.key:rotate.backups'));
93
        if (! $this->confirm(trans('commands.key:rotate.confirm'))) {
94
            return 1;
95
        }
96
97
        $success = $this->rekeyConfigData('validation.encryption.test');
98
99
        if (! $success) {
100
            $this->line(trans('commands.key:rotate.old_key', ['key' => $old]));
101
            $this->line(trans('commands.key:rotate.new_key', ['key' => $new]));
102
            $this->error(trans('commands.key:rotate.failed'));
103
104
            return 1;
105
        }
106
107
        $this->info(trans('commands.key:rotate.success'));
108
109
        if ($this->option('generate-new-key') && $this->confirm(trans('commands.key:rotate.save_key'))) {
110
            EnvHelper::writeEnv([
111
                'OLD_APP_KEY' => $old,
112
                'APP_KEY' => $new,
113
            ], ['OLD_APP_KEY', 'APP_KEY']);
114
        }
115
116
        return 0;
117
    }
118
119
    private function createEncrypter(string $key, string $cipher): Encrypter
120
    {
121
        return new Encrypter(base64_decode(Str::after($key, 'base64:')), $cipher);
122
    }
123
124
    private function rekeyConfigData(string $key): bool
125
    {
126
        if (! \LibreNMS\Config::has($key)) {
127
            return true;
128
        }
129
130
        try {
131
            $data = $this->decrypt->decryptString(\LibreNMS\Config::get($key));
132
            \LibreNMS\Config::persist($key, $this->encrypt->encryptString($data));
133
134
            return true;
135
        } catch (DecryptException $e) {
136
            try {
137
                $this->encrypt->decryptString(\LibreNMS\Config::get($key));
138
139
                return true; // already rotated
140
            } catch (DecryptException $e) {
141
                $this->warn(trans('commands.key:rotate.decrypt-failed', ['item' => $key]));
0 ignored issues
show
It seems like trans('commands.key:rota... array('item' => $key)) can also be of type array and array; however, parameter $string of Illuminate\Console\Command::warn() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
                $this->warn(/** @scrutinizer ignore-type */ trans('commands.key:rotate.decrypt-failed', ['item' => $key]));
Loading history...
142
143
                return false;
144
            }
145
        }
146
    }
147
}
148