mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 12:14:07 +00:00
Merge remote-tracking branch 'origin/next' into chore/update-deployment-validation
This commit is contained in:
commit
1bad82cf43
10 files changed
+379
-22
No files matched your search
@@ -17,6 +17,7 @@ use App\Models\LocalPersistentVolume;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Rules\DockerImageFormat;
|
||||
use App\Rules\ValidGitBranch;
|
||||
use App\Rules\ValidGitRepositoryUrl;
|
||||
use App\Services\DockerImageParser;
|
||||
@@ -1790,8 +1791,8 @@ class ApplicationsController extends Controller
|
||||
]))->setStatusCode(201);
|
||||
} elseif ($type === 'dockerimage') {
|
||||
$validationRules = [
|
||||
'docker_registry_image_name' => 'string|required',
|
||||
'docker_registry_image_tag' => 'string',
|
||||
'docker_registry_image_name' => ['required', 'string', 'max:255', new DockerImageFormat],
|
||||
'docker_registry_image_tag' => ValidationPatterns::dockerImageTagRules(),
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||
];
|
||||
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||
|
||||
@@ -220,6 +220,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
$this->dockerImagePreviewTag = $this->application_deployment_queue->docker_registry_image_tag;
|
||||
$this->validateDockerRegistryImageConfiguration();
|
||||
|
||||
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||
|
||||
@@ -1139,6 +1140,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return $this->pull_request_id === 0;
|
||||
}
|
||||
|
||||
private function validateDockerRegistryImageConfiguration(): void
|
||||
{
|
||||
if (! ValidationPatterns::isValidDockerImageName($this->application->docker_registry_image_name)) {
|
||||
throw new DeploymentException('Docker registry image name contains invalid characters.');
|
||||
}
|
||||
|
||||
if (! ValidationPatterns::isValidDockerImageTag($this->application->docker_registry_image_tag)) {
|
||||
throw new DeploymentException('Docker registry image tag contains invalid characters.');
|
||||
}
|
||||
|
||||
if (! ValidationPatterns::isValidDockerImageTag($this->dockerImagePreviewTag)) {
|
||||
throw new DeploymentException('Docker registry preview image tag contains invalid characters.');
|
||||
}
|
||||
}
|
||||
|
||||
private function generate_image_names()
|
||||
{
|
||||
if ($this->application->dockerfile) {
|
||||
|
||||
@@ -158,8 +158,8 @@ class General extends Component
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'customNetworkAliases' => 'nullable',
|
||||
'dockerfile' => 'nullable',
|
||||
'dockerRegistryImageName' => 'nullable',
|
||||
'dockerRegistryImageTag' => 'nullable',
|
||||
'dockerRegistryImageName' => ValidationPatterns::dockerImageNameRules(),
|
||||
'dockerRegistryImageTag' => ValidationPatterns::dockerImageTagRules(),
|
||||
'dockerfileLocation' => ValidationPatterns::filePathRules(),
|
||||
'dockerComposeLocation' => ValidationPatterns::filePathRules(),
|
||||
'dockerCompose' => 'nullable',
|
||||
@@ -849,7 +849,7 @@ class General extends Component
|
||||
}
|
||||
if ($this->buildPack === 'dockerimage') {
|
||||
$this->validate([
|
||||
'dockerRegistryImageName' => 'required',
|
||||
'dockerRegistryImageName' => ValidationPatterns::dockerImageNameRules(required: true),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Livewire\Project\New;
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use App\Services\DockerImageParser;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -81,8 +82,8 @@ class DockerImage extends Component
|
||||
public function submit()
|
||||
{
|
||||
$this->validate([
|
||||
'imageName' => ['required', 'string'],
|
||||
'imageTag' => ['nullable', 'string', 'regex:/^[a-z0-9][a-z0-9._-]*$/i'],
|
||||
'imageName' => ValidationPatterns::dockerImageNameRules(required: true),
|
||||
'imageTag' => ValidationPatterns::dockerImageTagRules(),
|
||||
'imageSha256' => ['nullable', 'string', 'regex:/^[a-f0-9]{64}$/i'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,18 +2,26 @@
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Support\ValidationPatterns;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||
|
||||
class DockerImageFormat implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
* @param Closure(string, ?string=): PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the value contains ":sha256:" or ":sha" which is incorrect format
|
||||
if (preg_match('/:sha256?:/i', $value)) {
|
||||
$fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');
|
||||
@@ -21,20 +29,21 @@ class DockerImageFormat implements ValidationRule
|
||||
return;
|
||||
}
|
||||
|
||||
// Valid formats:
|
||||
// 1. image:tag (e.g., nginx:latest)
|
||||
// 2. registry/image:tag (e.g., ghcr.io/user/app:v1.2.3)
|
||||
// 3. image@sha256:hash (e.g., nginx@sha256:abc123...)
|
||||
// 4. registry/image@sha256:hash
|
||||
// 5. registry:port/image:tag (e.g., localhost:5000/app:latest)
|
||||
$imageName = $value;
|
||||
$tag = null;
|
||||
|
||||
$pattern = '/^
|
||||
(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?\/)? # Optional registry with optional port
|
||||
[a-z0-9]+(?:[._\/-][a-z0-9]+)* # Image name (required)
|
||||
(?::[a-z0-9][a-z0-9._-]*|@sha256:[a-f0-9]{64})? # Optional :tag or @sha256:hash
|
||||
$/ix';
|
||||
if (preg_match('/\A(.+)@sha256:([a-f0-9]{64})\z/i', $value, $matches) === 1) {
|
||||
$imageName = $matches[1];
|
||||
} else {
|
||||
$lastColon = strrpos($value, ':');
|
||||
$lastSlash = strrpos($value, '/');
|
||||
if ($lastColon !== false && ($lastSlash === false || $lastColon > $lastSlash)) {
|
||||
$imageName = substr($value, 0, $lastColon);
|
||||
$tag = substr($value, $lastColon + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (! preg_match($pattern, $value)) {
|
||||
if (! ValidationPatterns::isValidDockerImageName($imageName) || ! ValidationPatterns::isValidDockerImageTag($tag)) {
|
||||
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,23 @@ class ValidationPatterns
|
||||
*/
|
||||
public const DB_PASSWORD_PATTERN = '/^[A-Za-z0-9!@#%^*()_+\-=\[\]{}:,.?\/~]+$/';
|
||||
|
||||
/**
|
||||
* Pattern for Docker image repository names without a tag.
|
||||
*
|
||||
* Allows an optional registry host/port followed by lowercase repository
|
||||
* path components. A trailing @sha256 marker is accepted for existing
|
||||
* digest-based dockerimage records that store the digest hash separately.
|
||||
*/
|
||||
public const DOCKER_IMAGE_NAME_PATTERN = '/\A(?=.{1,255}\z)(?:(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::[0-9]+)?\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*)(?:@sha256)?\z/';
|
||||
|
||||
/**
|
||||
* Pattern for Docker image tags.
|
||||
*
|
||||
* Docker tags may contain letters, digits, underscores, dots, and hyphens,
|
||||
* must start with an alphanumeric/underscore, and are limited to 128 chars.
|
||||
*/
|
||||
public const DOCKER_IMAGE_TAG_PATTERN = '/\A[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}\z/';
|
||||
|
||||
/**
|
||||
* Normalize environment variable keys before validation and storage.
|
||||
*/
|
||||
@@ -163,6 +180,81 @@ class ValidationPatterns
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for Docker image repository names without tags.
|
||||
*/
|
||||
public static function dockerImageNameRules(bool $required = false, int $maxLength = 255): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($required) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
$rules[] = 'string';
|
||||
$rules[] = "max:$maxLength";
|
||||
$rules[] = 'regex:'.self::DOCKER_IMAGE_NAME_PATTERN;
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for Docker image tags.
|
||||
*/
|
||||
public static function dockerImageTagRules(bool $required = false, int $maxLength = 128): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($required) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
$rules[] = 'string';
|
||||
$rules[] = "max:$maxLength";
|
||||
$rules[] = 'regex:'.self::DOCKER_IMAGE_TAG_PATTERN;
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages for Docker image fields.
|
||||
*/
|
||||
public static function dockerImageMessages(string $nameField = 'docker_registry_image_name', string $tagField = 'docker_registry_image_tag'): array
|
||||
{
|
||||
return [
|
||||
"{$nameField}.regex" => 'The Docker registry image name must be a valid image repository without a tag and may not contain shell metacharacters.',
|
||||
"{$tagField}.regex" => 'The Docker registry image tag must be a valid Docker tag and may not contain shell metacharacters.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid Docker image repository name without a tag.
|
||||
*/
|
||||
public static function isValidDockerImageName(?string $value): bool
|
||||
{
|
||||
if (blank($value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return preg_match(self::DOCKER_IMAGE_NAME_PATTERN, $value) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid Docker image tag.
|
||||
*/
|
||||
public static function isValidDockerImageTag(?string $value): bool
|
||||
{
|
||||
if (blank($value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return preg_match(self::DOCKER_IMAGE_TAG_PATTERN, $value) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for database identifier fields (username, database name).
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user