mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-24 10:05:47 -05:00
Add Doppler, Infisical, and Vault integrations with per-resource secret links, autocomplete, and deploy-time resolution for applications, services, and databases without persisting remote values.
173 lines
5.6 KiB
PHP
173 lines
5.6 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Security\IntegrationTokenForm;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\IntegrationToken;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
if (! InstanceSettings::query()->whereKey(0)->exists()) {
|
|
$settings = new InstanceSettings;
|
|
$settings->id = 0;
|
|
$settings->save();
|
|
}
|
|
|
|
$this->team = Team::factory()->create();
|
|
$this->user = User::factory()->create();
|
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
|
|
|
session(['currentTeam' => $this->team]);
|
|
$this->actingAs($this->user);
|
|
});
|
|
|
|
test('a doppler token is validated against the doppler api before it is saved', function () {
|
|
Http::fake([
|
|
'https://api.doppler.com/v3/me' => Http::response(['type' => 'service_token']),
|
|
]);
|
|
|
|
Livewire::test(IntegrationTokenForm::class, ['modal_mode' => true])
|
|
->set('provider', 'doppler')
|
|
->set('name', 'Production secrets')
|
|
->set('token', 'dp.st.token')
|
|
->call('addToken')
|
|
->assertHasNoErrors()
|
|
->assertDispatched('close-modal');
|
|
|
|
$this->assertDatabaseHas('integration_tokens', [
|
|
'team_id' => $this->team->id,
|
|
'provider' => 'doppler',
|
|
'name' => 'Production secrets',
|
|
]);
|
|
});
|
|
|
|
test('selecting a secret manager provider switches the capability to secrets', function () {
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'doppler')
|
|
->assertSet('capabilities', ['secrets'])
|
|
->set('provider', 'cloudflare')
|
|
->assertSet('capabilities', ['dns']);
|
|
});
|
|
|
|
test('an invalid doppler token is not saved', function () {
|
|
Http::fake([
|
|
'https://api.doppler.com/v3/me' => Http::response([], 401),
|
|
]);
|
|
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'doppler')
|
|
->set('name', 'Bad token')
|
|
->set('token', 'wrong')
|
|
->call('addToken')
|
|
->assertHasErrors(['token']);
|
|
|
|
$this->assertDatabaseCount('integration_tokens', 0);
|
|
});
|
|
|
|
test('doppler only accepts service and service account tokens', function (string $token) {
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'doppler')
|
|
->set('name', 'Unsupported token')
|
|
->set('token', $token)
|
|
->call('addToken')
|
|
->assertHasErrors(['token']);
|
|
|
|
$this->assertDatabaseCount('integration_tokens', 0);
|
|
})->with([
|
|
'personal token' => 'dp.pt.token',
|
|
'unknown token' => 'token',
|
|
]);
|
|
|
|
test('a doppler service account token is accepted', function () {
|
|
Http::fake([
|
|
'https://api.doppler.com/v3/me' => Http::response(['type' => 'service_account']),
|
|
]);
|
|
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'doppler')
|
|
->set('name', 'Shared secrets')
|
|
->set('token', 'dp.sa.token')
|
|
->call('addToken')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertDatabaseHas('integration_tokens', [
|
|
'provider' => 'doppler',
|
|
'name' => 'Shared secrets',
|
|
]);
|
|
});
|
|
|
|
test('an infisical token requires a base url and a client id', function () {
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'infisical')
|
|
->set('name', 'Infisical')
|
|
->set('token', 'client-secret')
|
|
->set('metadata', [])
|
|
->call('addToken')
|
|
->assertHasErrors(['metadata.base_url', 'metadata.client_id']);
|
|
|
|
$this->assertDatabaseCount('integration_tokens', 0);
|
|
});
|
|
|
|
test('the infisical fields put the client id before the client secret', function () {
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'infisical')
|
|
->assertSeeInOrder(['Token name', 'Client ID', 'Client secret', 'Base URL']);
|
|
});
|
|
|
|
test('an infisical token stores its metadata after a successful login', function () {
|
|
Http::fake([
|
|
'https://infisical.example.com/api/v1/auth/universal-auth/login' => Http::response([
|
|
'accessToken' => 'token',
|
|
]),
|
|
]);
|
|
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'infisical')
|
|
->set('name', 'Infisical')
|
|
->set('token', 'client-secret')
|
|
->set('metadata', ['base_url' => 'https://infisical.example.com', 'client_id' => 'client-1'])
|
|
->call('addToken')
|
|
->assertHasNoErrors();
|
|
|
|
$token = IntegrationToken::query()->where('provider', 'infisical')->firstOrFail();
|
|
|
|
expect($token->metadata)->toBe(['base_url' => 'https://infisical.example.com', 'client_id' => 'client-1'])
|
|
->and($token->capabilities)->toBe(['secrets']);
|
|
});
|
|
|
|
test('a vault token is validated with lookup-self before it is saved', function () {
|
|
Http::fake([
|
|
'https://vault.example.com:8200/v1/auth/token/lookup-self' => Http::response(['data' => []]),
|
|
]);
|
|
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'vault')
|
|
->set('name', 'Vault')
|
|
->set('token', 'hvs.token')
|
|
->set('metadata', ['base_url' => 'https://vault.example.com:8200'])
|
|
->call('addToken')
|
|
->assertHasNoErrors();
|
|
|
|
$this->assertDatabaseHas('integration_tokens', [
|
|
'provider' => 'vault',
|
|
'name' => 'Vault',
|
|
]);
|
|
});
|
|
|
|
test('the dns capability is rejected for secret manager providers', function () {
|
|
Livewire::test(IntegrationTokenForm::class)
|
|
->set('provider', 'doppler')
|
|
->set('name', 'Doppler')
|
|
->set('token', 'dp.st.token')
|
|
->set('capabilities', ['dns'])
|
|
->call('addToken')
|
|
->assertHasErrors(['capabilities.0']);
|
|
|
|
$this->assertDatabaseCount('integration_tokens', 0);
|
|
});
|