mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 08:17:02 +00:00
fix(database): enforce credential format validation and sanitize init/SSL arguments
Add ValidationPatterns helpers for database identifiers and passwords, apply them across database Livewire components and the API controller, encode MongoDB init script values via json_encode, and pass the MySQL user through escapeshellarg when generating SSL chown commands.
This commit is contained in:
1 parent
2264a2ef76
commit
03313e54cc
14 files changed
+502
-82
No files matched your search
@@ -340,7 +340,10 @@ class StartMongodb
|
||||
|
||||
private function add_default_database()
|
||||
{
|
||||
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
|
||||
$dbJson = json_encode($this->database->mongo_initdb_database, JSON_UNESCAPED_SLASHES);
|
||||
$userJson = json_encode($this->database->mongo_initdb_root_username, JSON_UNESCAPED_SLASHES);
|
||||
$pwdJson = json_encode($this->database->mongo_initdb_root_password, JSON_UNESCAPED_SLASHES);
|
||||
$content = "db = db.getSiblingDB({$dbJson});db.createCollection('init_collection');db.createUser({user: {$userJson}, pwd: {$pwdJson}, roles: [{role:\"readWrite\",db:{$dbJson}}]});";
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
|
||||
|
||||
@@ -215,7 +215,8 @@ class StartMysql
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
|
||||
if ($this->database->enable_ssl) {
|
||||
$this->commands[] = executeInDocker($this->database->uuid, "chown {$this->database->mysql_user}:{$this->database->mysql_user} /etc/mysql/certs/server.crt /etc/mysql/certs/server.key");
|
||||
$mysqlUser = escapeshellarg($this->database->mysql_user);
|
||||
$this->commands[] = executeInDocker($this->database->uuid, "chown {$mysqlUser}:{$mysqlUser} /etc/mysql/certs/server.crt /etc/mysql/certs/server.key");
|
||||
}
|
||||
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
|
||||
@@ -379,9 +379,9 @@ class DatabasesController extends Controller
|
||||
case 'standalone-postgresql':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'postgres_user' => 'string',
|
||||
'postgres_password' => 'string',
|
||||
'postgres_db' => 'string',
|
||||
'postgres_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'postgres_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'postgres_db' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'postgres_initdb_args' => 'string',
|
||||
'postgres_host_auth_method' => 'string',
|
||||
'postgres_conf' => 'string',
|
||||
@@ -410,20 +410,20 @@ class DatabasesController extends Controller
|
||||
case 'standalone-clickhouse':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'clickhouse_admin_user' => 'string',
|
||||
'clickhouse_admin_password' => 'string',
|
||||
'clickhouse_admin_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'clickhouse_admin_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
]);
|
||||
break;
|
||||
case 'standalone-dragonfly':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'dragonfly_password' => 'string',
|
||||
'dragonfly_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
]);
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'redis_password' => 'string',
|
||||
'redis_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'redis_conf' => 'string',
|
||||
]);
|
||||
if ($request->has('redis_conf')) {
|
||||
@@ -450,7 +450,7 @@ class DatabasesController extends Controller
|
||||
case 'standalone-keydb':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'keydb_password' => 'string',
|
||||
'keydb_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'keydb_conf' => 'string',
|
||||
]);
|
||||
if ($request->has('keydb_conf')) {
|
||||
@@ -478,10 +478,10 @@ class DatabasesController extends Controller
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mariadb_conf' => 'string',
|
||||
'mariadb_root_password' => 'string',
|
||||
'mariadb_user' => 'string',
|
||||
'mariadb_password' => 'string',
|
||||
'mariadb_database' => 'string',
|
||||
'mariadb_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mariadb_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mariadb_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mariadb_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
]);
|
||||
if ($request->has('mariadb_conf')) {
|
||||
if (! isBase64Encoded($request->mariadb_conf)) {
|
||||
@@ -508,9 +508,9 @@ class DatabasesController extends Controller
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
'mongo_initdb_root_username' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mongo_initdb_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mongo_initdb_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
]);
|
||||
if ($request->has('mongo_conf')) {
|
||||
if (! isBase64Encoded($request->mongo_conf)) {
|
||||
@@ -537,10 +537,10 @@ class DatabasesController extends Controller
|
||||
case 'standalone-mysql':
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mysql_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mysql_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mysql_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mysql_conf' => 'string',
|
||||
]);
|
||||
if ($request->has('mysql_conf')) {
|
||||
@@ -1724,9 +1724,9 @@ class DatabasesController extends Controller
|
||||
if ($type === NewDatabaseTypes::POSTGRESQL) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'postgres_user' => 'string',
|
||||
'postgres_password' => 'string',
|
||||
'postgres_db' => 'string',
|
||||
'postgres_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'postgres_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'postgres_db' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'postgres_initdb_args' => 'string',
|
||||
'postgres_host_auth_method' => 'string',
|
||||
'postgres_conf' => 'string',
|
||||
@@ -1783,8 +1783,11 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::MARIADB) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'clickhouse_admin_user' => 'string',
|
||||
'clickhouse_admin_password' => 'string',
|
||||
'mariadb_conf' => 'string',
|
||||
'mariadb_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mariadb_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mariadb_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mariadb_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
@@ -1839,10 +1842,10 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mysql_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mysql_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mysql_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mysql_conf' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -1898,7 +1901,7 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::REDIS) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'redis_password' => 'string',
|
||||
'redis_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'redis_conf' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -1954,7 +1957,7 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::DRAGONFLY) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'dragonfly_password' => 'string',
|
||||
'dragonfly_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -1984,7 +1987,7 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::KEYDB) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'keydb_password' => 'string',
|
||||
'keydb_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'keydb_conf' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -2040,8 +2043,8 @@ class DatabasesController extends Controller
|
||||
} elseif ($type === NewDatabaseTypes::CLICKHOUSE) {
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'clickhouse_admin_user' => 'string',
|
||||
'clickhouse_admin_password' => 'string',
|
||||
'clickhouse_admin_user' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'clickhouse_admin_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
@@ -2077,9 +2080,9 @@ class DatabasesController extends Controller
|
||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'public_port_timeout', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
'mongo_initdb_root_username' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
'mongo_initdb_root_password' => ValidationPatterns::databasePasswordRules(required: false),
|
||||
'mongo_initdb_database' => ValidationPatterns::databaseIdentifierRules(required: false),
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
||||
@@ -76,8 +76,8 @@ class General extends Component
|
||||
return [
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'clickhouseAdminUser' => 'required|string',
|
||||
'clickhouseAdminPassword' => 'required|string',
|
||||
'clickhouseAdminUser' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'clickhouseAdminPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
@@ -96,10 +96,8 @@ class General extends Component
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'clickhouseAdminUser.required' => 'The Admin User field is required.',
|
||||
'clickhouseAdminUser.string' => 'The Admin User must be a string.',
|
||||
'clickhouseAdminPassword.required' => 'The Admin Password field is required.',
|
||||
'clickhouseAdminPassword.string' => 'The Admin Password must be a string.',
|
||||
...ValidationPatterns::databaseIdentifierMessages('clickhouseAdminUser', 'Admin User'),
|
||||
...ValidationPatterns::databasePasswordMessages('clickhouseAdminPassword', 'Admin Password'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
|
||||
@@ -89,7 +89,7 @@ class General extends Component
|
||||
return [
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'dragonflyPassword' => 'required|string',
|
||||
'dragonflyPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
@@ -109,8 +109,7 @@ class General extends Component
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'dragonflyPassword.required' => 'The Dragonfly Password field is required.',
|
||||
'dragonflyPassword.string' => 'The Dragonfly Password must be a string.',
|
||||
...ValidationPatterns::databasePasswordMessages('dragonflyPassword', 'Dragonfly Password'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
|
||||
@@ -92,7 +92,7 @@ class General extends Component
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'keydbConf' => 'nullable|string',
|
||||
'keydbPassword' => 'required|string',
|
||||
'keydbPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
@@ -114,8 +114,7 @@ class General extends Component
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'keydbPassword.required' => 'The KeyDB Password field is required.',
|
||||
'keydbPassword.string' => 'The KeyDB Password must be a string.',
|
||||
...ValidationPatterns::databasePasswordMessages('keydbPassword', 'KeyDB Password'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
|
||||
@@ -74,10 +74,10 @@ class General extends Component
|
||||
return [
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'mariadbRootPassword' => 'required',
|
||||
'mariadbUser' => 'required',
|
||||
'mariadbPassword' => 'required',
|
||||
'mariadbDatabase' => 'required',
|
||||
'mariadbRootPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'mariadbUser' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'mariadbPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'mariadbDatabase' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'mariadbConf' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
@@ -97,10 +97,10 @@ class General extends Component
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mariadbRootPassword.required' => 'The Root Password field is required.',
|
||||
'mariadbUser.required' => 'The MariaDB User field is required.',
|
||||
'mariadbPassword.required' => 'The MariaDB Password field is required.',
|
||||
'mariadbDatabase.required' => 'The MariaDB Database field is required.',
|
||||
...ValidationPatterns::databasePasswordMessages('mariadbRootPassword', 'Root Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('mariadbUser', 'MariaDB User'),
|
||||
...ValidationPatterns::databasePasswordMessages('mariadbPassword', 'MariaDB Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('mariadbDatabase', 'MariaDB Database'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
|
||||
@@ -75,9 +75,9 @@ class General extends Component
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'mongoConf' => 'nullable',
|
||||
'mongoInitdbRootUsername' => 'required',
|
||||
'mongoInitdbRootPassword' => 'required',
|
||||
'mongoInitdbDatabase' => 'required',
|
||||
'mongoInitdbRootUsername' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'mongoInitdbRootPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'mongoInitdbDatabase' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'image' => 'required',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
@@ -97,9 +97,9 @@ class General extends Component
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
|
||||
'mongoInitdbRootPassword.required' => 'The Root Password field is required.',
|
||||
'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.',
|
||||
...ValidationPatterns::databaseIdentifierMessages('mongoInitdbRootUsername', 'Root Username'),
|
||||
...ValidationPatterns::databasePasswordMessages('mongoInitdbRootPassword', 'Root Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('mongoInitdbDatabase', 'MongoDB Database'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
|
||||
@@ -76,10 +76,10 @@ class General extends Component
|
||||
return [
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'mysqlRootPassword' => 'required',
|
||||
'mysqlUser' => 'required',
|
||||
'mysqlPassword' => 'required',
|
||||
'mysqlDatabase' => 'required',
|
||||
'mysqlRootPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'mysqlUser' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'mysqlPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'mysqlDatabase' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'mysqlConf' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
@@ -100,10 +100,10 @@ class General extends Component
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mysqlRootPassword.required' => 'The Root Password field is required.',
|
||||
'mysqlUser.required' => 'The MySQL User field is required.',
|
||||
'mysqlPassword.required' => 'The MySQL Password field is required.',
|
||||
'mysqlDatabase.required' => 'The MySQL Database field is required.',
|
||||
...ValidationPatterns::databasePasswordMessages('mysqlRootPassword', 'Root Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('mysqlUser', 'MySQL User'),
|
||||
...ValidationPatterns::databasePasswordMessages('mysqlPassword', 'MySQL Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('mysqlDatabase', 'MySQL Database'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
|
||||
@@ -86,9 +86,9 @@ class General extends Component
|
||||
return [
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'postgresUser' => 'required',
|
||||
'postgresPassword' => 'required',
|
||||
'postgresDb' => 'required',
|
||||
'postgresUser' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'postgresPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'postgresDb' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'postgresInitdbArgs' => 'nullable',
|
||||
'postgresHostAuthMethod' => 'nullable',
|
||||
'postgresConf' => 'nullable',
|
||||
@@ -112,9 +112,9 @@ class General extends Component
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'postgresUser.required' => 'The Postgres User field is required.',
|
||||
'postgresPassword.required' => 'The Postgres Password field is required.',
|
||||
'postgresDb.required' => 'The Postgres Database field is required.',
|
||||
...ValidationPatterns::databaseIdentifierMessages('postgresUser', 'Postgres User'),
|
||||
...ValidationPatterns::databasePasswordMessages('postgresPassword', 'Postgres Password'),
|
||||
...ValidationPatterns::databaseIdentifierMessages('postgresDb', 'Postgres Database'),
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
|
||||
@@ -81,8 +81,8 @@ class General extends Component
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
'redisUsername' => 'required',
|
||||
'redisPassword' => 'required',
|
||||
'redisUsername' => ValidationPatterns::databaseIdentifierRules(),
|
||||
'redisPassword' => ValidationPatterns::databasePasswordRules(),
|
||||
'enableSsl' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -100,8 +100,8 @@ class General extends Component
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
'redisUsername.required' => 'The Redis Username field is required.',
|
||||
'redisPassword.required' => 'The Redis Password field is required.',
|
||||
...ValidationPatterns::databaseIdentifierMessages('redisUsername', 'Redis Username'),
|
||||
...ValidationPatterns::databasePasswordMessages('redisPassword', 'Redis Password'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,98 @@ class ValidationPatterns
|
||||
*/
|
||||
public const DOCKER_NETWORK_PATTERN = '/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/';
|
||||
|
||||
/**
|
||||
* Pattern for SQL-safe unquoted database identifiers (usernames, database names).
|
||||
* Allows letters, digits, underscore; first char must be letter or underscore.
|
||||
* Excludes all shell metacharacters. Max 63 chars (Postgres identifier limit).
|
||||
*/
|
||||
public const DB_IDENTIFIER_PATTERN = '/^[A-Za-z_][A-Za-z0-9_]{0,62}$/';
|
||||
|
||||
/**
|
||||
* Pattern for database passwords.
|
||||
* Excludes shell-dangerous characters: backtick, $, ;, |, &, <, >, \, ', ", space, newline, CR, tab, null.
|
||||
* Allows a broad set of printable characters so passwords remain strong.
|
||||
*/
|
||||
public const DB_PASSWORD_PATTERN = '/^[A-Za-z0-9!@#%^*()_+\-=\[\]{}:,.?\/~]+$/';
|
||||
|
||||
/**
|
||||
* Get validation rules for database identifier fields (username, database name).
|
||||
*/
|
||||
public static function databaseIdentifierRules(bool $required = true, int $minLength = 1, int $maxLength = 63): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($required) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
$rules[] = 'string';
|
||||
$rules[] = "min:$minLength";
|
||||
$rules[] = "max:$maxLength";
|
||||
$rules[] = 'regex:'.self::DB_IDENTIFIER_PATTERN;
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages for database identifier fields.
|
||||
*/
|
||||
public static function databaseIdentifierMessages(string $field, string $label = ''): array
|
||||
{
|
||||
$label = $label ?: $field;
|
||||
|
||||
return [
|
||||
"{$field}.regex" => "The {$label} may only contain letters, digits, and underscores, and must start with a letter or underscore.",
|
||||
"{$field}.min" => "The {$label} must be at least :min character.",
|
||||
"{$field}.max" => "The {$label} may not be greater than :max characters.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for database password fields.
|
||||
*/
|
||||
public static function databasePasswordRules(bool $required = true, int $minLength = 1, int $maxLength = 128): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($required) {
|
||||
$rules[] = 'required';
|
||||
} else {
|
||||
$rules[] = 'nullable';
|
||||
}
|
||||
|
||||
$rules[] = 'string';
|
||||
$rules[] = "min:$minLength";
|
||||
$rules[] = "max:$maxLength";
|
||||
$rules[] = 'regex:'.self::DB_PASSWORD_PATTERN;
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages for database password fields.
|
||||
*/
|
||||
public static function databasePasswordMessages(string $field, string $label = ''): array
|
||||
{
|
||||
$label = $label ?: $field;
|
||||
|
||||
return [
|
||||
"{$field}.regex" => "The {$label} may not contain shell-unsafe characters (backtick, \$, ;, |, &, <, >, \\, quotes, spaces, or control characters).",
|
||||
"{$field}.min" => "The {$label} must be at least :min character.",
|
||||
"{$field}.max" => "The {$label} may not be greater than :max characters.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid database identifier.
|
||||
*/
|
||||
public static function isValidDatabaseIdentifier(string $value): bool
|
||||
{
|
||||
return preg_match(self::DB_IDENTIFIER_PATTERN, $value) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for name fields
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user