Files
coolify/app/Services/InfisicalService.php
Andras Bacsai 91d4467322 feat(secrets): resolve integrations across deployments and databases
Add secret manager integration links and API support, resolve referenced credentials in database startup commands, and improve environment variable handling and filtering.
2026-08-23 21:33:00 +02:00

90 lines
2.8 KiB
PHP

<?php
namespace App\Services;
use App\Rules\SafeExternalUrl;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Validator;
class InfisicalService
{
private string $baseUrl;
/** @var array<string, mixed> */
private array $httpClientOptions;
public function __construct(string $baseUrl, private string $clientId, private string $clientSecret)
{
$this->baseUrl = rtrim($baseUrl, '/');
Validator::make(['base_url' => $this->baseUrl], ['base_url' => new SafeExternalUrl])->validate();
$this->httpClientOptions = SafeExternalUrl::httpClientOptions($this->baseUrl);
}
public function validate(): bool
{
try {
$this->login();
return true;
} catch (\Throwable) {
return false;
}
}
/**
* @return array<string, string>
*/
public function fetchSecrets(string $projectId, string $environment, string $secretPath = '/'): array
{
$client = $this->client()->withToken($this->login());
$secretPath = $secretPath ?: '/';
$response = $client->get($this->baseUrl.'/api/v4/secrets', [
'projectId' => $projectId,
'environment' => $environment,
'secretPath' => $secretPath,
]);
// Older self-hosted instances only expose the v3 endpoint.
if ($response->status() === 404) {
$response = $client->get($this->baseUrl.'/api/v3/secrets/raw', [
'workspaceId' => $projectId,
'environment' => $environment,
'secretPath' => $secretPath,
]);
}
if (! $response->successful()) {
throw new \RuntimeException('Infisical API error: '.($response->json('message') ?? 'HTTP '.$response->status()));
}
return collect($response->json('secrets', []))
->mapWithKeys(fn ($secret) => [(string) data_get($secret, 'secretKey') => (string) data_get($secret, 'secretValue', '')])
->all();
}
private function login(): string
{
$response = $this->client()->post($this->baseUrl.'/api/v1/auth/universal-auth/login', [
'clientId' => $this->clientId,
'clientSecret' => $this->clientSecret,
]);
$accessToken = $response->json('accessToken');
if (! $response->successful() || blank($accessToken)) {
throw new \RuntimeException('Infisical login failed: '.($response->json('message') ?? 'HTTP '.$response->status()));
}
return $accessToken;
}
private function client(): PendingRequest
{
return Http::acceptJson()
->withOptions($this->httpClientOptions)
->connectTimeout(5)
->timeout(10);
}
}