fix(api): hide sensitive fields by default, expose via makeVisible for privileged tokens
Models now declare $hidden for passwords, tokens, db URLs, and compose fields. API controllers flip from makeHidden-on-deny to makeVisible-on- allow (can_read_sensitive=true), fixing fields that were never hidden. Also adds missing fields (mysql/mariadb passwords, logdrain keys, etc.) to privileged disclosure lists. Tests added: Feature/Security/ApiSensitiveFieldsTest and Unit/Models/SensitiveFieldsHiddenTest cover all affected models and controllers.
This commit is contained in:
parent
7ab16ad7b5
commit
8b7dbbafb2
19 changed files with 549 additions and 16 deletions
|
|
@ -43,8 +43,8 @@ private function removeSensitiveData($application)
|
|||
'resourceable_id',
|
||||
'resourceable_type',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$application->makeHidden([
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$application->makeVisible([
|
||||
'custom_labels',
|
||||
'dockerfile',
|
||||
'docker_compose',
|
||||
|
|
@ -53,10 +53,13 @@ private function removeSensitiveData($application)
|
|||
'manual_webhook_secret_gitea',
|
||||
'manual_webhook_secret_github',
|
||||
'manual_webhook_secret_gitlab',
|
||||
'private_key_id',
|
||||
'http_basic_auth_password',
|
||||
'value',
|
||||
'real_value',
|
||||
'http_basic_auth_password',
|
||||
]);
|
||||
} else {
|
||||
$application->makeHidden([
|
||||
'private_key_id',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,16 +33,21 @@ private function removeSensitiveData($database)
|
|||
'id',
|
||||
'laravel_through_key',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$database->makeHidden([
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$database->makeVisible([
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
'init_scripts',
|
||||
'postgres_password',
|
||||
'dragonfly_password',
|
||||
'redis_password',
|
||||
'mongo_initdb_root_password',
|
||||
'keydb_password',
|
||||
'clickhouse_admin_password',
|
||||
'mysql_password',
|
||||
'mysql_root_password',
|
||||
'mariadb_password',
|
||||
'mariadb_root_password',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -2957,8 +2962,8 @@ private function removeSensitiveEnvData($env)
|
|||
'resourceable_id',
|
||||
'resourceable_type',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$env->makeHidden([
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$env->makeVisible([
|
||||
'value',
|
||||
'real_value',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -22,9 +22,14 @@ class ServersController extends Controller
|
|||
{
|
||||
private function removeSensitiveDataFromSettings($settings)
|
||||
{
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$settings = $settings->makeHidden([
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$settings = $settings->makeVisible([
|
||||
'sentinel_token',
|
||||
'sentinel_custom_url',
|
||||
'logdrain_newrelic_license_key',
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_custom_config',
|
||||
'logdrain_custom_config_parser',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -36,8 +41,11 @@ private function removeSensitiveData($server)
|
|||
$server->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
// Do nothing
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$server->makeVisible([
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_newrelic_license_key',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($server);
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ private function removeSensitiveData($service)
|
|||
'resourceable_id',
|
||||
'resourceable_type',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$service->makeHidden([
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true) {
|
||||
$service->makeVisible([
|
||||
'docker_compose_raw',
|
||||
'docker_compose',
|
||||
'value',
|
||||
|
|
|
|||
|
|
@ -215,6 +215,24 @@ class Application extends BaseModel
|
|||
|
||||
protected $appends = ['server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability. Internal serializers (deployment
|
||||
* job, compose generation) must makeVisible explicitly before toArray().
|
||||
*/
|
||||
protected $hidden = [
|
||||
'http_basic_auth_password',
|
||||
'manual_webhook_secret_github',
|
||||
'manual_webhook_secret_gitlab',
|
||||
'manual_webhook_secret_bitbucket',
|
||||
'manual_webhook_secret_gitea',
|
||||
'dockerfile',
|
||||
'docker_compose',
|
||||
'docker_compose_raw',
|
||||
'custom_labels',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -76,9 +76,19 @@ class EnvironmentVariable extends BaseModel
|
|||
|
||||
protected $appends = ['real_value', 'is_shared', 'is_really_required', 'is_nixpacks', 'is_coolify'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'value',
|
||||
'real_value',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function (EnvironmentVariable $environment_variable) {
|
||||
static::created(function (ModelsEnvironmentVariable $environment_variable) {
|
||||
if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) {
|
||||
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)
|
||||
->where('resourceable_type', Application::class)
|
||||
|
|
@ -109,7 +119,7 @@ protected static function booted()
|
|||
]);
|
||||
});
|
||||
|
||||
static::saving(function (EnvironmentVariable $environmentVariable) {
|
||||
static::saving(function (ModelsEnvironmentVariable $environmentVariable) {
|
||||
$environmentVariable->updateIsShared();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,16 @@ public static function flushIdentityMap(): void
|
|||
'force_disabled' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_newrelic_license_key',
|
||||
];
|
||||
|
||||
protected $schemalessAttributes = [
|
||||
'proxy',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -113,6 +113,20 @@ class ServerSetting extends Model
|
|||
'connection_timeout' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'sentinel_token',
|
||||
'sentinel_custom_url',
|
||||
'logdrain_newrelic_license_key',
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_custom_config',
|
||||
'logdrain_custom_config_parser',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($setting) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,17 @@ class Service extends BaseModel
|
|||
|
||||
protected $appends = ['server_status', 'status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability. Internal compose generators
|
||||
* must makeVisible explicitly before toArray().
|
||||
*/
|
||||
protected $hidden = [
|
||||
'docker_compose',
|
||||
'docker_compose_raw',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($service) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ class StandaloneClickhouse extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'clickhouse_admin_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'clickhouse_admin_password' => 'encrypted',
|
||||
'public_port_timeout' => 'integer',
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ class StandaloneDragonfly extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'dragonfly_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'dragonfly_password' => 'encrypted',
|
||||
'public_port_timeout' => 'integer',
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ class StandaloneKeydb extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'keydb_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'keydb_password' => 'encrypted',
|
||||
'public_port_timeout' => 'integer',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ class StandaloneMariadb extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'mariadb_password',
|
||||
'mariadb_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'mariadb_password' => 'encrypted',
|
||||
'public_port_timeout' => 'integer',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,17 @@ class StandaloneMongodb extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'mongo_initdb_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'public_port_timeout' => 'integer',
|
||||
'restart_count' => 'integer',
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@ class StandaloneMysql extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'mysql_password',
|
||||
'mysql_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'mysql_password' => 'encrypted',
|
||||
'mysql_root_password' => 'encrypted',
|
||||
|
|
|
|||
|
|
@ -54,6 +54,18 @@ class StandalonePostgresql extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'postgres_password',
|
||||
'init_scripts',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'init_scripts' => 'array',
|
||||
'postgres_password' => 'encrypted',
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ class StandaloneRedis extends BaseModel
|
|||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
/**
|
||||
* Sensitive fields hidden by default in serialized output (toArray/toJson).
|
||||
* API controllers should call makeVisible([...]) for callers with the
|
||||
* `read:sensitive` or `root` token ability.
|
||||
*/
|
||||
protected $hidden = [
|
||||
'redis_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'public_port_timeout' => 'integer',
|
||||
'restart_count' => 'integer',
|
||||
|
|
|
|||
184
tests/Feature/Security/ApiSensitiveFieldsTest.php
Normal file
184
tests/Feature/Security/ApiSensitiveFieldsTest.php
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
function makeApiToken(User $user, Team $team, array $abilities): string
|
||||
{
|
||||
session(['currentTeam' => $team]);
|
||||
$token = $user->createToken('sensitive-test', $abilities);
|
||||
DB::table('personal_access_tokens')->where('id', $token->accessToken->id)->update([
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
return $token->plainTextToken;
|
||||
}
|
||||
|
||||
function makeTeamUser(): array
|
||||
{
|
||||
$team = Team::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
$team->members()->attach($user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
return [$team, $user];
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::query()->delete();
|
||||
$settings = new InstanceSettings;
|
||||
$settings->id = 0;
|
||||
$settings->save();
|
||||
|
||||
[$this->team, $this->user] = makeTeamUser();
|
||||
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->server->settings->forceFill([
|
||||
'sentinel_token' => encrypt('super-secret-sentinel-token'),
|
||||
'sentinel_custom_url' => 'https://sentinel.internal',
|
||||
'logdrain_axiom_api_key' => encrypt('axiom-key-secret'),
|
||||
'logdrain_newrelic_license_key' => encrypt('newrelic-key-secret'),
|
||||
'logdrain_custom_config' => 'custom-config-data',
|
||||
'logdrain_custom_config_parser' => 'custom-parser-data',
|
||||
])->saveQuietly();
|
||||
});
|
||||
|
||||
describe('GET /api/v1/servers sensitive field gating', function () {
|
||||
test('read token does not leak sentinel or logdrain fields', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/servers');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->not->toContain('sentinel_token');
|
||||
expect($body)->not->toContain('sentinel_custom_url');
|
||||
expect($body)->not->toContain('logdrain_axiom_api_key');
|
||||
expect($body)->not->toContain('logdrain_newrelic_license_key');
|
||||
expect($body)->not->toContain('logdrain_custom_config');
|
||||
});
|
||||
|
||||
test('read sensitive token sees sentinel and logdrain fields', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['read', 'read:sensitive']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/servers');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->toContain('sentinel_token');
|
||||
expect($body)->toContain('sentinel_custom_url');
|
||||
expect($body)->toContain('logdrain_axiom_api_key');
|
||||
});
|
||||
|
||||
test('root token sees sentinel and logdrain fields', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['root']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/servers');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->toContain('sentinel_token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/v1/applications nested-relation scrubbing', function () {
|
||||
beforeEach(function () {
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
$destination = $this->server->standaloneDockers()->firstOrFail();
|
||||
|
||||
$this->application = Application::create([
|
||||
'name' => 'sensitive-test-app',
|
||||
'git_repository' => 'https://github.com/test/test',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
});
|
||||
|
||||
test('read token does not leak sentinel_token via destination.server.settings', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/applications');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->not->toContain('sentinel_token');
|
||||
expect($body)->not->toContain('sentinel_custom_url');
|
||||
expect($body)->not->toContain('logdrain_axiom_api_key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/v1/databases sensitive field gating', function () {
|
||||
beforeEach(function () {
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
$destination = $this->server->standaloneDockers()->firstOrFail();
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'name' => 'sensitive-db',
|
||||
'description' => 'test',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => encrypt('super-secret-db-password'),
|
||||
'postgres_db' => 'app',
|
||||
'image' => 'postgres:16-alpine',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
});
|
||||
|
||||
test('read token does not leak postgres_password or db urls', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/databases');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->not->toContain('postgres_password');
|
||||
expect($body)->not->toContain('internal_db_url');
|
||||
expect($body)->not->toContain('external_db_url');
|
||||
});
|
||||
|
||||
test('read sensitive token sees postgres_password', function () {
|
||||
$token = makeApiToken($this->user, $this->team, ['read', 'read:sensitive']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token,
|
||||
])->getJson('/api/v1/databases');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$body = $response->getContent();
|
||||
expect($body)->toContain('postgres_password');
|
||||
expect($body)->toContain('internal_db_url');
|
||||
});
|
||||
});
|
||||
179
tests/Unit/Models/SensitiveFieldsHiddenTest.php
Normal file
179
tests/Unit/Models/SensitiveFieldsHiddenTest.php
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerSetting;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
|
||||
describe('Sensitive model fields are hidden by default', function () {
|
||||
test('ServerSetting hides sentinel and logdrain secrets', function () {
|
||||
$hidden = (new ServerSetting)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'sentinel_token',
|
||||
'sentinel_custom_url',
|
||||
'logdrain_newrelic_license_key',
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_custom_config',
|
||||
'logdrain_custom_config_parser',
|
||||
);
|
||||
});
|
||||
|
||||
test('Server hides logdrain api keys', function () {
|
||||
$hidden = (new Server)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'logdrain_axiom_api_key',
|
||||
'logdrain_newrelic_license_key',
|
||||
);
|
||||
});
|
||||
|
||||
test('Application hides webhook secrets, dockerfile, compose, and labels', function () {
|
||||
$hidden = (new Application)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'http_basic_auth_password',
|
||||
'manual_webhook_secret_github',
|
||||
'manual_webhook_secret_gitlab',
|
||||
'manual_webhook_secret_bitbucket',
|
||||
'manual_webhook_secret_gitea',
|
||||
'dockerfile',
|
||||
'docker_compose',
|
||||
'docker_compose_raw',
|
||||
'custom_labels',
|
||||
);
|
||||
});
|
||||
|
||||
test('EnvironmentVariable hides value and real_value', function () {
|
||||
$hidden = (new EnvironmentVariable)->getHidden();
|
||||
|
||||
expect($hidden)->toContain('value', 'real_value');
|
||||
});
|
||||
|
||||
test('Service hides docker_compose and docker_compose_raw', function () {
|
||||
$hidden = (new Service)->getHidden();
|
||||
|
||||
expect($hidden)->toContain('docker_compose', 'docker_compose_raw');
|
||||
});
|
||||
|
||||
test('StandalonePostgresql hides password, init_scripts, db urls', function () {
|
||||
$hidden = (new StandalonePostgresql)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'postgres_password',
|
||||
'init_scripts',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneMysql hides passwords and db urls', function () {
|
||||
$hidden = (new StandaloneMysql)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'mysql_password',
|
||||
'mysql_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneMariadb hides passwords and db urls', function () {
|
||||
$hidden = (new StandaloneMariadb)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'mariadb_password',
|
||||
'mariadb_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneMongodb hides root password and db urls', function () {
|
||||
$hidden = (new StandaloneMongodb)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'mongo_initdb_root_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneRedis hides password and db urls', function () {
|
||||
$hidden = (new StandaloneRedis)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'redis_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneClickhouse hides password and db urls', function () {
|
||||
$hidden = (new StandaloneClickhouse)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'clickhouse_admin_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneKeydb hides password and db urls', function () {
|
||||
$hidden = (new StandaloneKeydb)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'keydb_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
|
||||
test('StandaloneDragonfly hides password and db urls', function () {
|
||||
$hidden = (new StandaloneDragonfly)->getHidden();
|
||||
|
||||
expect($hidden)->toContain(
|
||||
'dragonfly_password',
|
||||
'internal_db_url',
|
||||
'external_db_url',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sensitive fields are absent from toArray() by default', function () {
|
||||
test('ServerSetting::toArray() excludes sentinel_custom_url by default', function () {
|
||||
$setting = new ServerSetting;
|
||||
$setting->setRawAttributes([
|
||||
'server_id' => 1,
|
||||
'sentinel_custom_url' => 'https://secret.example.com',
|
||||
'wildcard_domain' => 'public.example.com',
|
||||
], sync: true);
|
||||
|
||||
$array = $setting->toArray();
|
||||
|
||||
expect($array)->not->toHaveKey('sentinel_custom_url');
|
||||
expect($array)->toHaveKey('wildcard_domain');
|
||||
});
|
||||
|
||||
test('ServerSetting::toArray() includes sentinel_custom_url after makeVisible', function () {
|
||||
$setting = new ServerSetting;
|
||||
$setting->setRawAttributes([
|
||||
'server_id' => 1,
|
||||
'sentinel_custom_url' => 'https://secret.example.com',
|
||||
], sync: true);
|
||||
|
||||
$setting->makeVisible(['sentinel_custom_url']);
|
||||
$array = $setting->toArray();
|
||||
|
||||
expect($array['sentinel_custom_url'])->toBe('https://secret.example.com');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue