mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 21:18:11 -04:00
feat(logging): strengthen secret redaction and add admin controls (#12151)
This commit is contained in:
@@ -50,6 +50,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile',
|
||||
'include_source_commit_in_build',
|
||||
'is_env_sorting_enabled',
|
||||
'redact_all_env_values_in_logs',
|
||||
'is_pr_deployments_public_enabled',
|
||||
'stop_grace_period',
|
||||
'docker_images_to_keep',
|
||||
@@ -76,6 +77,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile',
|
||||
'include_source_commit_in_build',
|
||||
'is_env_sorting_enabled',
|
||||
'redact_all_env_values_in_logs',
|
||||
'is_pr_deployments_public_enabled',
|
||||
'is_gzip_enabled',
|
||||
'is_stripprefix_enabled',
|
||||
@@ -429,6 +431,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -629,6 +632,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -829,6 +833,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -1000,6 +1005,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -1167,6 +1173,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -2700,7 +2707,10 @@ class ApplicationsController extends Controller
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($application->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
$logs = sanitizeLogsForExport(
|
||||
getContainerLogs($application->destination->server, $container['ID'], $lines, $showTimestamps),
|
||||
EnvironmentVariable::logRedactionSecretsFor($application, includePreview: $pullRequestId !== null),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
@@ -3172,6 +3182,7 @@ class ApplicationsController extends Controller
|
||||
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
|
||||
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
|
||||
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
|
||||
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
|
||||
@@ -3278,6 +3289,10 @@ class ApplicationsController extends Controller
|
||||
|
||||
$this->authorize('update', $application);
|
||||
|
||||
if ($request->has('redact_all_env_values_in_logs')) {
|
||||
$this->authorize('manageEnvironment', $application);
|
||||
}
|
||||
|
||||
if ($request->instant_deploy) {
|
||||
abort_unless($request->user()->tokenCan('deploy') || $request->user()->tokenCan('root'), 403, 'Missing required permissions: deploy');
|
||||
$this->authorize('deploy', $application);
|
||||
|
||||
@@ -18,6 +18,7 @@ use App\Models\LocalPersistentVolume;
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledDatabaseBackupExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
@@ -355,11 +356,19 @@ class DatabasesController extends Controller
|
||||
}
|
||||
|
||||
$databaseIds = $databases->pluck('id')->toArray();
|
||||
$databasesByMorphKey = $databases->keyBy(fn ($database) => $database->getMorphClass().':'.$database->id);
|
||||
$messageSecrets = [];
|
||||
|
||||
$backupConfigs = ScheduledDatabaseBackup::ownedByCurrentTeamAPI($teamId)->with(['latest_log', 's3', 's3Storages'])
|
||||
->whereIn('database_id', $databaseIds)
|
||||
->get()
|
||||
->map(fn (ScheduledDatabaseBackup $backup) => $this->withBackupS3StorageUuids($backup))
|
||||
->map(function (ScheduledDatabaseBackup $backup) use ($databasesByMorphKey, &$messageSecrets): ScheduledDatabaseBackup {
|
||||
if ($backup->latest_log) {
|
||||
$this->redactBackupExecutionMessage($backup->latest_log, $databasesByMorphKey->get($backup->database_type.':'.$backup->database_id), $messageSecrets);
|
||||
}
|
||||
|
||||
return $this->withBackupS3StorageUuids($backup);
|
||||
})
|
||||
->groupBy(fn (ScheduledDatabaseBackup $backup) => $backup->database_type.':'.$backup->database_id);
|
||||
|
||||
$databases = $databases->map(function ($database) use ($backupConfigs) {
|
||||
@@ -431,8 +440,15 @@ class DatabasesController extends Controller
|
||||
|
||||
$this->authorize('view', $database);
|
||||
|
||||
$messageSecrets = [];
|
||||
$backupConfig = ScheduledDatabaseBackup::ownedByCurrentTeamAPI($teamId)->with(['executions', 's3', 's3Storages'])->where('database_id', $database->id)->get()
|
||||
->map(fn (ScheduledDatabaseBackup $backup) => $this->withBackupS3StorageUuids($backup));
|
||||
->map(function (ScheduledDatabaseBackup $backup) use ($database, &$messageSecrets): ScheduledDatabaseBackup {
|
||||
$backup->executions->each(function (ScheduledDatabaseBackupExecution $execution) use ($database, &$messageSecrets): void {
|
||||
$this->redactBackupExecutionMessage($execution, $database, $messageSecrets);
|
||||
});
|
||||
|
||||
return $this->withBackupS3StorageUuids($backup);
|
||||
});
|
||||
|
||||
return response()->json($backupConfig);
|
||||
}
|
||||
@@ -573,6 +589,7 @@ class DatabasesController extends Controller
|
||||
'health_check_timeout' => ['type' => 'integer', 'description' => 'Healthcheck timeout in seconds.', 'minimum' => 1, 'default' => 5],
|
||||
'health_check_retries' => ['type' => 'integer', 'description' => 'Healthcheck retries count.', 'minimum' => 1, 'default' => 5],
|
||||
'health_check_start_period' => ['type' => 'integer', 'description' => 'Healthcheck start period in seconds.', 'minimum' => 0, 'default' => 5],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values, database passwords, and remote secrets are redacted. Requires team admin permissions.'],
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -627,6 +644,7 @@ class DatabasesController extends Controller
|
||||
'limits_cpus' => 'string',
|
||||
'limits_cpuset' => 'string|nullable',
|
||||
'limits_cpu_shares' => 'numeric',
|
||||
'redact_all_env_values_in_logs' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@@ -636,6 +654,7 @@ class DatabasesController extends Controller
|
||||
], 422);
|
||||
}
|
||||
$uuid = $request->uuid;
|
||||
$redactAllEnvValuesInLogs = $request->has('redact_all_env_values_in_logs') ? $request->boolean('redact_all_env_values_in_logs') : null;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
if (! $database) {
|
||||
@@ -644,6 +663,10 @@ class DatabasesController extends Controller
|
||||
|
||||
$this->authorize('update', $database);
|
||||
|
||||
if ($redactAllEnvValuesInLogs !== null) {
|
||||
$this->authorize('manageEnvironment', $database);
|
||||
}
|
||||
|
||||
if ($request->is_public && $request->public_port) {
|
||||
if (isPublicPortAlreadyUsed($database->destination->server, $request->public_port, $database->id)) {
|
||||
return response()->json(['message' => 'Public port already used by another database.'], 400);
|
||||
@@ -876,6 +899,9 @@ class DatabasesController extends Controller
|
||||
}
|
||||
|
||||
// Only update database fields, not backup configuration
|
||||
if ($redactAllEnvValuesInLogs !== null) {
|
||||
$database->redact_all_env_values_in_logs = $redactAllEnvValuesInLogs;
|
||||
}
|
||||
$database->update($request->only($allowedFields));
|
||||
|
||||
if ($whatToDoWithDatabaseProxy === 'start') {
|
||||
@@ -889,7 +915,10 @@ class DatabasesController extends Controller
|
||||
'database_uuid' => $database->uuid,
|
||||
'database_name' => $database->name,
|
||||
'database_type' => $database->type(),
|
||||
'changed_fields' => array_values(array_intersect($allowedFields, array_keys($request->all()))),
|
||||
'changed_fields' => [
|
||||
...array_values(array_intersect($allowedFields, array_keys($request->all()))),
|
||||
...($redactAllEnvValuesInLogs === null ? [] : ['redact_all_env_values_in_logs']),
|
||||
],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
@@ -2783,7 +2812,10 @@ class DatabasesController extends Controller
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($database->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
$logs = sanitizeLogsForExport(
|
||||
getContainerLogs($database->destination->server, $container['ID'], $lines, $showTimestamps),
|
||||
EnvironmentVariable::logRedactionSecretsFor($database),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
@@ -3145,7 +3177,7 @@ class DatabasesController extends Controller
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List backup executions',
|
||||
description: 'Get all executions for a specific backup configuration.',
|
||||
description: 'Get all executions for a specific backup configuration. The execution `message` is only returned (redacted) to tokens with the `read:sensitive` or `root` ability; otherwise it is `null`.',
|
||||
path: '/databases/{uuid}/backups/{scheduled_backup_uuid}/executions',
|
||||
operationId: 'list-backup-executions',
|
||||
security: [
|
||||
@@ -3185,7 +3217,7 @@ class DatabasesController extends Controller
|
||||
new OA\Property(property: 'filename', type: 'string'),
|
||||
new OA\Property(property: 'size', type: 'integer'),
|
||||
new OA\Property(property: 'created_at', type: 'string'),
|
||||
new OA\Property(property: 'message', type: 'string'),
|
||||
new OA\Property(property: 'message', type: 'string', nullable: true, description: 'Redacted execution output. Null unless the token has the `read:sensitive` or `root` ability.'),
|
||||
new OA\Property(property: 'status', type: 'string'),
|
||||
]
|
||||
)
|
||||
@@ -3226,10 +3258,13 @@ class DatabasesController extends Controller
|
||||
}
|
||||
|
||||
// Get all executions for this backup configuration
|
||||
$messageSecrets = [];
|
||||
$executions = $backup->executions()
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
->map(function ($execution) {
|
||||
->map(function ($execution) use ($database, &$messageSecrets) {
|
||||
$this->redactBackupExecutionMessage($execution, $database, $messageSecrets);
|
||||
|
||||
return [
|
||||
'uuid' => $execution->uuid,
|
||||
'filename' => $execution->filename,
|
||||
@@ -5282,4 +5317,34 @@ class DatabasesController extends Controller
|
||||
'message' => 'Database cloned.',
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution output can embed credentials: hide it unless the token can read sensitive data, and redact it when shown.
|
||||
* The database secrets are loaded once per database and kept in $secretsByDatabase for the rest of the request.
|
||||
*
|
||||
* @param array<string, array{known: array<int, string>, locked: array<int, string>}|null> $secretsByDatabase
|
||||
*/
|
||||
private function redactBackupExecutionMessage(ScheduledDatabaseBackupExecution $execution, ?Model $database, array &$secretsByDatabase): void
|
||||
{
|
||||
$message = $execution->message;
|
||||
if (request()->attributes->get('can_read_sensitive', false) !== true || ! is_string($message) || ! $database) {
|
||||
$execution->setAttribute('message', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $database->getMorphClass().':'.$database->getKey();
|
||||
if (! array_key_exists($key, $secretsByDatabase)) {
|
||||
try {
|
||||
// Use a copy so the relations loaded for redaction do not end up in the response.
|
||||
$secretsByDatabase[$key] = EnvironmentVariable::logRedactionSecretsFor(clone $database);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
$secretsByDatabase[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Without the database secrets the message cannot be redacted reliably, so hide it.
|
||||
$execution->setAttribute('message', $secretsByDatabase[$key] === null ? null : sanitizeLogsForExport($message, $secretsByDatabase[$key]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Actions\Service\RestartServiceApplication;
|
||||
use App\Actions\Service\StopServiceApplication;
|
||||
use App\Actions\Service\UpdateServiceApplicationFromApi;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\ServerSetting;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
@@ -532,7 +533,10 @@ class ServiceApplicationsController extends Controller
|
||||
}
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$logs = getContainerLogs($server, $containerName, $lines);
|
||||
$logs = sanitizeLogsForExport(
|
||||
getContainerLogs($server, $containerName, $lines),
|
||||
EnvironmentVariable::logRedactionSecretsFor($serviceApplication->service),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Actions\Service\DeployServiceApplication;
|
||||
use App\Actions\Service\RestartServiceApplication;
|
||||
use App\Actions\Service\StopServiceApplication;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -401,7 +402,10 @@ class ServiceDatabasesController extends Controller
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
|
||||
return response()->json([
|
||||
'logs' => getContainerLogs($server, $containerName, $lines),
|
||||
'logs' => sanitizeLogsForExport(
|
||||
getContainerLogs($server, $containerName, $lines),
|
||||
EnvironmentVariable::logRedactionSecretsFor($serviceDatabase->service),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +321,7 @@ class ServicesController extends Controller
|
||||
],
|
||||
'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. If you want to use env variables inside the labels, turn this off.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'default' => true, 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the service.'],
|
||||
],
|
||||
),
|
||||
@@ -388,7 +389,7 @@ class ServicesController extends Controller
|
||||
)]
|
||||
public function create_service(Request $request)
|
||||
{
|
||||
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'tags'];
|
||||
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'redact_all_env_values_in_logs', 'tags'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -422,6 +423,7 @@ class ServicesController extends Controller
|
||||
'urls.*.url' => ValidationPatterns::applicationDomainRules(),
|
||||
'force_domain_override' => 'boolean',
|
||||
'is_container_label_escape_enabled' => 'boolean',
|
||||
'redact_all_env_values_in_logs' => 'boolean',
|
||||
'tags' => 'array|nullable',
|
||||
'tags.*' => 'string|min:2',
|
||||
];
|
||||
@@ -554,6 +556,9 @@ class ServicesController extends Controller
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
$service->is_container_label_escape_enabled = $request->boolean('is_container_label_escape_enabled');
|
||||
}
|
||||
if ($request->has('redact_all_env_values_in_logs')) {
|
||||
$service->redact_all_env_values_in_logs = $request->boolean('redact_all_env_values_in_logs');
|
||||
}
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||
@@ -622,7 +627,7 @@ class ServicesController extends Controller
|
||||
|
||||
return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404);
|
||||
} elseif (filled($request->docker_compose_raw)) {
|
||||
$allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'tags'];
|
||||
$allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'redact_all_env_values_in_logs', 'tags'];
|
||||
|
||||
$validationRules = [
|
||||
'project_uuid' => 'string|required',
|
||||
@@ -641,6 +646,7 @@ class ServicesController extends Controller
|
||||
'urls.*.url' => ValidationPatterns::applicationDomainRules(),
|
||||
'force_domain_override' => 'boolean',
|
||||
'is_container_label_escape_enabled' => 'boolean',
|
||||
'redact_all_env_values_in_logs' => 'boolean',
|
||||
'tags' => 'array|nullable',
|
||||
'tags.*' => 'string|min:2',
|
||||
];
|
||||
@@ -757,6 +763,9 @@ class ServicesController extends Controller
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
$service->is_container_label_escape_enabled = $request->boolean('is_container_label_escape_enabled');
|
||||
}
|
||||
if ($request->has('redact_all_env_values_in_logs')) {
|
||||
$service->redact_all_env_values_in_logs = $request->boolean('redact_all_env_values_in_logs');
|
||||
}
|
||||
$service->save();
|
||||
|
||||
$service->parse(isNew: true);
|
||||
@@ -985,7 +994,10 @@ class ServicesController extends Controller
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($service->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
$logs = sanitizeLogsForExport(
|
||||
getContainerLogs($service->destination->server, $container['ID'], $lines, $showTimestamps),
|
||||
EnvironmentVariable::logRedactionSecretsFor($service),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
@@ -1132,6 +1144,7 @@ class ServicesController extends Controller
|
||||
],
|
||||
'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. If you want to use env variables inside the labels, turn this off.'],
|
||||
'redact_all_env_values_in_logs' => ['type' => 'boolean', 'default' => true, 'description' => 'Redact all environment variable values in logs. When false, only locked (shown once) values are redacted. Requires team admin permissions.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -1220,12 +1233,16 @@ class ServicesController extends Controller
|
||||
|
||||
$this->authorize('update', $service);
|
||||
|
||||
if ($request->has('redact_all_env_values_in_logs')) {
|
||||
$this->authorize('manageEnvironment', $service);
|
||||
}
|
||||
|
||||
if ($request->boolean('instant_deploy')) {
|
||||
abort_unless($request->user()->tokenCan('deploy') || $request->user()->tokenCan('root'), 403, 'Missing required permissions: deploy');
|
||||
$this->authorize('deploy', $service);
|
||||
}
|
||||
|
||||
$allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override', 'is_container_label_escape_enabled'];
|
||||
$allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override', 'is_container_label_escape_enabled', 'redact_all_env_values_in_logs'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
@@ -1239,6 +1256,7 @@ class ServicesController extends Controller
|
||||
'urls.*.url' => ValidationPatterns::applicationDomainRules(),
|
||||
'force_domain_override' => 'boolean',
|
||||
'is_container_label_escape_enabled' => 'boolean',
|
||||
'redact_all_env_values_in_logs' => 'boolean',
|
||||
];
|
||||
$validationMessages = [
|
||||
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
|
||||
@@ -1306,6 +1324,9 @@ class ServicesController extends Controller
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
$service->is_container_label_escape_enabled = $request->boolean('is_container_label_escape_enabled');
|
||||
}
|
||||
if ($request->has('redact_all_env_values_in_logs')) {
|
||||
$service->redact_all_env_values_in_logs = $request->boolean('redact_all_env_values_in_logs');
|
||||
}
|
||||
$previousDnsHostnames = $this->serviceApplicationDnsHostnames($service);
|
||||
$service->save();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user