|
| 1 | +From 2e32658e52e196ef2e3ef671606da9131f0079d9 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Raito Bezarius <masterancpp@gmail.com> |
| 3 | +Date: Tue, 21 Mar 2023 16:38:29 +0100 |
| 4 | +Subject: [PATCH] Setup: remove custom dbuser creation behavior |
| 5 | + |
| 6 | +Both PostgreSQL and MySQL can be authenticated against from Nextcloud by |
| 7 | +supplying a database password. Now, during setup the following things |
| 8 | +happen: |
| 9 | + |
| 10 | +* When using postgres and the db user has elevated permissions, a new |
| 11 | + unprivileged db user is created and the settings `dbuser`/`dbpass` are |
| 12 | + altered in `config.php`. |
| 13 | + |
| 14 | +* When using MySQL, the password is **always** regenerated since |
| 15 | + 24.0.5/23.0.9[1]. |
| 16 | + |
| 17 | +I consider both cases problematic: the reason why people do configuration |
| 18 | +management is to have it as single source of truth! So, IMHO any |
| 19 | +application that silently alters config and thus causes deployed |
| 20 | +nodes to diverge from the configuration is harmful for that. |
| 21 | + |
| 22 | +I guess it was sheer luck that it worked for so long in NixOS because |
| 23 | +nobody has apparently used password authentication with a privileged |
| 24 | +user to operate Nextcloud (which is a good thing in fact). |
| 25 | + |
| 26 | +[1] https://github.com/nextcloud/server/pull/33513 |
| 27 | + |
| 28 | +Original-Author: Maximilian Bosch <maximilian@mbosch.me> |
| 29 | +--- |
| 30 | + lib/private/Setup/MySQL.php | 61 +++----------------------------- |
| 31 | + lib/private/Setup/PostgreSQL.php | 39 -------------------- |
| 32 | + 2 files changed, 4 insertions(+), 96 deletions(-) |
| 33 | + |
| 34 | +diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php |
| 35 | +index 50f566728a9..ce721415e30 100644 |
| 36 | +--- a/lib/private/Setup/MySQL.php |
| 37 | ++++ b/lib/private/Setup/MySQL.php |
| 38 | +@@ -145,62 +145,9 @@ class MySQL extends AbstractDatabase { |
| 39 | + * @param IDBConnection $connection |
| 40 | + */ |
| 41 | + private function createSpecificUser($username, $connection): void { |
| 42 | +- $rootUser = $this->dbUser; |
| 43 | +- $rootPassword = $this->dbPassword; |
| 44 | +- |
| 45 | +- //create a random password so we don't need to store the admin password in the config file |
| 46 | +- $saveSymbols = str_replace(['\"', '\\', '\'', '`'], '', ISecureRandom::CHAR_SYMBOLS); |
| 47 | +- $password = $this->random->generate(22, ISecureRandom::CHAR_ALPHANUMERIC . $saveSymbols) |
| 48 | +- . $this->random->generate(2, ISecureRandom::CHAR_UPPER) |
| 49 | +- . $this->random->generate(2, ISecureRandom::CHAR_LOWER) |
| 50 | +- . $this->random->generate(2, ISecureRandom::CHAR_DIGITS) |
| 51 | +- . $this->random->generate(2, $saveSymbols); |
| 52 | +- $this->dbPassword = str_shuffle($password); |
| 53 | +- |
| 54 | +- try { |
| 55 | +- //user already specified in config |
| 56 | +- $oldUser = $this->config->getValue('dbuser', false); |
| 57 | +- |
| 58 | +- //we don't have a dbuser specified in config |
| 59 | +- if ($this->dbUser !== $oldUser) { |
| 60 | +- //add prefix to the admin username to prevent collisions |
| 61 | +- $adminUser = substr('oc_' . $username, 0, 16); |
| 62 | +- |
| 63 | +- $i = 1; |
| 64 | +- while (true) { |
| 65 | +- //this should be enough to check for admin rights in mysql |
| 66 | +- $query = 'SELECT user FROM mysql.user WHERE user=?'; |
| 67 | +- $result = $connection->executeQuery($query, [$adminUser]); |
| 68 | +- |
| 69 | +- //current dbuser has admin rights |
| 70 | +- $data = $result->fetchAll(); |
| 71 | +- $result->closeCursor(); |
| 72 | +- //new dbuser does not exist |
| 73 | +- if (count($data) === 0) { |
| 74 | +- //use the admin login data for the new database user |
| 75 | +- $this->dbUser = $adminUser; |
| 76 | +- $this->createDBUser($connection); |
| 77 | +- |
| 78 | +- break; |
| 79 | +- } else { |
| 80 | +- //repeat with different username |
| 81 | +- $length = strlen((string)$i); |
| 82 | +- $adminUser = substr('oc_' . $username, 0, 16 - $length) . $i; |
| 83 | +- $i++; |
| 84 | +- } |
| 85 | +- } |
| 86 | +- } else { |
| 87 | +- // Reuse existing password if a database config is already present |
| 88 | +- $this->dbPassword = $rootPassword; |
| 89 | +- } |
| 90 | +- } catch (\Exception $ex) { |
| 91 | +- $this->logger->info('Can not create a new MySQL user, will continue with the provided user.', [ |
| 92 | +- 'exception' => $ex, |
| 93 | +- 'app' => 'mysql.setup', |
| 94 | +- ]); |
| 95 | +- // Restore the original credentials |
| 96 | +- $this->dbUser = $rootUser; |
| 97 | +- $this->dbPassword = $rootPassword; |
| 98 | +- } |
| 99 | ++ $this->config->setValues([ |
| 100 | ++ 'dbuser' => $this->dbUser, |
| 101 | ++ 'dbpassword' => $this->dbPassword, |
| 102 | ++ ]); |
| 103 | + } |
| 104 | + } |
| 105 | +diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php |
| 106 | +index 490cbba69a9..48e7f24f260 100644 |
| 107 | +--- a/lib/private/Setup/PostgreSQL.php |
| 108 | ++++ b/lib/private/Setup/PostgreSQL.php |
| 109 | +@@ -45,45 +45,6 @@ class PostgreSQL extends AbstractDatabase { |
| 110 | + $connection = $this->connect([ |
| 111 | + 'dbname' => 'postgres' |
| 112 | + ]); |
| 113 | +- if ($this->tryCreateDbUser) { |
| 114 | +- //check for roles creation rights in postgresql |
| 115 | +- $builder = $connection->getQueryBuilder(); |
| 116 | +- $builder->automaticTablePrefix(false); |
| 117 | +- $query = $builder |
| 118 | +- ->select('rolname') |
| 119 | +- ->from('pg_roles') |
| 120 | +- ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE'))) |
| 121 | +- ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); |
| 122 | +- |
| 123 | +- try { |
| 124 | +- $result = $query->execute(); |
| 125 | +- $canCreateRoles = $result->rowCount() > 0; |
| 126 | +- } catch (DatabaseException $e) { |
| 127 | +- $canCreateRoles = false; |
| 128 | +- } |
| 129 | +- |
| 130 | +- if ($canCreateRoles) { |
| 131 | +- $connectionMainDatabase = $this->connect(); |
| 132 | +- //use the admin login data for the new database user |
| 133 | +- |
| 134 | +- //add prefix to the postgresql user name to prevent collisions |
| 135 | +- $this->dbUser = 'oc_' . strtolower($username); |
| 136 | +- //create a new password so we don't need to store the admin config in the config file |
| 137 | +- $this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); |
| 138 | +- |
| 139 | +- $this->createDBUser($connection); |
| 140 | +- |
| 141 | +- // Go to the main database and grant create on the public schema |
| 142 | +- // The code below is implemented to make installing possible with PostgreSQL version 15: |
| 143 | +- // https://www.postgresql.org/docs/release/15.0/ |
| 144 | +- // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases |
| 145 | +- // Therefore we assume that the database is only used by one user/service which is Nextcloud |
| 146 | +- // Additional services should get installed in a separate database in order to stay secure |
| 147 | +- // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS |
| 148 | +- $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); |
| 149 | +- $connectionMainDatabase->close(); |
| 150 | +- } |
| 151 | +- } |
| 152 | + |
| 153 | + $this->config->setValues([ |
| 154 | + 'dbuser' => $this->dbUser, |
| 155 | +-- |
| 156 | +2.39.2 |
| 157 | + |
0 commit comments