mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
Cloning an application, database, service or environment did not copy the secret manager link, so variables with remote secret references failed on the next deployment with "no secret manager source is configured". Clones now get a copy of the link when the integration token belongs to the clone's team. Server transfer does not export integration tokens or links; the export now warns which resources need the token and link again on the target instance.
131 lines
4.4 KiB
PHP
131 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Traits;
|
|
|
|
use App\Models\EnvironmentVariable;
|
|
use App\Models\SecretManagerLink;
|
|
use App\Support\RemoteSecretReferences;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
|
use RuntimeException;
|
|
|
|
trait HasSecretManager
|
|
{
|
|
/** @var array<string, string>|null */
|
|
private ?array $resolvedSecretManagerValues = null;
|
|
|
|
public static function bootHasSecretManager(): void
|
|
{
|
|
// Return nothing: a non-null result halts the "deleting" event and skips later listeners.
|
|
static::deleting(function ($resource): void {
|
|
$resource->secretManagerLink()->delete();
|
|
});
|
|
}
|
|
|
|
public function secretManagerLink(): MorphOne
|
|
{
|
|
return $this->morphOne(SecretManagerLink::class, 'resourceable');
|
|
}
|
|
|
|
/**
|
|
* Give a cloned resource the same secret manager source. The token is team-scoped, so a clone in another team gets no link.
|
|
*/
|
|
public function cloneSecretManagerLinkTo(Model $target): void
|
|
{
|
|
$link = $this->secretManagerLink()->with('integrationToken')->first();
|
|
// fresh(): a replicated model keeps the relations of its source, such as the old environment.
|
|
$targetTeamId = $target->fresh()?->team()?->id;
|
|
|
|
if (! $link || $targetTeamId === null || (int) $link->integrationToken?->team_id !== (int) $targetTeamId) {
|
|
return;
|
|
}
|
|
|
|
$target->secretManagerLink()->create([
|
|
'integration_token_id' => $link->integration_token_id,
|
|
'settings' => $link->settings,
|
|
]);
|
|
}
|
|
|
|
public function resolveSecretManagerEnvironmentVariable(EnvironmentVariable $environmentVariable): ?string
|
|
{
|
|
$value = $this->resolveSecretManagerEnvironmentVariableValue($environmentVariable);
|
|
|
|
return $this->formatEnvironmentVariableValue($environmentVariable, $value);
|
|
}
|
|
|
|
public function formatEnvironmentVariableValue(EnvironmentVariable $environmentVariable, ?string $value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
if (json_validate($value) && (str_starts_with($value, '{') || str_starts_with($value, '['))) {
|
|
return $value;
|
|
}
|
|
|
|
return $environmentVariable->is_literal || $environmentVariable->is_multiline
|
|
? "'{$value}'"
|
|
: escapeEnvVariables($value);
|
|
}
|
|
|
|
public function resolveSecretManagerEnvironmentVariableValue(EnvironmentVariable $environmentVariable): ?string
|
|
{
|
|
$value = $this->resolvedEnvironmentVariableValue($environmentVariable);
|
|
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
if (RemoteSecretReferences::containsReference($value)) {
|
|
$secrets = $this->secretManagerValues();
|
|
$missing = RemoteSecretReferences::missingKeys($value, $secrets);
|
|
|
|
if ($missing !== []) {
|
|
throw new RuntimeException('Missing secret keys: '.implode(', ', $missing)." (referenced by {$environmentVariable->key}).");
|
|
}
|
|
|
|
$value = RemoteSecretReferences::substitute($value, $secrets);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function environmentVariableUsesSecretManager(EnvironmentVariable $environmentVariable): bool
|
|
{
|
|
return RemoteSecretReferences::containsReference(
|
|
$this->resolvedEnvironmentVariableValue($environmentVariable),
|
|
);
|
|
}
|
|
|
|
private function resolvedEnvironmentVariableValue(EnvironmentVariable $environmentVariable): ?string
|
|
{
|
|
return $environmentVariable->get_real_environment_variables_with_server(
|
|
$environmentVariable->value,
|
|
$this,
|
|
data_get($this, 'server'),
|
|
);
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
private function secretManagerValues(): array
|
|
{
|
|
if ($this->resolvedSecretManagerValues !== null) {
|
|
return $this->resolvedSecretManagerValues;
|
|
}
|
|
|
|
$link = $this->secretManagerLink()->with('integrationToken')->first();
|
|
|
|
if (! $link) {
|
|
throw new RuntimeException('Environment variables reference remote secrets, but no secret manager source is configured.');
|
|
}
|
|
|
|
return $this->resolvedSecretManagerValues = $link->fetchSecrets();
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public function resolvedSecretManagerValuesForRedaction(): array
|
|
{
|
|
return $this->resolvedSecretManagerValues ?? [];
|
|
}
|
|
}
|