This commit is contained in:
eddie-sjoblad 2026-03-11 03:21:31 +08:00 committed by GitHub
commit a15243fcb5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 116 additions and 8 deletions

View file

@ -145,6 +145,9 @@ class General extends Component
#[Validate(['string', 'nullable'])]
public ?string $httpBasicAuthPassword = null;
#[Validate(['string', 'nullable'])]
public ?string $testIpallowlist = null;
#[Validate(['nullable'])]
public ?string $watchPaths = null;
@ -411,6 +414,7 @@ class General extends Component
$this->application->is_http_basic_auth_enabled = $this->isHttpBasicAuthEnabled;
$this->application->http_basic_auth_username = $this->httpBasicAuthUsername;
$this->application->http_basic_auth_password = $this->httpBasicAuthPassword;
$this->application->test_ipallowlist = $this->testIpallowlist;
$this->application->watch_paths = $this->watchPaths;
$this->application->redirect = $this->redirect;
@ -461,6 +465,7 @@ class General extends Component
$this->isHttpBasicAuthEnabled = $this->application->is_http_basic_auth_enabled;
$this->httpBasicAuthUsername = $this->application->http_basic_auth_username;
$this->httpBasicAuthPassword = $this->application->http_basic_auth_password;
$this->testIpallowlist = $this->application->test_ipallowlist;
$this->watchPaths = $this->application->watch_paths;
$this->redirect = $this->application->redirect;

View file

@ -109,6 +109,7 @@ use Visus\Cuid2\Cuid2;
'is_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'],
'test_ipallowlist' => ['type' => 'string', 'nullable' => true, 'description' => 'List of allowed ips'],
]
)]

View file

@ -5,6 +5,7 @@ use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceApplication;
use Aws\Middleware;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Spatie\Url\Url;
@ -406,7 +407,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
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', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
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 $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?string $test_ipallowlist = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
@ -417,6 +418,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
$http_basic_auth_label = "http-basic-auth-{$uuid}";
$list_string = '';
if ($is_http_basic_auth_enabled) {
$hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
}
@ -425,6 +427,22 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}");
}
if ($test_ipallowlist) {
$ip_list = explode(',', $test_ipallowlist);
$sane_ip_list = [];
foreach ($ip_list as $_ip) {
$_ip = trim($_ip);
if (preg_match('/^(\d{1,3}\.){3}\d{1,3}(\/([0-9]|[12][0-9]|3[0-2]))?$/', $_ip)) {
$sane_ip_list[] = $_ip;
}
}
if (count($sane_ip_list) > 0) {
$list_string = implode(',', $sane_ip_list);
$labels->push("traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange={$list_string}");
}
}
$middlewares_from_labels = collect([]);
if ($serviceLabels) {
@ -523,6 +541,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label);
}
if (isListStringFilled($test_ipallowlist)) {
$middlewares->push('test-ipallowlist');
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@ -568,7 +589,11 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
}
if ($is_force_https_enabled) {
$labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https");
$middleware_append_string = 'redirect-to-https';
if (isListStringFilled($test_ipallowlist)) {
$middleware_append_string .= ',test-ipallowlist';
}
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middleware_append_string}");
}
} else {
// Set labels for http
@ -601,6 +626,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_http_basic_auth_enabled) {
$middlewares->push($http_basic_auth_label);
}
if (isListStringFilled($test_ipallowlist)) {
$middlewares->push('test-ipallowlist');
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@ -613,6 +641,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if (isListStringFilled($test_ipallowlist)) {
$middlewares->push('test-ipallowlist');
}
if (str($image)->contains('ghost')) {
$middlewares->push("redir-ghost-{$uuid}");
}
@ -674,6 +705,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password,
test_ipallowlist: $application->test_ipallowlist,
));
break;
case ProxyTypes::CADDY->value:
@ -704,6 +736,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password,
test_ipallowlist: $application->test_ipallowlist,
));
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
@ -740,6 +773,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password,
test_ipallowlist: $application->test_ipallowlist,
));
break;
case ProxyTypes::CADDY->value:
@ -768,6 +802,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
http_basic_auth_username: $application->http_basic_auth_username,
http_basic_auth_password: $application->http_basic_auth_password,
test_ipallowlist: $application->test_ipallowlist,
));
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
@ -982,6 +1017,21 @@ function isDatabaseImageWithContext(string $imageName, array $serviceConfig): bo
return true;
}
function isListStringFilled(string $list)
{
$items = explode(',', $list);
if (! empty($items)) {
$sane_list = [];
foreach ($items as $item) {
if (strlen($item) > 0) {
return true;
}
}
}
return false;
}
function convertDockerRunToCompose(?string $custom_docker_run_options = null)
{
$options = [];

View file

@ -1324,7 +1324,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
is_gzip_enabled: $originalResource->isGzipEnabled(),
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
service_name: $serviceName,
image: $image
image: $image,
test_ipallowlist: $originalResource->test_ipallowlist,
));
break;
case ProxyTypes::CADDY->value:
@ -1351,7 +1352,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
is_gzip_enabled: $originalResource->isGzipEnabled(),
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
service_name: $serviceName,
image: $image
image: $image,
test_ipallowlist: $originalResource->test_ipallowlist,
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $network,
@ -2573,7 +2575,8 @@ function serviceParser(Service $resource): Collection
is_gzip_enabled: $originalResource->isGzipEnabled(),
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
service_name: $serviceName,
image: $image
image: $image,
test_ipallowlist: $originalResource->test_ipallowlist,
));
break;
case ProxyTypes::CADDY->value:
@ -2600,7 +2603,8 @@ function serviceParser(Service $resource): Collection
is_gzip_enabled: $originalResource->isGzipEnabled(),
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
service_name: $serviceName,
image: $image
image: $image,
test_ipallowlist: $originalResource->test_ipallowlist,
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $network,

