feat: Add HTTP Basic Authentication

This commit is contained in:
Christopher Kaster
2025-04-17 14:14:32 +02:00
parent 6834c9e4dd
commit 2634f516d5
5 changed files with 107 additions and 10 deletions

View File

@@ -94,6 +94,9 @@ class General extends Component
'application.settings.is_preserve_repository_enabled' => 'boolean|required', 'application.settings.is_preserve_repository_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable', 'application.watch_paths' => 'nullable',
'application.redirect' => 'string|required', '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 = [ protected $validationAttributes = [

View File

@@ -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.'], '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.'], '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.'], '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'],
] ]
)] )]

View File

@@ -296,7 +296,8 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
return $payload; 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([]); $labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -304,6 +305,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
} else { } else {
$labels->push("caddy_ingress_network={$network}"); $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) { foreach ($domains as $loop => $domain) {
$url = Url::fromString($domain); $url = Url::fromString($domain);
$host = $url->getHost(); $host = $url->getHost();
@@ -343,17 +347,30 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
if (isDev()) { if (isDev()) {
// $labels->push("caddy_{$loop}.tls=internal"); // $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(); 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 = collect([]);
$labels->push('traefik.enable=true'); $labels->push('traefik.enable=true');
$labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); $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([]); $middlewares_from_labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -511,6 +528,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -534,6 +554,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -577,7 +600,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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; break;
case ProxyTypes::CADDY->value: case ProxyTypes::CADDY->value:
@@ -589,7 +615,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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; break;
} }
@@ -601,7 +630,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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( $labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network, network: $application->destination->network,
@@ -611,7 +643,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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; break;
case ProxyTypes::CADDY->value: case ProxyTypes::CADDY->value:
@@ -642,7 +680,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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; break;
} }
@@ -653,7 +694,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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( $labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network, network: $application->destination->network,
@@ -662,7 +706,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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,
)); ));
} }
} }

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->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');
});
}
};

View File

@@ -349,6 +349,18 @@
@endif @endif
</div> </div>
<h3 class="pt-8">HTTP Basic Authentication</h3>
<div x-data="{enabled: {{ $application->http_basic_auth_enable ? "true" : "false" }}}">
<div class="w-96">
<x-forms.checkbox label="Enable" id="application.http_basic_auth_enable" x-model="enabled" />
</div>
<div class="w-96" x-show="enabled">
<x-forms.input id="application.http_basic_auth_username" label="Username" />
<x-forms.input id="application.http_basic_auth_password" label="Password" />
</div>
</div>
@if ($application->settings->is_container_label_readonly_enabled) @if ($application->settings->is_container_label_readonly_enabled)
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels" <x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea> monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>