coolify/app/Livewire/Project/New/DockerCompose.php

96 lines
3.4 KiB
PHP
Raw Permalink Normal View History

2023-09-19 13:51:13 +00:00
<?php
2023-12-07 18:06:32 +00:00
namespace App\Livewire\Project\New;
2023-09-19 13:51:13 +00:00
2023-09-26 12:45:52 +00:00
use App\Models\EnvironmentVariable;
2023-09-19 13:51:13 +00:00
use App\Models\Project;
2023-09-20 13:42:41 +00:00
use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
2024-06-10 20:43:34 +00:00
use Livewire\Component;
use Symfony\Component\Yaml\Yaml;
2023-09-19 13:51:13 +00:00
class DockerCompose extends Component
{
2023-09-25 13:48:43 +00:00
public string $dockerComposeRaw = '';
2024-06-10 20:43:34 +00:00
2023-09-26 12:45:52 +00:00
public string $envFile = '';
2024-06-10 20:43:34 +00:00
2023-09-19 13:51:13 +00:00
public array $parameters;
2024-06-10 20:43:34 +00:00
2023-09-19 13:51:13 +00:00
public array $query;
2024-06-10 20:43:34 +00:00
2023-09-19 13:51:13 +00:00
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
$this->dockerComposeRaw = file_get_contents(base_path('templates/test-database-detection.yaml'));
2023-09-19 13:51:13 +00:00
}
}
2024-06-10 20:43:34 +00:00
2023-09-19 13:51:13 +00:00
public function submit()
{
2024-03-14 08:21:48 +00:00
$server_id = $this->query['server_id'];
try {
$this->validate([
2024-06-10 20:43:34 +00:00
'dockerComposeRaw' => 'required',
]);
2023-09-25 13:48:43 +00:00
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
fix: prevent command injection in Docker Compose parsing - add pre-save validation This commit addresses a critical security issue where malicious Docker Compose data was being saved to the database before validation occurred. Problem: - Service models were saved to database first - Validation ran afterwards during parse() - Malicious data persisted even when validation failed - User saw error but damage was already done Solution: 1. Created validateDockerComposeForInjection() to validate YAML before save 2. Added pre-save validation to all Service creation/update points: - Livewire: DockerCompose.php, StackForm.php - API: ServicesController.php (create, update, one-click) 3. Validates service names and volume paths (string + array formats) 4. Blocks shell metacharacters: backticks, $(), |, ;, &, >, <, newlines Security fixes: - Volume source paths (string format) - validated before save - Volume source paths (array format) - validated before save - Service names - validated before save - Environment variable patterns - safe ${VAR} allowed, ${VAR:-$(cmd)} blocked Testing: - 60 security tests pass (176 assertions) - PreSaveValidationTest.php: 15 tests for pre-save validation - ValidateShellSafePathTest.php: 15 tests for core validation - VolumeSecurityTest.php: 15 tests for volume parsing - ServiceNameSecurityTest.php: 15 tests for service names Related commits: - Previous: Added validation during parse() phase - This commit: Moves validation before database save 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 19:46:26 +00:00
// Validate for command injection BEFORE saving to database
validateDockerComposeForInjection($this->dockerComposeRaw);
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
2024-11-22 15:03:20 +00:00
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (! $destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$service = Service::create([
2023-09-25 13:48:43 +00:00
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
]);
2023-09-26 12:45:52 +00:00
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $data) {
// Extract value and comment from parsed data
// Handle both array format ['value' => ..., 'comment' => ...] and plain string values
$value = is_array($data) ? ($data['value'] ?? '') : $data;
$comment = is_array($data) ? ($data['comment'] ?? null) : null;
EnvironmentVariable::create([
2023-09-26 12:45:52 +00:00
'key' => $key,
'value' => $value,
'comment' => $comment,
2023-09-26 12:45:52 +00:00
'is_preview' => false,
'resourceable_id' => $service->id,
'resourceable_type' => $service->getMorphClass(),
2023-09-26 12:45:52 +00:00
]);
}
$service->parse(isNew: true);
2023-09-21 15:48:31 +00:00
// Apply service-specific application prerequisites
applyServiceApplicationPrerequisites($service);
2023-12-27 15:45:01 +00:00
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
2024-11-22 15:03:20 +00:00
'environment_uuid' => $environment->uuid,
'project_uuid' => $project->uuid,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
2023-09-19 13:51:13 +00:00
}
}