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:
Andras Bacsai
2025-09-17 10:34:38 +02:00
parent 87967b8734
commit c1bee32f09
4 changed files with 89 additions and 27 deletions

View File

@@ -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;
} }

View File

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

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('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');
});
}
};

View File

@@ -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"