mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 09:34:36 +08:00
141 lines
4.6 KiB
PHP
141 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Services\DopplerService;
|
|
use App\Services\InfisicalService;
|
|
use App\Services\VaultService;
|
|
use App\Support\ValidationPatterns;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|
|
|
/**
|
|
* Links a resource (currently Application) to a remote secret manager source.
|
|
* Holds only the source coordinates — secret values are never persisted.
|
|
*/
|
|
class SecretManagerLink extends BaseModel
|
|
{
|
|
protected $fillable = [
|
|
'resourceable_type',
|
|
'resourceable_id',
|
|
'integration_token_id',
|
|
'settings',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'settings' => 'array',
|
|
];
|
|
}
|
|
|
|
public function resourceable(): MorphTo
|
|
{
|
|
return $this->morphTo();
|
|
}
|
|
|
|
public function integrationToken(): BelongsTo
|
|
{
|
|
return $this->belongsTo(IntegrationToken::class);
|
|
}
|
|
|
|
/**
|
|
* Fetch the secrets from the remote manager. Values live only in memory.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public function fetchSecrets(): array
|
|
{
|
|
$token = $this->integrationToken;
|
|
$teamId = $this->resourceable?->team()?->id;
|
|
if ($teamId === null || (int) $token->team_id !== (int) $teamId) {
|
|
throw new \RuntimeException('The secret manager token does not belong to the team of this resource.');
|
|
}
|
|
$settings = $this->settings ?? [];
|
|
$metadata = $token->metadata ?? [];
|
|
|
|
return match ($token->provider) {
|
|
'doppler' => (new DopplerService($token->token))->fetchSecrets(
|
|
data_get($settings, 'project'),
|
|
data_get($settings, 'config'),
|
|
),
|
|
'infisical' => (new InfisicalService(
|
|
data_get($metadata, 'base_url', 'https://app.infisical.com'),
|
|
(string) data_get($metadata, 'client_id'),
|
|
$token->token,
|
|
IntegrationToken::requestTimeout($metadata),
|
|
))->fetchSecrets(
|
|
(string) data_get($settings, 'project_id'),
|
|
(string) data_get($settings, 'environment'),
|
|
(string) data_get($settings, 'secret_path', '/'),
|
|
),
|
|
'vault' => (new VaultService(
|
|
(string) data_get($metadata, 'base_url'),
|
|
$token->token,
|
|
data_get($metadata, 'namespace'),
|
|
IntegrationToken::requestTimeout($metadata),
|
|
))->fetchSecrets(
|
|
(string) data_get($settings, 'mount', 'secret'),
|
|
(string) data_get($settings, 'path'),
|
|
),
|
|
default => throw new \RuntimeException("Unsupported secret manager provider [{$token->provider}]."),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create one {{vault.KEY}} reference variable per remote key that has no
|
|
* variable with that key yet. Only key names touch the database. Remote
|
|
* names that are not valid variable keys (e.g. `db-password`) are skipped
|
|
* as they are, never renamed.
|
|
*
|
|
* @return array{imported: list<string>, skipped: list<string>}
|
|
*/
|
|
public function importMissingReferences(): array
|
|
{
|
|
$keys = array_keys($this->fetchSecrets());
|
|
sort($keys);
|
|
|
|
$existing = $this->resourceable->environment_variables()->pluck('key')->flip();
|
|
$imported = [];
|
|
$skipped = [];
|
|
|
|
foreach ($keys as $key) {
|
|
$key = (string) $key;
|
|
|
|
if (! ValidationPatterns::isValidEnvironmentVariableKey($key)) {
|
|
$skipped[] = $key;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (isset($existing[$key])) {
|
|
continue;
|
|
}
|
|
|
|
$this->resourceable->environment_variables()->create([
|
|
'key' => $key,
|
|
'value' => '{{vault.'.$key.'}}',
|
|
]);
|
|
$imported[] = $key;
|
|
}
|
|
|
|
return ['imported' => $imported, 'skipped' => $skipped];
|
|
}
|
|
|
|
/** Short human-readable description of the remote source for the UI. */
|
|
public function sourceSummary(): string
|
|
{
|
|
$settings = $this->settings ?? [];
|
|
|
|
return match ($this->integrationToken->provider) {
|
|
'doppler' => trim(implode('/', array_filter([
|
|
data_get($settings, 'project'),
|
|
data_get($settings, 'config'),
|
|
])), '/') ?: 'token scope',
|
|
'infisical' => data_get($settings, 'project_id').'/'.data_get($settings, 'environment').data_get($settings, 'secret_path', '/'),
|
|
'vault' => data_get($settings, 'mount', 'secret').'/'.data_get($settings, 'path'),
|
|
default => '',
|
|
};
|
|
}
|
|
}
|