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 @@ namespace App\Livewire\Project\Database\Redis;
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 @@ use Livewire\Component;
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 @@ class General extends Component
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 @@ class General extends Component
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 @@ class General extends Component
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 @@ class StandaloneRedis extends BaseModel
{ {
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 @@ class StandaloneRedis extends BaseModel
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 @@ class StandaloneRedis extends BaseModel
); );
} }
private function get_redis_version() public function getRedisVersion()
{ {
$image_parts = explode(':', $this->image); $image_parts = explode(':', $this->image);
@@ -318,4 +311,32 @@ class StandaloneRedis extends BaseModel
{ {
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\Database\Migrations\Migration;
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 @@ class EncryptExistingRedisPasswords extends Migration
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