1
0
mirror of https://github.com/postgres/postgres.git synced 2025-10-21 02:52:47 +03:00

Allow granting SET and ALTER SYSTEM privileges on GUC parameters.

This patch allows "PGC_SUSET" parameters to be set by non-superusers
if they have been explicitly granted the privilege to do so.
The privilege to perform ALTER SYSTEM SET/RESET on a specific parameter
can also be granted.
Such privileges are cluster-wide, not per database.  They are tracked
in a new shared catalog, pg_parameter_acl.

Granting and revoking these new privileges works as one would expect.
One caveat is that PGC_USERSET GUCs are unaffected by the SET privilege
--- one could wish that those were handled by a revocable grant to
PUBLIC, but they are not, because we couldn't make it robust enough
for GUCs defined by extensions.

Mark Dilger, reviewed at various times by Andrew Dunstan, Robert Haas,
Joshua Brindle, and myself

Discussion: https://postgr.es/m/3D691E20-C1D5-4B80-8BA5-6BEB63AF3029@enterprisedb.com
This commit is contained in:
Tom Lane
2022-04-06 13:24:33 -04:00
parent 2ef6f11b0c
commit a0ffa885e4
44 changed files with 2465 additions and 194 deletions

View File

@@ -45,6 +45,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_parameter_acl.h"
#include "catalog/storage.h"
#include "commands/async.h"
#include "commands/prepare.h"
@@ -5713,6 +5714,65 @@ guc_name_compare(const char *namea, const char *nameb)
}
/*
* Convert a GUC name to the form that should be used in pg_parameter_acl.
*
* We need to canonicalize entries since, for example, case should not be
* significant. In addition, we apply the map_old_guc_names[] mapping so that
* any obsolete names will be converted when stored in a new PG version.
* Note however that this function does not verify legality of the name.
*
* The result is a palloc'd string.
*/
char *
convert_GUC_name_for_parameter_acl(const char *name)
{
char *result;
/* Apply old-GUC-name mapping. */
for (int i = 0; map_old_guc_names[i] != NULL; i += 2)
{
if (guc_name_compare(name, map_old_guc_names[i]) == 0)
{
name = map_old_guc_names[i + 1];
break;
}
}
/* Apply case-folding that matches guc_name_compare(). */
result = pstrdup(name);
for (char *ptr = result; *ptr != '\0'; ptr++)
{
char ch = *ptr;
if (ch >= 'A' && ch <= 'Z')
{
ch += 'a' - 'A';
*ptr = ch;
}
}
return result;
}
/*
* Check whether we should allow creation of a pg_parameter_acl entry
* for the given name. (This can be applied either before or after
* canonicalizing it.)
*/
bool
check_GUC_name_for_parameter_acl(const char *name)
{
/* OK if the GUC exists. */
if (find_option(name, false, true, DEBUG1) != NULL)
return true;
/* Otherwise, it'd better be a valid custom GUC name. */
if (valid_custom_variable_name(name))
return true;
return false;
}
/*
* Initialize GUC options during program startup.
*
@@ -7518,14 +7578,24 @@ set_config_option(const char *name, const char *value,
*/
break;
case PGC_SU_BACKEND:
/* Reject if we're connecting but user is not superuser */
if (context == PGC_BACKEND)
{
ereport(elevel,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to set parameter \"%s\"",
name)));
return 0;
/*
* Check whether the current user has been granted privilege
* to set this GUC.
*/
AclResult aclresult;
aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
if (aclresult != ACLCHECK_OK)
{
/* No granted privilege */
ereport(elevel,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to set parameter \"%s\"",
name)));
return 0;
}
}
/* fall through to process the same as PGC_BACKEND */
/* FALLTHROUGH */
@@ -7568,11 +7638,22 @@ set_config_option(const char *name, const char *value,
case PGC_SUSET:
if (context == PGC_USERSET || context == PGC_BACKEND)
{
ereport(elevel,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to set parameter \"%s\"",
name)));
return 0;
/*
* Check whether the current user has been granted privilege
* to set this GUC.
*/
AclResult aclresult;
aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
if (aclresult != ACLCHECK_OK)
{
/* No granted privilege */
ereport(elevel,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to set parameter \"%s\"",
name)));
return 0;
}
}
break;
case PGC_USERSET:
@@ -8617,11 +8698,6 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
char AutoConfFileName[MAXPGPATH];
char AutoConfTmpFileName[MAXPGPATH];
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to execute ALTER SYSTEM command")));
/*
* Extract statement arguments
*/
@@ -8649,6 +8725,29 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
break;
}
/*
* Check permission to run ALTER SYSTEM on the target variable
*/
if (!superuser())
{
if (resetall)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to perform ALTER SYSTEM RESET ALL")));
else
{
AclResult aclresult;
aclresult = pg_parameter_aclcheck(name, GetUserId(),
ACL_ALTER_SYSTEM);
if (aclresult != ACLCHECK_OK)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to set parameter \"%s\"",
name)));
}
}
/*
* Unless it's RESET_ALL, validate the target variable and value
*/
@@ -8760,13 +8859,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
}
/*
* Invoke the post-alter hook for altering this GUC variable.
* Invoke the post-alter hook for setting this GUC variable. GUCs
* typically do not have corresponding entries in pg_parameter_acl, so we
* call the hook using the name rather than a potentially-non-existent
* OID. Nonetheless, we pass ParameterAclRelationId so that this call
* context can be distinguished from others. (Note that "name" will be
* NULL in the RESET ALL case.)
*
* We do this here rather than at the end, because ALTER SYSTEM is not
* transactional. If the hook aborts our transaction, it will be cleaner
* to do so before we touch any files.
*/
InvokeObjectPostAlterHookArgStr(InvalidOid, name,
InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name,
ACL_ALTER_SYSTEM,
altersysstmt->setstmt->kind,
false);
@@ -8943,9 +9047,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
break;
}
/* Invoke the post-alter hook for setting this GUC variable. */
InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
ACL_SET_VALUE, stmt->kind, false);
/* Invoke the post-alter hook for setting this GUC variable, by name. */
InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name,
ACL_SET, stmt->kind, false);
}
/*