feat(deployment): introduce 'use_build_secrets' setting for enhanced security during Docker builds and update related logic in deployment process
This commit is contained in:
@@ -361,6 +361,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function detectBuildKitCapabilities(): void
|
private function detectBuildKitCapabilities(): void
|
||||||
{
|
{
|
||||||
|
// If build secrets are not enabled, skip detection and use traditional args
|
||||||
|
if (! $this->application->settings->use_build_secrets) {
|
||||||
|
$this->dockerBuildkitSupported = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
|
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
|
||||||
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
|
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
|
||||||
|
|
||||||
@@ -376,7 +383,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
|
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+). Using traditional build arguments.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+). Build secrets feature disabled.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -395,11 +402,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (trim($buildkitTest) === 'supported') {
|
if (trim($buildkitTest) === 'supported') {
|
||||||
$this->dockerBuildkitSupported = true;
|
$this->dockerBuildkitSupported = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit secrets support detected on {$serverName}.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit secrets support detected on {$serverName}.");
|
||||||
$this->application_deployment_queue->addLogEntry('✓ Build secrets will be used for enhanced security during builds.');
|
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
||||||
} else {
|
} else {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not have BuildKit secrets support enabled.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not have BuildKit secrets support.");
|
||||||
$this->application_deployment_queue->addLogEntry('⚠ Using traditional build arguments (less secure but compatible).');
|
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Buildx is available, which means BuildKit is available
|
// Buildx is available, which means BuildKit is available
|
||||||
@@ -412,17 +419,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (trim($secretsTest) === 'supported') {
|
if (trim($secretsTest) === 'supported') {
|
||||||
$this->dockerBuildkitSupported = true;
|
$this->dockerBuildkitSupported = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
||||||
$this->application_deployment_queue->addLogEntry('✓ Build secrets will be used for enhanced security during builds.');
|
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
||||||
} else {
|
} else {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with Buildx on {$serverName}, but secrets not supported.");
|
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with Buildx on {$serverName}, but secrets not supported.");
|
||||||
$this->application_deployment_queue->addLogEntry('⚠ Using traditional build arguments (less secure but compatible).');
|
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->dockerBuildkitSupported = false;
|
$this->dockerBuildkitSupported = false;
|
||||||
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
|
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
|
||||||
$this->application_deployment_queue->addLogEntry('⚠ Using traditional build arguments as fallback.');
|
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but detection failed. Using traditional build arguments.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +562,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
|
|
||||||
$this->detectBuildKitCapabilities();
|
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
|
|
||||||
$this->application->loadComposeFile(isInit: false);
|
$this->application->loadComposeFile(isInit: false);
|
||||||
@@ -566,7 +572,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
// For raw compose, we cannot automatically add secrets configuration
|
// For raw compose, we cannot automatically add secrets configuration
|
||||||
// User must define it manually in their docker-compose file
|
// User must define it manually in their docker-compose file
|
||||||
if ($this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||||
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
|
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -588,8 +594,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add build secrets to compose file if BuildKit is supported
|
// Add build secrets to compose file if enabled and BuildKit is supported
|
||||||
if ($this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||||
$composeFile = $this->add_build_secrets_to_compose($composeFile);
|
$composeFile = $this->add_build_secrets_to_compose($composeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +722,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||||
}
|
}
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->detectBuildKitCapabilities();
|
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
@@ -2336,11 +2341,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
]);
|
]);
|
||||||
if ($this->dockerBuildkitSupported) {
|
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||||
// Modify the nixpacks Dockerfile to use build secrets
|
// Modify the nixpacks Dockerfile to use build secrets
|
||||||
$this->modify_nixpacks_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
$this->modify_nixpacks_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||||
$build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}";
|
$build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}";
|
||||||
|
} elseif ($this->dockerBuildkitSupported) {
|
||||||
|
// BuildKit without secrets
|
||||||
|
$build_command = "DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}";
|
||||||
} else {
|
} else {
|
||||||
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}";
|
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}";
|
||||||
}
|
}
|
||||||
@@ -2649,10 +2657,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$variables = collect([])->merge($this->env_args);
|
$variables = collect([])->merge($this->env_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->dockerBuildkitSupported) {
|
// Check if build secrets are enabled and BuildKit is supported
|
||||||
|
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||||
$this->generate_build_secrets($variables);
|
$this->generate_build_secrets($variables);
|
||||||
$this->build_args = '';
|
$this->build_args = '';
|
||||||
} else {
|
} else {
|
||||||
|
// Fall back to traditional build args
|
||||||
$this->build_args = $variables->map(function ($value, $key) {
|
$this->build_args = $variables->map(function ($value, $key) {
|
||||||
$value = escapeshellarg($value);
|
$value = escapeshellarg($value);
|
||||||
|
|
||||||
@@ -2663,6 +2673,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function generate_docker_env_flags_for_secrets()
|
private function generate_docker_env_flags_for_secrets()
|
||||||
{
|
{
|
||||||
|
// Only generate env flags if build secrets are enabled
|
||||||
|
if (! $this->application->settings->use_build_secrets) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
$variables = $this->pull_request_id === 0
|
$variables = $this->pull_request_id === 0
|
||||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
|
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
|
||||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||||
@@ -2737,8 +2752,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function modify_nixpacks_dockerfile_for_secrets($dockerfile_path)
|
private function modify_nixpacks_dockerfile_for_secrets($dockerfile_path)
|
||||||
{
|
{
|
||||||
// Only process if we have secrets to mount
|
// Only process if build secrets are enabled and we have secrets to mount
|
||||||
if (empty($this->build_secrets)) {
|
if (! $this->application->settings->use_build_secrets || empty($this->build_secrets)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,6 +25,8 @@ class All extends Component
|
|||||||
|
|
||||||
public bool $is_env_sorting_enabled = false;
|
public bool $is_env_sorting_enabled = false;
|
||||||
|
|
||||||
|
public bool $use_build_secrets = false;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'saveKey' => 'submit',
|
'saveKey' => 'submit',
|
||||||
'refreshEnvs',
|
'refreshEnvs',
|
||||||
@@ -34,6 +36,7 @@ class All extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false);
|
$this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false);
|
||||||
|
$this->use_build_secrets = data_get($this->resource, 'settings.use_build_secrets', false);
|
||||||
$this->resourceClass = get_class($this->resource);
|
$this->resourceClass = get_class($this->resource);
|
||||||
$resourceWithPreviews = [\App\Models\Application::class];
|
$resourceWithPreviews = [\App\Models\Application::class];
|
||||||
$simpleDockerfile = filled(data_get($this->resource, 'dockerfile'));
|
$simpleDockerfile = filled(data_get($this->resource, 'dockerfile'));
|
||||||
@@ -49,6 +52,7 @@ class All extends Component
|
|||||||
$this->authorize('manageEnvironment', $this->resource);
|
$this->authorize('manageEnvironment', $this->resource);
|
||||||
|
|
||||||
$this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled;
|
$this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled;
|
||||||
|
$this->resource->settings->use_build_secrets = $this->use_build_secrets;
|
||||||
$this->resource->settings->save();
|
$this->resource->settings->save();
|
||||||
$this->getDevView();
|
$this->getDevView();
|
||||||
$this->dispatch('success', 'Environment variable settings updated.');
|
$this->dispatch('success', 'Environment variable settings updated.');
|
||||||
|
@@ -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('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('use_build_secrets')->default(false)->after('is_build_server_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('use_build_secrets');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -13,8 +13,10 @@
|
|||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
<div>Environment variables (secrets) for this resource. </div>
|
<div>Environment variables (secrets) for this resource. </div>
|
||||||
@if ($resourceClass === 'App\Models\Application' && data_get($resource, 'build_pack') !== 'dockercompose')
|
@if ($resourceClass === 'App\Models\Application')
|
||||||
<div class="w-64 pt-2">
|
<div class="flex flex-col gap-2 pt-2">
|
||||||
|
@if (data_get($resource, 'build_pack') !== 'dockercompose')
|
||||||
|
<div class="w-64">
|
||||||
@can('manageEnvironment', $resource)
|
@can('manageEnvironment', $resource)
|
||||||
<x-forms.checkbox id="is_env_sorting_enabled" label="Sort alphabetically"
|
<x-forms.checkbox id="is_env_sorting_enabled" label="Sort alphabetically"
|
||||||
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||||
@@ -26,6 +28,19 @@
|
|||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<div class="w-64">
|
||||||
|
@can('manageEnvironment', $resource)
|
||||||
|
<x-forms.checkbox id="use_build_secrets" label="Use Docker Build Secrets"
|
||||||
|
helper="Enable Docker BuildKit secrets for enhanced security during builds. Secrets won't be exposed in the final image. Requires Docker 18.09+ with BuildKit support."
|
||||||
|
instantSave></x-forms.checkbox>
|
||||||
|
@else
|
||||||
|
<x-forms.checkbox id="use_build_secrets" label="Use Docker Build Secrets"
|
||||||
|
helper="Enable Docker BuildKit secrets for enhanced security during builds. Secrets won't be exposed in the final image. Requires Docker 18.09+ with BuildKit support."
|
||||||
|
disabled></x-forms.checkbox>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
|
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
|
||||||
<div class="flex items-center gap-1 pt-4 dark:text-warning text-coollabs">
|
<div class="flex items-center gap-1 pt-4 dark:text-warning text-coollabs">
|
||||||
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
||||||
|
Reference in New Issue
Block a user