View file

@ -2299,7 +2299,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
image: data_get($service, 'image'),
test_ipallowlist: $savedService->test_ipallowlist,
));
break;
case ProxyTypes::CADDY->value:
@ -2325,7 +2326,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
image: data_get($service, 'image'),
test_ipallowlist: $savedService->test_ipallowlist,
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $resource->destination->network,
@ -3075,6 +3077,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
is_force_https_enabled: $resource->isForceHttpsEnabled(),
is_gzip_enabled: $resource->isGzipEnabled(),
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
test_ipallowlist: $resource->test_ipallowlist,
)
);
break;
@ -3104,6 +3107,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
is_force_https_enabled: $resource->isForceHttpsEnabled(),
is_gzip_enabled: $resource->isGzipEnabled(),
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
test_ipallowlist: $resource->test_ipallowlist,
)
);
$serviceLabels = $serviceLabels->merge(

View file

@ -0,0 +1,28 @@
<?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->text('test_ipallowlist')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('test_ipallowlist');
});
}
};

View file

@ -11256,6 +11256,11 @@
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
},
"test_ipallowlist": {
"type": "string",
"nullable": true,
"description": "List of allowed ips"
}
},
"type": "object"

View file

@ -7143,6 +7143,10 @@ components:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
test_ipallowlist:
type: string
nullable: true
description: 'List of allowed ips'
type: object
ApplicationDeploymentQueue:
description: 'Project model'

View file

@ -510,6 +510,13 @@
wire:model="customNetworkAliases" x-bind:disabled="!canUpdate" />
@endif
</div>
@if(!$application->destination->server->isSwarm())
<div class="flex xl:flex-row">
<x-forms.input id="testIpallowlist" label="Network allow list (Traefik only)"
helper="A comma separated list of allowed ips you would like to be able to access the Docker service.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>192.168.1.100, 192.168.1.200"
wire:model="testIpallowlist" x-bind:disabled="!canUpdate" />
</div>
@endif
<h3 class="pt-8">HTTP Basic Authentication</h3>
<div>