From 3f582a1ea4f9f2e6dbf3d1c9b0d8aa3a1947d61e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:03:13 +0100 Subject: [PATCH 001/180] feat(migration): Add `ssl_certificates` table and model --- app/Models/SslCertificate.php | 32 +++++++++++++++++++ ...7_153741_create_ssl_certificates_table.php | 29 +++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 app/Models/SslCertificate.php create mode 100644 database/migrations/2025_01_27_153741_create_ssl_certificates_table.php diff --git a/app/Models/SslCertificate.php b/app/Models/SslCertificate.php new file mode 100644 index 000000000..5a1767ab5 --- /dev/null +++ b/app/Models/SslCertificate.php @@ -0,0 +1,32 @@ + 'encrypted', + 'ssl_private_key' => 'encrypted', + 'valid_until' => 'datetime', + ]; + + public function resource() + { + return $this->morphTo(); + } +} diff --git a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php new file mode 100644 index 000000000..f10306ed7 --- /dev/null +++ b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php @@ -0,0 +1,29 @@ +id(); + $table->text('ssl_certificate')->nullable(); + $table->text('ssl_private_key')->nullable(); + $table->string('resource_type')->nullable(); + $table->unsignedBigInteger('resource_id')->nullable(); + $table->enum('certificate_type', ['internal', 'external'])->default('internal'); + $table->timestamp('valid_until')->nullable(); + $table->timestamps(); + + $table->index(['resource_type', 'resource_id']); + }); + } + + public function down() + { + Schema::dropIfExists('ssl_certificates'); + } +}; From 214a7a089e29c84a4429fd15bde272202525b812 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:04:27 +0100 Subject: [PATCH 002/180] feat(migration): Add ssl setting to `standalone_postgresqls` table --- ...d_ssl_fields_to_standalone_postgresqls.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php diff --git a/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php b/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php new file mode 100644 index 000000000..409d6bb36 --- /dev/null +++ b/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php @@ -0,0 +1,30 @@ +boolean('enable_ssl')->default(true); + $table->string('ssl_mode')->nullable()->default('verify-full'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('standalone_postgresqls', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + $table->dropColumn('ssl_mode'); + }); + } +}; From 875d1d49bbff862d6a05cbbc5d6de52c1e360205 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:25:05 +0100 Subject: [PATCH 003/180] feat(ui): Add ssl settings to Postgres ui --- .../Project/Database/Postgresql/General.php | 16 +++ .../database/postgresql/general.blade.php | 125 +++++++++++------- 2 files changed, 90 insertions(+), 51 deletions(-) diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 88dd5c1a8..25f3f4f30 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -48,6 +48,8 @@ class General extends Component 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', ]; protected $validationAttributes = [ @@ -65,6 +67,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', 'database.custom_docker_run_options' => 'Custom Docker Run Options', + 'database.enable_ssl' => 'Enable SSL', + 'database.ssl_mode' => 'SSL Mode', ]; public function mount() @@ -91,6 +95,18 @@ class General extends Component } } + public function instantSaveSSL() + { + try { + $this->database->enable_ssl = $this->database->enable_ssl; + $this->database->ssl_mode = $this->database->ssl_mode; + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() { try { diff --git a/resources/views/livewire/project/database/postgresql/general.blade.php b/resources/views/livewire/project/database/postgresql/general.blade.php index 5abde2b01..ec29584fe 100644 --- a/resources/views/livewire/project/database/postgresql/general.blade.php +++ b/resources/views/livewire/project/database/postgresql/general.blade.php @@ -73,58 +73,81 @@ type="password" readonly wire:model="db_url_public" /> @endif -
-
-
-
-

Proxy

- -
- @if (data_get($database, 'is_public')) - - Proxy Logs - - - - Logs - - @endif -
- -
- -
- - -

Advanced

-
- -
-
-
-

Initialization scripts

- -
- - - - Save - - -
-
- @forelse(data_get($database,'init_scripts', []) as $script) - - @empty -
No initialization scripts found.
- @endforelse +

SSL Configuration

+
+ + @if($database->enable_ssl) + + + + + + + + @endif +
+
+ +
+
+
+

Proxy

+ +
+ @if (data_get($database, 'is_public')) + + Proxy Logs + + + + Logs + + @endif +
+
+ + +
+
+ +
+ +
+ + +
+

Advanced

+
+ +
+ +
+
+

Initialization scripts

+ +
+ + + + Save + + +
+
+
+ @forelse(data_get($database,'init_scripts', []) as $script) + + @empty +
No initialization scripts found.
+ @endforelse +
-
From 92a4b5fce712cd714a41418038af55d6275c4352 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:28:42 +0100 Subject: [PATCH 004/180] feat(db): add ssl mode to Postgres URLs --- app/Models/StandalonePostgresql.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index dd92ae7c9..7e8a071f3 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -219,7 +219,14 @@ class StandalonePostgresql extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", + get: function () { + $url = "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}"; + if ($this->enable_ssl) { + $url .= "?sslmode={$this->ssl_mode}"; + } + + return $url; + }, ); } @@ -228,7 +235,12 @@ class StandalonePostgresql extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; + $url = "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; + if ($this->enable_ssl) { + $url .= "?sslmode={$this->ssl_mode}"; + } + + return $url; } return null; From b1249042453153dfa5e95e16bf226133313666e4 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:30:45 +0100 Subject: [PATCH 005/180] feat(db): setup ssl during Postgres start - create ssl directory - create a new certificate if one does not already exist - add the certificates to the file store so that they are created as file mounts - add SSL startup commands --- app/Actions/Database/StartPostgresql.php | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 035849340..518639935 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandalonePostgresql; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -18,6 +20,8 @@ class StartPostgresql public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandalonePostgresql $database) { $this->database = $database; @@ -31,8 +35,27 @@ class StartPostgresql "echo 'Starting database.'", "mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/", + "mkdir -p $this->configuration_dir/ssl", ]; + if ($this->database->enable_ssl) { + $this->commands[] = "echo 'Setting up SSL certificate.'"; + $this->ssl_certificate = SslCertificate::where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->where('certificate_type', 'internal') + ->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'Generating new SSL certificate.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + certificateType: 'internal', + ); + $this->addSslFilesToFileStorage(); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -107,6 +130,17 @@ class StartPostgresql ]; } } + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['command'] = [ + 'postgres', + '-c', + 'ssl=off', // temp for dev + '-c', + 'ssl_cert_file=/etc/postgresql/ssl/internal.crt', + '-c', + 'ssl_key_file=/etc/postgresql/ssl/internal.key', + ]; + } if (filled($this->database->postgres_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', @@ -233,4 +267,27 @@ class StartPostgresql $content_base64 = base64_encode($content); $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null"; } + + private function addSslFilesToFileStorage() + { + if (! $this->ssl_certificate) { + return; + } + + $this->database->fileStorages()->create([ + 'fs_path' => $this->configuration_dir.'/ssl/internal.crt', + 'mount_path' => '/etc/postgresql/ssl/internal.crt', + 'content' => $this->ssl_certificate->ssl_certificate, + 'is_directory' => false, + 'chmod' => '644', + ]); + + $this->database->fileStorages()->create([ + 'fs_path' => $this->configuration_dir.'/ssl/internal.key', + 'mount_path' => '/etc/postgresql/ssl/internal.key', + 'content' => $this->ssl_certificate->ssl_private_key, + 'is_directory' => false, + 'chmod' => '600', + ]); + } } From 9f9349925a13110b8b0bd936011a425f7d846c71 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:58:48 +0100 Subject: [PATCH 006/180] fix(ssl): permission of ssl crt and key inside the container --- app/Actions/Database/StartPostgresql.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 518639935..df516bbda 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -134,7 +134,7 @@ class StartPostgresql $docker_compose['services'][$container_name]['command'] = [ 'postgres', '-c', - 'ssl=off', // temp for dev + 'ssl=on', '-c', 'ssl_cert_file=/etc/postgresql/ssl/internal.crt', '-c', @@ -166,6 +166,9 @@ class StartPostgresql $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + if ($this->database->enable_ssl) { + $this->commands[] = executeInDocker($this->database->uuid, "chown {$this->database->postgres_user}:{$this->database->postgres_user} /etc/postgresql/ssl/internal.key /etc/postgresql/ssl/internal.crt"); + } $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); From edddbc8536efa799a6ca7c6c1679e42838ac9acd Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:54:00 +0100 Subject: [PATCH 007/180] feat(migration): encrypt local file volumes content and paths --- ...5223_encrypt_local_file_volumes_fields.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php diff --git a/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php b/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php new file mode 100644 index 000000000..f29cdaa23 --- /dev/null +++ b/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php @@ -0,0 +1,63 @@ +text('mount_path')->nullable()->change(); + }); + + if (DB::table('local_file_volumes')->exists()) { + $volumes = DB::table('local_file_volumes')->get(); + foreach ($volumes as $volume) { + try { + DB::table('local_file_volumes')->where('id', $volume->id)->update([ + 'fs_path' => $volume->fs_path ? Crypt::encryptString($volume->fs_path) : null, + 'mount_path' => $volume->mount_path ? Crypt::encryptString($volume->mount_path) : null, + 'content' => $volume->content ? Crypt::encryptString($volume->content) : null, + ]); + } catch (\Exception $e) { + Log::error('Error encrypting local file volume fields: '.$e->getMessage()); + } + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('local_file_volumes', function (Blueprint $table) { + $table->string('fs_path')->change(); + $table->string('mount_path')->nullable()->change(); + $table->longText('content')->nullable()->change(); + }); + + if (DB::table('local_file_volumes')->exists()) { + $volumes = DB::table('local_file_volumes')->get(); + foreach ($volumes as $volume) { + try { + DB::table('local_file_volumes')->where('id', $volume->id)->update([ + 'fs_path' => $volume->fs_path ? Crypt::decryptString($volume->fs_path) : null, + 'mount_path' => $volume->mount_path ? Crypt::decryptString($volume->mount_path) : null, + 'content' => $volume->content ? Crypt::decryptString($volume->content) : null, + ]); + } catch (\Exception $e) { + Log::error('Error decrypting local file volume fields: '.$e->getMessage()); + } + } + } + } +}; From 429453af36ddf5e093907589077ee6b3a5b47573 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:16:27 +0100 Subject: [PATCH 008/180] fix(ui): make sure file mounts do not showing the encrypted values --- app/Models/LocalFileVolume.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index 2c223be77..c1345c0fb 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -7,6 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class LocalFileVolume extends BaseModel { + protected $casts = [ + 'fs_path' => 'encrypted', + 'mount_path' => 'encrypted', + 'content' => 'encrypted', + ]; + use HasFactory; protected $guarded = []; From 2ac9147532c7cfd3435905415dcc598e83b93b08 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:16:52 +0100 Subject: [PATCH 009/180] chore(migration): remove unused columns --- app/Models/SslCertificate.php | 5 ----- .../2025_01_27_153741_create_ssl_certificates_table.php | 1 - 2 files changed, 6 deletions(-) diff --git a/app/Models/SslCertificate.php b/app/Models/SslCertificate.php index 5a1767ab5..f414e5eca 100644 --- a/app/Models/SslCertificate.php +++ b/app/Models/SslCertificate.php @@ -9,13 +9,8 @@ class SslCertificate extends Model protected $fillable = [ 'ssl_certificate', 'ssl_private_key', - 'cert_file_path', - 'key_file_path', 'resource_type', 'resource_id', - 'mount_path', - 'host_path', - 'certificate_type', 'valid_until', ]; diff --git a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php index f10306ed7..1d5c6aa1a 100644 --- a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php +++ b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php @@ -14,7 +14,6 @@ return new class extends Migration $table->text('ssl_private_key')->nullable(); $table->string('resource_type')->nullable(); $table->unsignedBigInteger('resource_id')->nullable(); - $table->enum('certificate_type', ['internal', 'external'])->default('internal'); $table->timestamp('valid_until')->nullable(); $table->timestamps(); From 3632f29af8d63f17218e6131d0476bfeec1fcded Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:17:12 +0100 Subject: [PATCH 010/180] feat(ssl): ssl generation helper --- app/Helpers/SslHelper.php | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 app/Helpers/SslHelper.php diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php new file mode 100644 index 000000000..7027d896c --- /dev/null +++ b/app/Helpers/SslHelper.php @@ -0,0 +1,91 @@ +addYears(self::DEFAULT_VALIDITY_YEARS); + $organizationName ??= self::DEFAULT_ORG_NAME; + + try { + $privateKey = openssl_pkey_new([ + 'private_key_bits' => self::DEFAULT_KEY_BITS, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + 'encrypt_key' => true, + ]); + + if ($privateKey === false) { + throw new \RuntimeException('Failed to generate private key: '.openssl_error_string()); + } + + if (! openssl_pkey_export($privateKey, $privateKeyStr)) { + throw new \RuntimeException('Failed to export private key: '.openssl_error_string()); + } + + $dn = [ + 'commonName' => $commonName, + 'organizationName' => $organizationName, + ]; + + $csr = openssl_csr_new($dn, $privateKey, [ + 'digest_alg' => self::DEFAULT_DIGEST_ALG, + 'config' => null, + 'encrypt_key' => true, + ]); + + if ($csr === false) { + throw new \RuntimeException('Failed to generate CSR: '.openssl_error_string()); + } + + $validityDays = max(1, Carbon::now()->diffInDays($validUntil)); + + $certificate = openssl_csr_sign( + $csr, + null, + $privateKey, + $validityDays, + [ + 'digest_alg' => self::DEFAULT_DIGEST_ALG, + 'config' => null, + ], + random_int(PHP_INT_MIN, PHP_INT_MAX) + ); + + if ($certificate === false) { + throw new \RuntimeException('Failed to sign certificate: '.openssl_error_string()); + } + + if (! openssl_x509_export($certificate, $certificateStr)) { + throw new \RuntimeException('Failed to export certificate: '.openssl_error_string()); + } + + return SslCertificate::create([ + 'ssl_certificate' => $certificateStr, + 'ssl_private_key' => $privateKeyStr, + 'resource_type' => $resourceType, + 'resource_id' => $resourceId, + 'valid_until' => $validUntil, + ]); + } catch (\Throwable $e) { + throw new \RuntimeException('SSL Certificate generation failed: '.$e->getMessage(), 0, $e); + } + } +} From 546001890c8dd1bf46bafa987c134a320c8b5b74 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:37:12 +0100 Subject: [PATCH 011/180] chore(ssl): improve code in ssl helper --- app/Helpers/SslHelper.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php index 7027d896c..9f3a56f86 100644 --- a/app/Helpers/SslHelper.php +++ b/app/Helpers/SslHelper.php @@ -7,10 +7,6 @@ use Carbon\Carbon; class SslHelper { - private const DEFAULT_KEY_BITS = 4096; - - private const DEFAULT_DIGEST_ALG = 'sha256'; - private const DEFAULT_VALIDITY_YEARS = 10; private const DEFAULT_ORG_NAME = 'Coolify'; @@ -27,9 +23,9 @@ class SslHelper try { $privateKey = openssl_pkey_new([ - 'private_key_bits' => self::DEFAULT_KEY_BITS, 'private_key_type' => OPENSSL_KEYTYPE_RSA, - 'encrypt_key' => true, + 'private_key_bits' => 4096, + 'encrypt_key' => false, ]); if ($privateKey === false) { @@ -46,9 +42,9 @@ class SslHelper ]; $csr = openssl_csr_new($dn, $privateKey, [ - 'digest_alg' => self::DEFAULT_DIGEST_ALG, + 'digest_alg' => 'sha512', 'config' => null, - 'encrypt_key' => true, + 'encrypt_key' => false, ]); if ($csr === false) { @@ -63,7 +59,7 @@ class SslHelper $privateKey, $validityDays, [ - 'digest_alg' => self::DEFAULT_DIGEST_ALG, + 'digest_alg' => 'sha512', 'config' => null, ], random_int(PHP_INT_MIN, PHP_INT_MAX) From b53d3d07d97470945f8db92caf7d3cbd6bc72190 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:09:37 +0100 Subject: [PATCH 012/180] fix(ssl): make default ssl mode require not verify-full as it does not need a ca cert --- ...25_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php b/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php index 409d6bb36..3b0ce8aa4 100644 --- a/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php +++ b/database/migrations/2025_01_27_102616_add_ssl_fields_to_standalone_postgresqls.php @@ -13,7 +13,7 @@ return new class extends Migration { Schema::table('standalone_postgresqls', function (Blueprint $table) { $table->boolean('enable_ssl')->default(true); - $table->string('ssl_mode')->nullable()->default('verify-full'); + $table->string('ssl_mode')->nullable()->default('require'); }); } From d280f11b6bddb85f595b0ec5b56ae54c2a71199d Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:21:18 +0100 Subject: [PATCH 013/180] feat(ssl): migrate to `ECC`certificates using `secp521r1` - Replace RSA 4096 with ECDSA secp521r1 for stronger security (256-bit vs 112-bit) - Faster certificate generation (3-4x speed improvement) - 75% smaller key sizes (0.8KB vs 3.2KB) improves storage and transmission --- app/Helpers/SslHelper.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php index 9f3a56f86..b6632da52 100644 --- a/app/Helpers/SslHelper.php +++ b/app/Helpers/SslHelper.php @@ -23,9 +23,8 @@ class SslHelper try { $privateKey = openssl_pkey_new([ - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - 'private_key_bits' => 4096, - 'encrypt_key' => false, + 'private_key_type' => OPENSSL_KEYTYPE_EC, + 'curve_name' => 'secp521r1', ]); if ($privateKey === false) { From 34188450ebad34a21af0e6afe97bea01994e766a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:52:21 +0100 Subject: [PATCH 014/180] feat(ssl): improve SSL helper - improve security by making certificates valid for only 90 days instead of 10 years - add SubjectAltName - remove unnecessary parameters - use carbon immutable to make sure expiration date stays the same --- app/Helpers/SslHelper.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php index b6632da52..3a8efd540 100644 --- a/app/Helpers/SslHelper.php +++ b/app/Helpers/SslHelper.php @@ -3,23 +3,20 @@ namespace App\Helpers; use App\Models\SslCertificate; -use Carbon\Carbon; +use Carbon\CarbonImmutable; class SslHelper { - private const DEFAULT_VALIDITY_YEARS = 10; - - private const DEFAULT_ORG_NAME = 'Coolify'; + private const DEFAULT_ORGANIZATION_NAME = 'Coolify'; public static function generateSslCertificate( + string $commonName, + array $additionalSans, string $resourceType, int $resourceId, - string $commonName, - ?Carbon $validUntil = null, - ?string $organizationName = null + ?string $organizationName = null, ): SslCertificate { - $validUntil ??= Carbon::now()->addYears(self::DEFAULT_VALIDITY_YEARS); - $organizationName ??= self::DEFAULT_ORG_NAME; + $organizationName ??= self::DEFAULT_ORGANIZATION_NAME; try { $privateKey = openssl_pkey_new([ @@ -38,6 +35,7 @@ class SslHelper $dn = [ 'commonName' => $commonName, 'organizationName' => $organizationName, + 'subjectAltName' => implode(', ', array_merge(["DNS:$commonName"], $additionalSans)), ]; $csr = openssl_csr_new($dn, $privateKey, [ @@ -50,13 +48,11 @@ class SslHelper throw new \RuntimeException('Failed to generate CSR: '.openssl_error_string()); } - $validityDays = max(1, Carbon::now()->diffInDays($validUntil)); - $certificate = openssl_csr_sign( $csr, null, $privateKey, - $validityDays, + 90, [ 'digest_alg' => 'sha512', 'config' => null, @@ -77,7 +73,7 @@ class SslHelper 'ssl_private_key' => $privateKeyStr, 'resource_type' => $resourceType, 'resource_id' => $resourceId, - 'valid_until' => $validUntil, + 'valid_until' => CarbonImmutable::now()->addDays(90), ]); } catch (\Throwable $e) { throw new \RuntimeException('SSL Certificate generation failed: '.$e->getMessage(), 0, $e); From 22c26cdf786ea110979dd6a99cfdcef229fed30c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:52:49 +0100 Subject: [PATCH 015/180] chore(migration): ssl cert and key should not be nullable --- .../2025_01_27_153741_create_ssl_certificates_table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php index 1d5c6aa1a..d7b02178d 100644 --- a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php +++ b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php @@ -10,8 +10,8 @@ return new class extends Migration { Schema::create('ssl_certificates', function (Blueprint $table) { $table->id(); - $table->text('ssl_certificate')->nullable(); - $table->text('ssl_private_key')->nullable(); + $table->text('ssl_certificate'); + $table->text('ssl_private_key'); $table->string('resource_type')->nullable(); $table->unsignedBigInteger('resource_id')->nullable(); $table->timestamp('valid_until')->nullable(); From e1245f49f1161c5e4857137ec55a14385938b639 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:57:30 +0100 Subject: [PATCH 016/180] fix(ui): select component should not always uses title case --- app/View/Components/Forms/Select.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index dd5ba66b7..86ae866c8 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -4,7 +4,6 @@ namespace App\View\Components\Forms; use Closure; use Illuminate\Contracts\View\View; -use Illuminate\Support\Str; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; @@ -36,8 +35,6 @@ class Select extends Component $this->name = $this->id; } - $this->label = Str::title($this->label); - return view('components.forms.select'); } } From 90a93ce7e0b80f3ff5d973597294130ab4c27669 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:23:00 +0100 Subject: [PATCH 017/180] feat(ssl): add a Coolify CA Certificate to all servers --- database/seeders/CaSslCertSeeder.php | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 database/seeders/CaSslCertSeeder.php diff --git a/database/seeders/CaSslCertSeeder.php b/database/seeders/CaSslCertSeeder.php new file mode 100644 index 000000000..75b78da21 --- /dev/null +++ b/database/seeders/CaSslCertSeeder.php @@ -0,0 +1,40 @@ +id)->first(); + + if (! $existingCert) { + $serverCert = SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $server->id, + validityDays: 15 * 365 + ); + + $serverCertPath = config('constants.coolify.base_config_path').'/ca/'; + + $commands = collect([ + "mkdir -p $serverCertPath", + "chown -R 9999:root $serverCertPath", + "chmod -R 700 $serverCertPath", + "echo '{$serverCert->ssl_certificate}' > $serverCertPath/ca.crt", + "chmod 644 $serverCertPath/ca.crt", + ]); + + remote_process($commands, $server); + } + } + }); + } +} From 503e1ffb6781b30597055a548d208a94f02b65d9 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:23:59 +0100 Subject: [PATCH 018/180] feat(seeder): Call CA SSL seeder in prod and dev --- database/seeders/DatabaseSeeder.php | 1 + database/seeders/ProductionSeeder.php | 1 + 2 files changed, 2 insertions(+) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6e66c64f4..e0e7a3ba5 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -28,6 +28,7 @@ class DatabaseSeeder extends Seeder OauthSettingSeeder::class, DisableTwoStepConfirmationSeeder::class, SentinelSeeder::class, + CaSslCertSeeder::class, ]); } } diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index bbb9fcb75..058d4c8e4 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -193,5 +193,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->call(PopulateSshKeysDirectorySeeder::class); $this->call(SentinelSeeder::class); $this->call(RootUserSeeder::class); + $this->call(CaSslCertSeeder::class); } } From 0915303769230a4ee1547517176ab20e03239d55 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:27:29 +0100 Subject: [PATCH 019/180] feat(ssl): Add Coolify CA Certificate when adding a new server --- app/Actions/Server/InstallDocker.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index cbcb20368..775de5ead 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -2,7 +2,9 @@ namespace App\Actions\Server; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneDocker; use Lorisleiva\Actions\Concerns\AsAction; @@ -17,6 +19,25 @@ class InstallDocker if (! $supported_os_type) { throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); } + + if (! SslCertificate::where('server_id', $server->id)->exists()) { + $serverCert = SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $server->id, + validityDays: 15 * 365 + ); + $serverCertPath = config('constants.coolify.base_config_path').'/ca/'; + + $commands = collect([ + "mkdir -p $serverCertPath", + "chown -R 9999:root $serverCertPath", + "chmod -R 700 $serverCertPath", + "echo '{$serverCert->ssl_certificate}' > $serverCertPath/ca.crt", + "chmod 644 $serverCertPath/ca.crt", + ]); + remote_process($commands, $server); + } + $config = base64_encode('{ "log-driver": "json-file", "log-opts": { From 34216af497c21e25d79edf770e396097e58a04a9 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:35:34 +0100 Subject: [PATCH 020/180] fix(db): SSL certificates table and model - server_id is a foreign id - server_id must be unique as each server can only have 1 CA cert - resource_id must be unique as each resource can only have 1 SSL cert --- app/Models/SslCertificate.php | 6 ++++++ .../2025_01_27_153741_create_ssl_certificates_table.php | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Models/SslCertificate.php b/app/Models/SslCertificate.php index f414e5eca..d16860b22 100644 --- a/app/Models/SslCertificate.php +++ b/app/Models/SslCertificate.php @@ -11,6 +11,7 @@ class SslCertificate extends Model 'ssl_private_key', 'resource_type', 'resource_id', + 'server_id', 'valid_until', ]; @@ -24,4 +25,9 @@ class SslCertificate extends Model { return $this->morphTo(); } + + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php index d7b02178d..702f1f534 100644 --- a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php +++ b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php @@ -14,10 +14,12 @@ return new class extends Migration $table->text('ssl_private_key'); $table->string('resource_type')->nullable(); $table->unsignedBigInteger('resource_id')->nullable(); - $table->timestamp('valid_until')->nullable(); + $table->unsignedBigInteger('server_id')->nullable(); + $table->timestamp('valid_until'); $table->timestamps(); - $table->index(['resource_type', 'resource_id']); + $table->foreign('server_id')->references('id')->on('servers'); + $table->unique(['server_id', 'resource_id']); }); } From fab7300a5f5a1ca0ee2012436548b44226b31df3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:36:26 +0100 Subject: [PATCH 021/180] feat(installer): create CA folder during installation --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 1889ad355..6eb46ad09 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -62,7 +62,7 @@ if [ "$WARNING_SPACE" = true ]; then sleep 5 fi -mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,sentinel} +mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,ca,webhooks-during-maintenance,sentinel} mkdir -p /data/coolify/ssh/{keys,mux} mkdir -p /data/coolify/proxy/dynamic From 02475c5232735f9cd5bea9afc8198c65c8be90aa Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:37:34 +0100 Subject: [PATCH 022/180] feat(ssl): improve SSL helper - improve function parameters - set default validity to 1 year as resources need to be manually restarted to use the new certificates - use the CA cert to sign certificates --- app/Helpers/SslHelper.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php index 3a8efd540..4797113b7 100644 --- a/app/Helpers/SslHelper.php +++ b/app/Helpers/SslHelper.php @@ -11,10 +11,14 @@ class SslHelper public static function generateSslCertificate( string $commonName, - array $additionalSans, - string $resourceType, - int $resourceId, + array $additionalSans = [], + ?string $resourceType = null, + ?int $resourceId = null, + ?int $serverId = null, ?string $organizationName = null, + int $validityDays = 365, + ?string $caCert = null, + ?string $caKey = null ): SslCertificate { $organizationName ??= self::DEFAULT_ORGANIZATION_NAME; @@ -50,9 +54,9 @@ class SslHelper $certificate = openssl_csr_sign( $csr, - null, - $privateKey, - 90, + $caCert ?? null, + $caKey ?? $privateKey, + $validityDays, [ 'digest_alg' => 'sha512', 'config' => null, @@ -73,7 +77,8 @@ class SslHelper 'ssl_private_key' => $privateKeyStr, 'resource_type' => $resourceType, 'resource_id' => $resourceId, - 'valid_until' => CarbonImmutable::now()->addDays(90), + 'server_id' => $serverId, + 'valid_until' => CarbonImmutable::now()->addDays($validityDays), ]); } catch (\Throwable $e) { throw new \RuntimeException('SSL Certificate generation failed: '.$e->getMessage(), 0, $e); From 85c777d2a4b09c06f3e596a29fafb39ddc81a7b8 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:56:20 +0100 Subject: [PATCH 023/180] feat(ssl): use new improved helper for SSL generation - use CA cert and key for SSL cert generation - remove unused parameters - add a few more echo with log output --- app/Actions/Database/StartPostgresql.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index df516bbda..de33a0114 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -33,24 +33,30 @@ class StartPostgresql $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/", "mkdir -p $this->configuration_dir/ssl", + "echo 'Directories created successfully.'", ]; if ($this->database->enable_ssl) { - $this->commands[] = "echo 'Setting up SSL certificate.'"; - $this->ssl_certificate = SslCertificate::where('resource_type', $this->database->getMorphClass()) - ->where('resource_id', $this->database->id) - ->where('certificate_type', 'internal') - ->first(); + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $server = $this->database->destination->server; + + $caCert = SslCertificate::where('server_id', $server->id)->firstOrFail(); + + $this->ssl_certificate = SslCertificate::where('resource_type', $this->database->getMorphClass())->where('resource_id', $this->database->id)->first(); if (! $this->ssl_certificate) { - $this->commands[] = "echo 'Generating new SSL certificate.'"; + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + // additionalSans: ["IP:{$server->ip_address}"], // Issue is the server IP can be also be a domain/ hostname and we need to be sure what it is before setting it. resourceType: $this->database->getMorphClass(), resourceId: $this->database->id, - certificateType: 'internal', + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, ); $this->addSslFilesToFileStorage(); } From 7406ee67c2a76c8f2a801ecef027b6ab3e776dfb Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:27:20 +0100 Subject: [PATCH 024/180] chore(ssl): rename CA cert to `coolify-ca.crt` because of conflicts --- app/Actions/Server/InstallDocker.php | 4 ++-- database/seeders/CaSslCertSeeder.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 775de5ead..993d38e5f 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -32,8 +32,8 @@ class InstallDocker "mkdir -p $serverCertPath", "chown -R 9999:root $serverCertPath", "chmod -R 700 $serverCertPath", - "echo '{$serverCert->ssl_certificate}' > $serverCertPath/ca.crt", - "chmod 644 $serverCertPath/ca.crt", + "echo '{$serverCert->ssl_certificate}' > $serverCertPath/coolify-ca.crt", + "chmod 644 $serverCertPath/coolify-ca.crt", ]); remote_process($commands, $server); } diff --git a/database/seeders/CaSslCertSeeder.php b/database/seeders/CaSslCertSeeder.php index 75b78da21..9970e8fb5 100644 --- a/database/seeders/CaSslCertSeeder.php +++ b/database/seeders/CaSslCertSeeder.php @@ -28,8 +28,8 @@ class CaSslCertSeeder extends Seeder "mkdir -p $serverCertPath", "chown -R 9999:root $serverCertPath", "chmod -R 700 $serverCertPath", - "echo '{$serverCert->ssl_certificate}' > $serverCertPath/ca.crt", - "chmod 644 $serverCertPath/ca.crt", + "echo '{$serverCert->ssl_certificate}' > $serverCertPath/coolify-ca.crt", + "chmod 644 $serverCertPath/coolify-ca.crt", ]); remote_process($commands, $server); From ab1833b159f448d57f6551342b9cf9187cb2c314 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:29:06 +0100 Subject: [PATCH 025/180] feat(ui): Add CA cert UI - brief instructions and recommendations - copy button to copy the CA file mount - ability to display the CA certificate - ability to save your own CA Cert or generate a new one --- .../views/livewire/server/advanced.blade.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/resources/views/livewire/server/advanced.blade.php b/resources/views/livewire/server/advanced.blade.php index b6dcf0ea0..737f52bea 100644 --- a/resources/views/livewire/server/advanced.blade.php +++ b/resources/views/livewire/server/advanced.blade.php @@ -38,6 +38,82 @@ + +
+

CA SSL Certificate

+
+ + + + +
+
+
+

Recommended Configuration:

+
    +
  • Mount this Coolify CA certificate into all containers that need to connect to a database over SSL.
  • +
  • Use verify-full (recommended) or verify-ca when connecting to a database over SSL.
  • +
+
+
+ +
+
+
+
+ CA Certificate Content + + {{ $showCertificate ? 'Hide' : 'Show' }} + +
+ @if($showCertificate) + + @else +
+
+
+ ━━━━━━━━ CERTIFICATE CONTENT ━━━━━━━━ +
+
+ Click "Show" to view or edit +
+
+
+ @endif +
+
From 6d0291a66f47c44600d552ab218fd7ccfbb849dd Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:31:02 +0100 Subject: [PATCH 026/180] feat(ui): new copy button component --- .../views/components/forms/copy-button.blade.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 resources/views/components/forms/copy-button.blade.php diff --git a/resources/views/components/forms/copy-button.blade.php b/resources/views/components/forms/copy-button.blade.php new file mode 100644 index 000000000..2e5f64c1a --- /dev/null +++ b/resources/views/components/forms/copy-button.blade.php @@ -0,0 +1,13 @@ +@props(['text']) + +
+ + +
From 4eba1d21303374ab8eb49cdff1122d3b1eeabbf6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:31:42 +0100 Subject: [PATCH 027/180] feat(ui): use new copy button component everywhere --- .../components/modal-confirmation.blade.php | 34 ++---------- .../views/livewire/profile/index.blade.php | 52 +++---------------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index e98a494c5..cc15de99e 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -40,7 +40,6 @@ userConfirmationText: '', confirmWithText: @js($confirmWithText && !$disableTwoStepConfirmation), confirmWithPassword: @js($confirmWithPassword && !$disableTwoStepConfirmation), - copied: false, submitAction: @js($submitAction), passwordError: '', selectedActions: @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all()), @@ -91,13 +90,6 @@ } }); }, - copyConfirmationText() { - navigator.clipboard.writeText(this.confirmationText); - this.copied = true; - setTimeout(() => { - this.copied = false; - }, 2000); - }, toggleAction(id) { const index = this.selectedActions.indexOf(id); if (index > -1) { @@ -255,29 +247,9 @@

Confirm Actions

{{ $confirmationLabel }}

- - +