From 2634f516d567a5a52cb729639c6012bda0204580 Mon Sep 17 00:00:00 2001 From: Christopher Kaster Date: Thu, 17 Apr 2025 14:14:32 +0200 Subject: [PATCH 1/2] feat: Add HTTP Basic Authentication --- app/Livewire/Project/Application/General.php | 3 + app/Models/Application.php | 3 + bootstrap/helpers/docker.php | 67 ++++++++++++++++--- ...add_application_http_basic_auth_fields.php | 32 +++++++++ .../project/application/general.blade.php | 12 ++++ 5 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index eaa988b99..a5c8fae88 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -94,6 +94,9 @@ class General extends Component 'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', 'application.redirect' => 'string|required', + 'application.http_basic_auth_enable' => 'boolean|required', + 'application.http_basic_auth_username' => 'nullable', + 'application.http_basic_auth_password' => 'nullable', ]; protected $validationAttributes = [ diff --git a/app/Models/Application.php b/app/Models/Application.php index 2feaebf94..01cdd565d 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,6 +103,9 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], + 'http_basic_auth_enable' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] )] diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index de80adbef..0a6c8684d 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -296,7 +296,8 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null) + +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); if ($serviceLabels) { @@ -304,6 +305,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, } else { $labels->push("caddy_ingress_network={$network}"); } + + $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + foreach ($domains as $loop => $domain) { $url = Url::fromString($domain); $host = $url->getHost(); @@ -343,17 +347,30 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if (isDev()) { // $labels->push("caddy_{$loop}.tls=internal"); } + if ($http_basic_auth_enabled) { + $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); + } } return $labels->sort(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both') + +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); $labels->push('traefik.enable=true'); $labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); + $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $http_basic_auth_label = "http-basic-auth-{$uuid}"; + + if ($http_basic_auth_enabled) { + $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}"); + } + $middlewares_from_labels = collect([]); if ($serviceLabels) { @@ -511,6 +528,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -534,6 +554,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } + if ($http_basic_auth_enabled) { + $middlewares->push($http_basic_auth_label); + } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { $middlewares->push($middleware_name); }); @@ -577,7 +600,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; case ProxyTypes::CADDY->value: @@ -589,7 +615,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enabled, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; } @@ -601,7 +630,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); $labels = $labels->merge(fqdnLabelsForCaddy( network: $application->destination->network, @@ -611,7 +643,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - redirect_direction: $application->redirect + redirect_direction: $application->redirect, + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); } } @@ -631,7 +666,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; case ProxyTypes::CADDY->value: @@ -642,7 +680,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); break; } @@ -653,7 +694,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); $labels = $labels->merge(fqdnLabelsForCaddy( network: $application->destination->network, @@ -662,7 +706,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_username: $application->http_basic_auth_username, + http_basic_auth_password: $application->http_basic_auth_password, )); } } diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php new file mode 100644 index 000000000..bc8f2712a --- /dev/null +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -0,0 +1,32 @@ +boolean('http_basic_auth_enable')->default(false); + $table->string('http_basic_auth_username')->nullable(true)->default(null); + $table->string('http_basic_auth_password')->nullable(true)->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('http_basic_auth_enable'); + $table->dropColumn('http_basic_auth_username'); + $table->dropColumn('http_basic_auth_password'); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index f971c8a4f..2c2cbde63 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -349,6 +349,18 @@ @endif +

HTTP Basic Authentication

+
+
+ +
+ +
+ + +
+
+ @if ($application->settings->is_container_label_readonly_enabled) From 9e608f7ba5af494d0cd364e7cb31652c128f0d24 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:30:27 +0200 Subject: [PATCH 2/2] refactor(http-basic-auth): rename 'http_basic_auth_enable' to 'http_basic_auth_enabled' across application files for consistency --- app/Livewire/Project/Application/General.php | 2 +- app/Models/Application.php | 2 +- bootstrap/helpers/docker.php | 18 ++++++++---------- ..._add_application_http_basic_auth_fields.php | 4 ++-- .../project/application/general.blade.php | 9 +++++---- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index a5c8fae88..b7cb693b6 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -94,7 +94,7 @@ class General extends Component 'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', 'application.redirect' => 'string|required', - 'application.http_basic_auth_enable' => 'boolean|required', + 'application.http_basic_auth_enabled' => 'boolean|required', 'application.http_basic_auth_username' => 'nullable', 'application.http_basic_auth_password' => 'nullable', ]; diff --git a/app/Models/Application.php b/app/Models/Application.php index cc69155cb..3306510d1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,7 +103,7 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], - 'http_basic_auth_enable' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 40d4acc0d..05fef91f5 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -344,9 +344,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); } - if (isDev()) { - // $labels->push("caddy_{$loop}.tls=internal"); - } if ($http_basic_auth_enabled) { $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); @@ -585,6 +582,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview if ($pull_request_id !== 0) { $appUuid = $appUuid.'-pr-'.$pull_request_id; } + ray($application); $labels = collect([]); if ($pull_request_id === 0) { if ($application->fqdn) { @@ -601,7 +599,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -631,7 +629,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -644,7 +642,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -667,7 +665,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -681,7 +679,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -695,7 +693,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); @@ -707,7 +705,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_enable, + http_basic_auth_enabled: $application->http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, )); diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php index bc8f2712a..247300abd 100644 --- a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->boolean('http_basic_auth_enable')->default(false); + $table->boolean('http_basic_auth_enabled')->default(false); $table->string('http_basic_auth_username')->nullable(true)->default(null); $table->string('http_basic_auth_password')->nullable(true)->default(null); }); @@ -24,7 +24,7 @@ return new class extends Migration public function down(): void { Schema::table('applications', function (Blueprint $table) { - $table->dropColumn('http_basic_auth_enable'); + $table->dropColumn('http_basic_auth_enabled'); $table->dropColumn('http_basic_auth_username'); $table->dropColumn('http_basic_auth_password'); }); diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 2c2cbde63..b6047d4be 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -350,14 +350,15 @@

HTTP Basic Authentication

-
+
- +
-
+
- +