DbTool: Enhance issuing and verifying pgsql privileges

This commit is contained in:
Yonas Habteab 2023-09-28 08:51:44 +02:00
parent a99f685d15
commit 329fd9e316
2 changed files with 81 additions and 80 deletions

View File

@ -99,14 +99,7 @@ class DbTool
protected $pgsqlGrantContexts = array( protected $pgsqlGrantContexts = array(
'ALL' => 63, 'ALL' => 63,
'ALL PRIVILEGES' => 63, 'ALL PRIVILEGES' => 63,
'SELECT' => 24, 'CREATE' => 13,
'INSERT' => 24,
'UPDATE' => 24,
'DELETE' => 8,
'TRUNCATE' => 8,
'REFERENCES' => 24,
'TRIGGER' => 8,
'CREATE' => 12,
'CONNECT' => 4, 'CONNECT' => 4,
'TEMPORARY' => 4, 'TEMPORARY' => 4,
'TEMP' => 4, 'TEMP' => 4,
@ -633,13 +626,21 @@ class DbTool
} }
} elseif ($this->config['db'] === 'pgsql') { } elseif ($this->config['db'] === 'pgsql') {
$dbPrivileges = array(); $dbPrivileges = array();
$tablePrivileges = array(); $schemaPrivileges = [];
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) { foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) { if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
$tablePrivileges[] = $privilege;
} elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
$dbPrivileges[] = $privilege; $dbPrivileges[] = $privilege;
} }
if ($this->pgsqlGrantContexts[$privilege] & static::GLOBAL_LEVEL) {
$schemaPrivileges[] = $privilege;
}
}
if (! empty($schemaPrivileges)) {
// Allow the user to create,alter and use all attribute types in schema public
// such as creating and dropping custom data types (boolenum)
$this->exec(sprintf('GRANT %s ON SCHEMA public TO %s', implode(',', $schemaPrivileges), $username));
} }
if (! empty($dbPrivileges)) { if (! empty($dbPrivileges)) {
@ -651,15 +652,10 @@ class DbTool
)); ));
} }
if (! empty($tablePrivileges)) { foreach ($context as $table) {
foreach ($context as $table) { // PostgreSQL documentation says "You must own the table to use ALTER TABLE.", hence it isn't
$this->exec(sprintf( // sufficient to just issue grants, as the user is still not allowed to alter that table.
'GRANT %s ON TABLE %s TO %s', $this->exec(sprintf('ALTER TABLE %s OWNER TO %s', $table, $username));
join(',', $tablePrivileges),
$table,
$username
));
}
} }
} }
} }
@ -854,57 +850,71 @@ EOD;
$username = null $username = null
) { ) {
$privilegesGranted = true; $privilegesGranted = true;
if ($this->dbFromConfig) { $owner = $username ?: $this->config['username'];
$dbPrivileges = array(); $isSuperUser = $this->query('select rolsuper from pg_roles where rolname = :user', [':user' => $owner])
$tablePrivileges = array(); ->fetchColumn();
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
$tablePrivileges[] = $privilege;
}
if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
$dbPrivileges[] = $privilege;
}
}
if (! empty($dbPrivileges)) { if ($this->dbFromConfig) {
$dbExclusivesGranted = true; $schemaPrivileges = [];
foreach ($dbPrivileges as $dbPrivilege) { $dbPrivileges = array();
$query = $this->query( if (! $isSuperUser) {
'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted', foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
array( if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
':user' => $username !== null ? $username : $this->config['username'], $dbPrivileges[] = $privilege;
':dbname' => $this->config['dbname'], }
':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '') if ($this->pgsqlGrantContexts[$privilege] & static::GLOBAL_LEVEL) {
) $schemaPrivileges[] = $privilege;
); }
if (! $query->fetchObject()->db_privilege_granted) { }
$privilegesGranted = false;
if (! in_array($dbPrivilege, $tablePrivileges)) { if (! empty($schemaPrivileges)) {
$dbExclusivesGranted = false; foreach ($schemaPrivileges as $schemaPrivilege) {
$query = $this->query(
'SELECT has_schema_privilege(:user, :schema, :privilege) AS db_privilege_granted',
[
':user' => $owner,
':schema' => 'public',
':privilege' => $schemaPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
]
);
if (! $query->fetchObject()->db_privilege_granted) {
// The user doesn't fully have the provided privileges.
$privilegesGranted = false;
break;
} }
} }
} }
if ($privilegesGranted) { if ($privilegesGranted && ! empty($dbPrivileges)) {
// Do not check privileges twice if they are already granted at database level foreach ($dbPrivileges as $dbPrivilege) {
$tablePrivileges = array_diff($tablePrivileges, $dbPrivileges);
} elseif ($dbExclusivesGranted) {
$privilegesGranted = true;
}
}
if ($privilegesGranted && !empty($tablePrivileges)) {
foreach (array_intersect($context, $this->listTables()) as $table) {
foreach ($tablePrivileges as $tablePrivilege) {
$query = $this->query( $query = $this->query(
'SELECT has_table_privilege(:user, :table, :privilege) AS table_privilege_granted', 'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted',
array( array(
':user' => $username !== null ? $username : $this->config['username'], ':user' => $owner,
':table' => $table, ':dbname' => $this->config['dbname'],
':privilege' => $tablePrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '') ':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
) )
); );
$privilegesGranted &= $query->fetchObject()->table_privilege_granted; if (! $query->fetchObject()->db_privilege_granted) {
// The user doesn't fully have the provided privileges.
$privilegesGranted = false;
break;
}
}
}
if ($privilegesGranted && ! empty($context)) {
foreach (array_intersect($context, $this->listTables()) as $table) {
$query = $this->query(
'SELECT tableowner FROM pg_catalog.pg_tables WHERE tablename = :tablename',
[':tablename' => $table]
);
if ($query->fetchColumn() !== $owner) {
$privilegesGranted = false;
break;
}
} }
} }
} }
@ -914,31 +924,27 @@ EOD;
// as the chances are very high that the database is created later causing the current user being // as the chances are very high that the database is created later causing the current user being
// the owner with ALL privileges. (Which in turn can be granted to others.) // the owner with ALL privileges. (Which in turn can be granted to others.)
if (array_search('CREATE', $privileges, true) !== false) { if (in_array('CREATE', $privileges, true)) {
$query = $this->query( $query = $this->query(
'select rolcreatedb from pg_roles where rolname = :user', 'select rolcreatedb from pg_roles where rolname = :user',
array(':user' => $username !== null ? $username : $this->config['username']) array(':user' => $username !== null ? $username : $this->config['username'])
); );
$privilegesGranted &= $query->fetchColumn() !== false; $privilegesGranted = $query->fetchColumn() !== false;
} }
} }
if (array_search('CREATEROLE', $privileges, true) !== false) { if ($privilegesGranted && in_array('CREATEROLE', $privileges, true)) {
$query = $this->query( $query = $this->query(
'select rolcreaterole from pg_roles where rolname = :user', 'select rolcreaterole from pg_roles where rolname = :user',
array(':user' => $username !== null ? $username : $this->config['username']) array(':user' => $username !== null ? $username : $this->config['username'])
); );
$privilegesGranted &= $query->fetchColumn() !== false; $privilegesGranted = $query->fetchColumn() !== false;
} }
if (array_search('SUPER', $privileges, true) !== false) { if ($privilegesGranted && in_array('SUPER', $privileges, true)) {
$query = $this->query( $privilegesGranted = $isSuperUser === true;
'select rolsuper from pg_roles where rolname = :user',
array(':user' => $username !== null ? $username : $this->config['username'])
);
$privilegesGranted &= $query->fetchColumn() !== false;
} }
return (bool) $privilegesGranted; return $privilegesGranted;
} }
} }

View File

@ -25527,7 +25527,7 @@ parameters:
- -
message: "#^Cannot call method fetchColumn\\(\\) on mixed\\.$#" message: "#^Cannot call method fetchColumn\\(\\) on mixed\\.$#"
count: 8 count: 9
path: modules/setup/library/Setup/Utils/DbTool.php path: modules/setup/library/Setup/Utils/DbTool.php
- -
@ -25660,11 +25660,6 @@ parameters:
count: 1 count: 1
path: modules/setup/library/Setup/Utils/DbTool.php path: modules/setup/library/Setup/Utils/DbTool.php
-
message: "#^Parameter \\#1 \\$array of function array_intersect expects array, array\\|null given\\.$#"
count: 1
path: modules/setup/library/Setup/Utils/DbTool.php
- -
message: "#^Parameter \\#1 \\$dbname of method Icinga\\\\Module\\\\Setup\\\\Utils\\\\DbTool\\:\\:pdoConnect\\(\\) expects string, string\\|null given\\.$#" message: "#^Parameter \\#1 \\$dbname of method Icinga\\\\Module\\\\Setup\\\\Utils\\\\DbTool\\:\\:pdoConnect\\(\\) expects string, string\\|null given\\.$#"
count: 1 count: 1