fix: redis database user and password

This commit is contained in:
Andras Bacsai 2024-10-21 12:13:42 +02:00
parent e8c7d7f972
commit bf7b0f9e06
7 changed files with 77 additions and 95 deletions

View file

@ -4,7 +4,6 @@
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
use App\Models\EnvironmentVariable;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Exception; use Exception;
@ -12,12 +11,21 @@
class General extends Component class General extends Component
{ {
protected $listeners = ['refresh']; protected $listeners = [
'envsUpdated' => 'refresh',
'refresh',
];
public Server $server; public Server $server;
public StandaloneRedis $database; public StandaloneRedis $database;
public ?string $redis_username;
public ?string $redis_password;
public string $redis_version;
public ?string $db_url = null; public ?string $db_url = null;
public ?string $db_url_public = null; public ?string $db_url_public = null;
@ -26,35 +34,33 @@ class General extends Component
'database.name' => 'required', 'database.name' => 'required',
'database.description' => 'nullable', 'database.description' => 'nullable',
'database.redis_conf' => 'nullable', 'database.redis_conf' => 'nullable',
'database.redis_username' => 'required',
'database.redis_password' => 'required',
'database.image' => 'required', 'database.image' => 'required',
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable', 'database.custom_docker_run_options' => 'nullable',
'redis_username' => 'required',
'redis_password' => 'required',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
'database.description' => 'Description', 'database.description' => 'Description',
'database.redis_conf' => 'Redis Configuration', 'database.redis_conf' => 'Redis Configuration',
'database.redis_username' => 'Redis Username',
'database.redis_password' => 'Redis Password',
'database.image' => 'Image', 'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options', 'database.custom_docker_run_options' => 'Custom Docker Options',
'redis_username' => 'Redis Username',
'redis_password' => 'Redis Password',
]; ];
public function mount() public function mount()
{ {
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server'); $this->server = data_get($this->database, 'destination.server');
$this->refreshView();
} }
public function instantSaveAdvanced() public function instantSaveAdvanced()
@ -79,31 +85,26 @@ public function submit()
try { try {
$this->validate(); $this->validate();
$redis_version = $this->get_redis_version(); if (version_compare($this->redis_version, '6.0', '>=')) {
$this->database->runtime_environment_variables()->updateOrCreate(
if (version_compare($redis_version, '6.0', '>=') && $this->database->isDirty('redis_username')) { ['key' => 'REDIS_USERNAME'],
$this->updateEnvironmentVariable('REDIS_USERNAME', $this->database->redis_username); ['value' => $this->redis_username, 'standalone_redis_id' => $this->database->id]
} );
if ($this->database->isDirty('redis_password')) {
$this->updateEnvironmentVariable('REDIS_PASSWORD', $this->database->redis_password);
} }
$this->database->runtime_environment_variables()->updateOrCreate(
['key' => 'REDIS_PASSWORD'],
['value' => $this->redis_password, 'standalone_redis_id' => $this->database->id]
);
$this->database->save(); $this->database->save();
$this->dispatch('success', 'Database updated.'); $this->dispatch('success', 'Database updated.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} finally {
$this->dispatch('refreshEnvs');
} }
} }
private function get_redis_version()
{
$image_parts = explode(':', $this->database->image);
return $image_parts[1] ?? '0.0';
}
public function instantSave() public function instantSave()
{ {
try { try {
@ -138,6 +139,14 @@ public function instantSave()
public function refresh(): void public function refresh(): void
{ {
$this->database->refresh(); $this->database->refresh();
$this->refreshView();
}
private function refreshView() {
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->redis_version = $this->database->getRedisVersion();
$this->redis_username = $this->database->redis_username;
$this->redis_password = $this->database->redis_password;
} }
public function render() public function render()
@ -147,28 +156,7 @@ public function render()
public function isSharedVariable($name) public function isSharedVariable($name)
{ {
return EnvironmentVariable::where('key', $name) return $this->database->runtime_environment_variables()->where('key', $name)->where('is_shared', true)->exists();
->where('standalone_redis_id', $this->database->id)
->where('is_shared', true)
->exists();
} }
private function updateEnvironmentVariable($key, $value)
{
$envVar = $this->database->runtime_environment_variables()
->where('key', $key)
->first();
if ($envVar) {
if (! $envVar->is_shared) {
$envVar->update(['value' => $value]);
}
} else {
$this->database->runtime_environment_variables()->create([
'key' => $key,
'value' => $value,
'is_shared' => false,
]);
}
}
} }

View file

@ -33,8 +33,6 @@ class Index extends Component
protected Server $server; protected Server $server;
public $timezones;
protected $rules = [ protected $rules = [
'settings.fqdn' => 'nullable', 'settings.fqdn' => 'nullable',
'settings.resale_license' => 'nullable', 'settings.resale_license' => 'nullable',

View file

@ -16,13 +16,6 @@ class StandaloneRedis extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'redis_password' => 'encrypted',
];
protected $attributes = [
'redis_username' => 'default',
];
protected static function booted() protected static function booted()
{ {
@ -219,7 +212,7 @@ protected function internalDbUrl(): Attribute
{ {
return new Attribute( return new Attribute(
get: function () { get: function () {
$redis_version = $this->get_redis_version(); $redis_version = $this->getRedisVersion();
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0"; return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0";
@ -232,7 +225,7 @@ protected function externalDbUrl(): Attribute
return new Attribute( return new Attribute(
get: function () { get: function () {
if ($this->is_public && $this->public_port) { if ($this->is_public && $this->public_port) {
$redis_version = $this->get_redis_version(); $redis_version = $this->getRedisVersion();
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
@ -243,7 +236,7 @@ protected function externalDbUrl(): Attribute
); );
} }
private function get_redis_version() public function getRedisVersion()
{ {
$image_parts = explode(':', $this->image); $image_parts = explode(':', $this->image);
@ -318,4 +311,32 @@ public function isBackupSolutionAvailable()
{ {
return false; return false;
} }
public function redisPassword(): Attribute
{
return new Attribute(
get: function () {
$password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first();
if (! $password) {
return null;
}
return $password->value;
},
);
}
public function redisUsername(): Attribute
{
return new Attribute(
get: function () {
$username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first();
if (! $username) {
return null;
}
return $username->value;
}
);
}
} }

View file

@ -50,7 +50,6 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
$database = new StandaloneRedis; $database = new StandaloneRedis;
$database->name = generate_database_name('redis'); $database->name = generate_database_name('redis');
$database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->redis_username = 'default';
$database->environment_id = $environment_id; $database->environment_id = $environment_id;
$database->destination_id = $destination->id; $database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass(); $database->destination_type = $destination->getMorphClass();
@ -68,7 +67,7 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => 'REDIS_USERNAME', 'key' => 'REDIS_USERNAME',
'value' => $database->redis_username, 'value' => 'default',
'standalone_redis_id' => $database->id, 'standalone_redis_id' => $database->id,
'is_shared' => false, 'is_shared' => false,
]); ]);

View file

@ -1,22 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('standalone_redis', function (Blueprint $table) {
$table->string('redis_username')->default('redis')->after('description');
});
}
public function down(): void
{
Schema::table('standalone_redis', function (Blueprint $table) {
$table->dropColumn('redis_username');
});
}
};

View file

@ -4,7 +4,7 @@
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class EncryptExistingRedisPasswords extends Migration class MoveRedisPasswordToEnvs extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -14,13 +14,15 @@ public function up(): void
try { try {
DB::table('standalone_redis')->chunkById(100, function ($redisInstances) { DB::table('standalone_redis')->chunkById(100, function ($redisInstances) {
foreach ($redisInstances as $redis) { foreach ($redisInstances as $redis) {
DB::table('standalone_redis') $redis->runtime_environment_variables()->firstOrCreate([
->where('id', $redis->id) 'key' => 'REDIS_PASSWORD',
->update(['redis_password' => Crypt::encryptString($redis->redis_password)]); 'value' => $redis->redis_password,
]);
} }
}); });
DB::statement('ALTER TABLE standalone_redis DROP COLUMN redis_password');
} catch (\Exception $e) { } catch (\Exception $e) {
echo 'Encrypting Redis passwords failed.'; echo 'Moving Redis passwords to envs failed.';
echo $e->getMessage(); echo $e->getMessage();
} }
} }

View file

@ -12,25 +12,21 @@
<x-forms.input label="Image" id="database.image" required helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" /> <x-forms.input label="Image" id="database.image" required helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@php
$redis_version = explode(':', $database->image)[1] ?? '0.0';
@endphp
@if (version_compare($redis_version, '6.0', '>=')) @if (version_compare($redis_version, '6.0', '>='))
<x-forms.input label="Username" id="database.redis_username" required <x-forms.input label="Username" id="redis_username" required
helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable. helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable.
<br><br> <br><br>
If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work. If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work.
<br><br> <br><br>
Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only." Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only."
:disabled="$this->isSharedVariable('REDIS_USERNAME')" /> :disabled="$this->isSharedVariable('REDIS_USERNAME')" />
@endif @endif
<x-forms.input label="Password" id="database.redis_password" type="password" required <x-forms.input label="Password" id="redis_password" type="password" required
helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable. helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable.
<br><br> <br><br>
If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work. If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work.
<br><br> <br><br>
Note: If the environment variable REDIS_PASSWORD is set as a shared variable (environment, project, or team-based), this input field will become read-only." Note: If the environment variable REDIS_PASSWORD is set as a shared variable (environment, project, or team-based), this input field will become read-only."
wire:model.defer="database.redis_password"
:disabled="$this->isSharedVariable('REDIS_PASSWORD')" /> :disabled="$this->isSharedVariable('REDIS_PASSWORD')" />
</div> </div>
<x-forms.input <x-forms.input