fix(databases): show custom configuration only to admins (#12353)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-10-27 22:34:47 +01:00
committed by GitHub
co-authored by Claude Opus 5.5
parent bc0cb1b37d
commit fae2752346
29 changed files with 328 additions and 62 deletions
@@ -251,6 +251,7 @@ class DatabasesController extends Controller
'mysql_root_password',
'mariadb_password',
'mariadb_root_password',
...$database::CONFIGURATION_ATTRIBUTES,
]);
$this->exposeNestedServerSecrets($database);
} else {
@@ -137,17 +137,17 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->clickhouseAdminUser = $this->database->clickhouse_admin_user;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->clickhouseAdminPassword = $canSeeCredentials ? $this->database->clickhouse_admin_password : '';
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
}
}
@@ -124,16 +124,16 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->dragonflyPassword = $canSeeCredentials ? $this->database->dragonfly_password : '';
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
}
}
@@ -128,9 +128,9 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->keydbConf = $canSeeCredentials ? $this->database->keydb_conf : null;
$this->keydbPassword = $canSeeCredentials ? $this->database->keydb_password : '';
$this->image = $this->database->image;
@@ -138,7 +138,7 @@ class General extends Component
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
}
}
@@ -153,9 +153,9 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->mariadbRootPassword = $canSeeCredentials ? $this->database->mariadb_root_password : '';
$this->mariadbUser = $this->database->mariadb_user;
$this->mariadbPassword = $canSeeCredentials ? $this->database->mariadb_password : '';
@@ -167,7 +167,7 @@ class General extends Component
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
}
}
@@ -145,9 +145,9 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->mongoConf = $canSeeCredentials ? $this->database->mongo_conf : null;
$this->mongoInitdbRootUsername = $this->database->mongo_initdb_root_username;
$this->mongoInitdbRootPassword = $canSeeCredentials ? $this->database->mongo_initdb_root_password : '';
@@ -158,7 +158,7 @@ class General extends Component
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
}
}
@@ -153,9 +153,9 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->mysqlRootPassword = $canSeeCredentials ? $this->database->mysql_root_password : '';
$this->mysqlUser = $this->database->mysql_user;
$this->mysqlPassword = $canSeeCredentials ? $this->database->mysql_password : '';
@@ -167,7 +167,7 @@ class General extends Component
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
}
}
@@ -169,10 +169,10 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->postgresUser = $this->database->postgres_user;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->postgresPassword = $canSeeCredentials ? $this->database->postgres_password : '';
$this->postgresDb = $this->database->postgres_db;
$this->postgresInitdbArgs = $this->database->postgres_initdb_args;
@@ -185,7 +185,7 @@ class General extends Component
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
}
}
@@ -146,6 +146,7 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->image = $this->database->image;
@@ -154,10 +155,9 @@ class General extends Component
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
$this->redisVersion = $this->database->getRedisVersion();
$this->redisUsername = $this->database->redis_username;
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->redisConf = $canSeeCredentials ? $this->database->redis_conf : null;
$this->isPasswordLocked = $this->database->isCredentialLocked();
$this->redisPassword = $canSeeCredentials && ! $this->isPasswordLocked ? $this->database->redis_password : null;
@@ -29,6 +29,8 @@ class General extends Component
public bool $isLogDrainEnabled = false;
public bool $isPasswordHiddenForMember = false;
public function mount()
{
try {
@@ -43,6 +45,8 @@ class General extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->isPasswordHiddenForMember = ! (auth()->user()?->can('update', $this->database) ?? false);
}
protected function rules(): array
@@ -83,11 +87,12 @@ class General extends Component
$this->database->save();
$this->dispatch('name-changed', uuid: $this->database->uuid, name: $this->database->name);
} else {
$canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false;
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->sqliteDatabases = $this->database->sqlite_databases;
$this->image = $this->database->image;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->customDockerRunOptions = $canSeeCredentials ? $this->database->custom_docker_run_options : null;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
}
}
+9
View File
@@ -62,6 +62,14 @@ class StandaloneClickhouse extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -69,6 +77,7 @@ class StandaloneClickhouse extends BaseModel
*/
protected $hidden = [
'clickhouse_admin_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
];
+9
View File
@@ -56,6 +56,14 @@ class StandaloneDragonfly extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -63,6 +71,7 @@ class StandaloneDragonfly extends BaseModel
*/
protected $hidden = [
'dragonfly_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
// Internal start-command state, not part of the API.
+10
View File
@@ -57,6 +57,15 @@ class StandaloneKeydb extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'keydb_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -64,6 +73,7 @@ class StandaloneKeydb extends BaseModel
*/
protected $hidden = [
'keydb_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
// Internal start-command state, not part of the API.
+10
View File
@@ -60,6 +60,15 @@ class StandaloneMariadb extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'mariadb_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -68,6 +77,7 @@ class StandaloneMariadb extends BaseModel
protected $hidden = [
'mariadb_password',
'mariadb_root_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
];
+10
View File
@@ -60,6 +60,15 @@ class StandaloneMongodb extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'mongo_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -67,6 +76,7 @@ class StandaloneMongodb extends BaseModel
*/
protected $hidden = [
'mongo_initdb_root_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
];
+10
View File
@@ -61,6 +61,15 @@ class StandaloneMysql extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'mysql_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -69,6 +78,7 @@ class StandaloneMysql extends BaseModel
protected $hidden = [
'mysql_password',
'mysql_root_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
];
+10
View File
@@ -63,6 +63,15 @@ class StandalonePostgresql extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'postgres_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -71,6 +80,7 @@ class StandalonePostgresql extends BaseModel
protected $hidden = [
'postgres_password',
'init_scripts',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
];
+10
View File
@@ -58,6 +58,15 @@ class StandaloneRedis extends BaseModel
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'redis_conf',
'custom_docker_run_options',
];
/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
@@ -65,6 +74,7 @@ class StandaloneRedis extends BaseModel
*/
protected $hidden = [
'redis_password',
...self::CONFIGURATION_ATTRIBUTES,
'internal_db_url',
'external_db_url',
// Internal start-command state, not part of the API.
+17
View File
@@ -57,6 +57,23 @@ class StandaloneSqlite extends BaseModel
'health_check_start_period',
];
/**
* Free-form configuration that can embed credentials. Only users who can
* update the database and `read:sensitive` tokens may read it.
*/
public const CONFIGURATION_ATTRIBUTES = [
'custom_docker_run_options',
];
/**
* 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 = [
...self::CONFIGURATION_ATTRIBUTES,
];
protected $appends = ['database_type', 'server_status'];
protected $casts = [
@@ -104,11 +104,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="log-delivery-section" title="Log delivery" helper="Forward container logs to the drain configured on the server.">
<x-forms.listbox canGate="update" :canResource="$database" id="isLogDrainEnabled" label="Log drain" live onChange="instantSaveAdvanced"
:disabled="! auth()->user()->can('update', $database)" :options="[
@@ -82,11 +82,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="log-delivery-section" title="Log delivery" helper="Forward container logs to the drain configured on the server.">
<x-forms.listbox canGate="update" :canResource="$database" id="isLogDrainEnabled" label="Log drain" live onChange="instantSaveAdvanced"
:disabled="! auth()->user()->can('update', $database)" :options="[
@@ -83,11 +83,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override only the KeyDB directives you need. All other defaults remain active.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom KeyDB configuration" disabled value="Hidden (only admins can view)" />
@@ -88,11 +88,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override the MariaDB configuration used by this container.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom MariaDB configuration" disabled value="Hidden (only admins can view)" />
@@ -85,11 +85,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override the MongoDB configuration used by this container.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom MongoDB configuration" disabled value="Hidden (only admins can view)" />
@@ -88,11 +88,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override the MySQL configuration used by this container.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom MySQL configuration" disabled value="Hidden (only admins can view)" />
@@ -90,11 +90,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override the PostgreSQL configuration used by this container.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom PostgreSQL configuration" disabled value="Hidden (only admins can view)" />
@@ -90,11 +90,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options, configuration, and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="configuration-section" title="Configuration" helper="Override only the Redis directives you need. All other defaults remain active.">
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Redis configuration" disabled value="Hidden (only admins can view)" />
@@ -31,11 +31,15 @@
<x-application.settings-section id="container-section" title="Container"
description="Docker options and log forwarding for the database container.">
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@if ($isPasswordHiddenForMember)
<x-forms.input label="Custom Docker options" disabled value="Hidden (only admins can view)" />
@else
<x-forms.input
helper="Add supported docker run options used when the container starts. Unsupported options can interfere with Coolify automation."
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse"
id="customDockerRunOptions" label="Custom Docker options" canGate="update"
:canResource="$database" />
@endif
<x-application.settings-subsection id="log-delivery-section" title="Log delivery" helper="Forward container logs to the drain configured on the server.">
<x-forms.listbox canGate="update" :canResource="$database" id="isLogDrainEnabled" label="Log drain" live onChange="instantSaveAdvanced"
:disabled="! auth()->user()->can('update', $database)" :options="[
@@ -1,5 +1,7 @@
<?php
use App\Livewire\Project\Database\Clickhouse\General as ClickhouseGeneral;
use App\Livewire\Project\Database\Dragonfly\General as DragonflyGeneral;
use App\Livewire\Project\Database\Dragonfly\StatusInfo as DragonflyStatusInfo;
use App\Livewire\Project\Database\Keydb\General as KeydbGeneral;
use App\Livewire\Project\Database\Keydb\StatusInfo as KeydbStatusInfo;
@@ -9,6 +11,7 @@ use App\Livewire\Project\Database\Mysql\General as MysqlGeneral;
use App\Livewire\Project\Database\Postgresql\General as PostgresqlGeneral;
use App\Livewire\Project\Database\Redis\General as RedisGeneral;
use App\Livewire\Project\Database\Redis\StatusInfo as RedisStatusInfo;
use App\Livewire\Project\Database\Sqlite\General as SqliteGeneral;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\InstanceSettings;
@@ -239,3 +242,129 @@ it('still returns the connection url of an unlocked redis password in the api',
->assertOk()
->assertJsonPath('internal_db_url', "redis://default:visible-redis-secret@{$redis->uuid}:6379/0");
});
dataset('database-api-config', [
'postgresql' => ['create_standalone_postgresql', 'postgresqls', ['postgres_conf' => "primary_conninfo = 'password=secret-config-value'"]],
'mysql' => ['create_standalone_mysql', 'mysqls', ['mysql_conf' => 'password=secret-config-value']],
'mariadb' => ['create_standalone_mariadb', 'mariadbs', ['mariadb_conf' => 'password=secret-config-value']],
'mongodb' => ['create_standalone_mongodb', 'mongodbs', ['mongo_conf' => 'keyFile: secret-config-value']],
'redis' => ['create_standalone_redis', 'redis', ['redis_conf' => 'masterauth secret-config-value']],
'keydb' => ['create_standalone_keydb', null, ['keydb_conf' => 'masterauth secret-config-value']],
'dragonfly' => ['create_standalone_dragonfly', null, []],
'clickhouse' => ['create_standalone_clickhouse', null, []],
]);
function apiTokenHeaders(User $user, Team $team, array $abilities): array
{
$token = $user->createToken('config-test', $abilities);
DB::table('personal_access_tokens')->where('id', $token->accessToken->id)->update(['team_id' => $team->id]);
return ['Authorization' => 'Bearer '.$token->plainTextToken];
}
it('hides custom configs and docker run options from a token without sensitive read access', function (string $createDatabase, ?string $environmentRelation, array $config) {
$database = createDatabaseWithConfig($createDatabase, [
...$config,
'custom_docker_run_options' => '--env API_TOKEN=secret-docker-value',
]);
session(['currentTeam' => $this->team]);
$project = $this->environment->project;
foreach ([
[$this->member, ['read']],
[$this->member, ['read', 'read:sensitive']],
[$this->owner, ['read']],
] as [$user, $abilities]) {
$headers = apiTokenHeaders($user, $this->team, $abilities);
$single = $this->withHeaders($headers)->getJson("/api/v1/databases/{$database->uuid}")->assertOk();
expect($single->json('uuid'))->toBe($database->uuid)
->and($single->getContent())->not->toContain('secret-config-value')->not->toContain('secret-docker-value');
$list = $this->withHeaders($headers)->getJson('/api/v1/databases')->assertOk();
expect($list->getContent())->toContain($database->uuid)
->not->toContain('secret-config-value')->not->toContain('secret-docker-value');
if ($environmentRelation !== null) {
$environment = $this->withHeaders($headers)->getJson("/api/v1/projects/{$project->uuid}/{$this->environment->uuid}")->assertOk();
expect($environment->getContent())->toContain($database->uuid)
->not->toContain('secret-config-value')->not->toContain('secret-docker-value');
}
}
})->with('database-api-config');
it('returns custom configs and docker run options to an owner token with sensitive read access', function (string $createDatabase, ?string $environmentRelation, array $config) {
$database = createDatabaseWithConfig($createDatabase, [
...$config,
'custom_docker_run_options' => '--env API_TOKEN=secret-docker-value',
]);
session(['currentTeam' => $this->team]);
$headers = apiTokenHeaders($this->owner, $this->team, ['read', 'read:sensitive']);
$single = $this->withHeaders($headers)->getJson("/api/v1/databases/{$database->uuid}")->assertOk()
->assertJsonPath('custom_docker_run_options', '--env API_TOKEN=secret-docker-value');
foreach ($config as $key => $value) {
$single->assertJsonPath($key, $value);
}
if ($environmentRelation !== null) {
$project = $this->environment->project;
$this->withHeaders($headers)->getJson("/api/v1/projects/{$project->uuid}/{$this->environment->uuid}")->assertOk()
->assertJsonPath("{$environmentRelation}.0.custom_docker_run_options", '--env API_TOKEN=secret-docker-value');
}
})->with('database-api-config');
dataset('database-docker-options-components', [
'postgresql' => ['create_standalone_postgresql', PostgresqlGeneral::class],
'mysql' => ['create_standalone_mysql', MysqlGeneral::class],
'mariadb' => ['create_standalone_mariadb', MariadbGeneral::class],
'mongodb' => ['create_standalone_mongodb', MongodbGeneral::class],
'redis' => ['create_standalone_redis', RedisGeneral::class],
'keydb' => ['create_standalone_keydb', KeydbGeneral::class],
'dragonfly' => ['create_standalone_dragonfly', DragonflyGeneral::class],
'clickhouse' => ['create_standalone_clickhouse', ClickhouseGeneral::class],
'sqlite' => ['create_standalone_sqlite', SqliteGeneral::class],
]);
it('keeps custom docker run options out of member state and html', function (string $createDatabase, string $generalClass) {
$database = createDatabaseWithConfig($createDatabase, ['custom_docker_run_options' => '--env API_TOKEN=secret-docker-value']);
$ownTeam = Team::factory()->create();
$ownTeam->members()->attach($this->member->id, ['role' => 'owner']);
$this->actingAs($this->member);
foreach ([$this->team, $ownTeam] as $sessionTeam) {
session(['currentTeam' => $sessionTeam]);
$general = Livewire::test($generalClass, ['database' => $database])
->assertSet('customDockerRunOptions', null);
expect(json_encode($general->snapshot))->not->toContain('secret-docker-value')
->and($general->html())->not->toContain('secret-docker-value')
->and($general->html())->toContain('Hidden (only admins can view)');
}
})->with('database-docker-options-components');
it('keeps custom docker run options available to an owner', function (string $createDatabase, string $generalClass) {
$database = createDatabaseWithConfig($createDatabase, ['custom_docker_run_options' => '--env API_TOKEN=secret-docker-value']);
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
Livewire::test($generalClass, ['database' => $database])
->assertSet('customDockerRunOptions', '--env API_TOKEN=secret-docker-value');
})->with('database-docker-options-components');
it('does not let a member save over hidden custom configs and docker run options', function (string $createDatabase, string $generalClass) {
$database = createDatabaseWithConfig($createDatabase, ['custom_docker_run_options' => '--env API_TOKEN=secret-docker-value']);
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
Livewire::test($generalClass, ['database' => $database])
->set('name', 'renamed-by-member')
->call('submit')
->call('instantSaveAdvanced');
$database = $database->fresh();
expect($database->name)->not->toBe('renamed-by-member')
->and($database->custom_docker_run_options)->toBe('--env API_TOKEN=secret-docker-value');
})->with('database-docker-options-components');