summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane2022-04-06 17:24:33 +0000
committerTom Lane2022-04-06 17:24:33 +0000
commita0ffa885e478f5eeacc4e250e35ce25a4740c487 (patch)
tree7ce236305d5eb50f34bfccaf9a662cf3f0b77adf /src/backend/utils
parent2ef6f11b0c77ec323c688ddfd98ffabddb72c11d (diff)
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/[email protected]
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/acl.c114
-rw-r--r--src/backend/utils/cache/syscache.c23
-rw-r--r--src/backend/utils/misc/guc.c146
3 files changed, 262 insertions, 21 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 83cf7ac9ff9..8bdba1c42a5 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -23,6 +23,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_parameter_acl.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/proclang.h"
@@ -36,6 +37,7 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
+#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -109,6 +111,7 @@ static Oid convert_tablespace_name(text *tablespacename);
static AclMode convert_tablespace_priv_string(text *priv_type_text);
static Oid convert_type_name(text *typename);
static AclMode convert_type_priv_string(text *priv_type_text);
+static AclMode convert_parameter_priv_string(text *priv_text);
static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
@@ -306,6 +309,12 @@ aclparse(const char *s, AclItem *aip)
case ACL_CONNECT_CHR:
read = ACL_CONNECT;
break;
+ case ACL_SET_CHR:
+ read = ACL_SET;
+ break;
+ case ACL_ALTER_SYSTEM_CHR:
+ read = ACL_ALTER_SYSTEM;
+ break;
case 'R': /* ignore old RULE privileges */
read = 0;
break;
@@ -794,6 +803,10 @@ acldefault(ObjectType objtype, Oid ownerId)
world_default = ACL_USAGE;
owner_default = ACL_ALL_RIGHTS_TYPE;
break;
+ case OBJECT_PARAMETER_ACL:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
@@ -876,6 +889,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
case 'n':
objtype = OBJECT_SCHEMA;
break;
+ case 'p':
+ objtype = OBJECT_PARAMETER_ACL;
+ break;
case 't':
objtype = OBJECT_TABLESPACE;
break;
@@ -1602,6 +1618,10 @@ convert_priv_string(text *priv_type_text)
return ACL_CREATE_TEMP;
if (pg_strcasecmp(priv_type, "CONNECT") == 0)
return ACL_CONNECT;
+ if (pg_strcasecmp(priv_type, "SET") == 0)
+ return ACL_SET;
+ if (pg_strcasecmp(priv_type, "ALTER SYSTEM") == 0)
+ return ACL_ALTER_SYSTEM;
if (pg_strcasecmp(priv_type, "RULE") == 0)
return 0; /* ignore old RULE privileges */
@@ -1698,6 +1718,10 @@ convert_aclright_to_string(int aclright)
return "TEMPORARY";
case ACL_CONNECT:
return "CONNECT";
+ case ACL_SET:
+ return "SET";
+ case ACL_ALTER_SYSTEM:
+ return "ALTER SYSTEM";
default:
elog(ERROR, "unrecognized aclright: %d", aclright);
return NULL;
@@ -4429,6 +4453,96 @@ convert_type_priv_string(text *priv_type_text)
return convert_any_priv_string(priv_type_text, type_priv_map);
}
+/*
+ * has_parameter_privilege variants
+ * These are all named "has_parameter_privilege" at the SQL level.
+ * They take various combinations of parameter name with
+ * user name, user OID, or implicit user = current_user.
+ *
+ * The result is a boolean value: true if user has been granted
+ * the indicated privilege or false if not.
+ */
+
+/*
+ * has_param_priv_byname
+ *
+ * Helper function to check user privileges on a parameter given the
+ * role by Oid, parameter by text name, and privileges as AclMode.
+ */
+static bool
+has_param_priv_byname(Oid roleid, const text *parameter, AclMode priv)
+{
+ char *paramstr = text_to_cstring(parameter);
+
+ return pg_parameter_aclcheck(paramstr, roleid, priv) == ACLCHECK_OK;
+}
+
+/*
+ * has_parameter_privilege_name_name
+ * Check user privileges on a parameter given name username, text
+ * parameter, and text priv name.
+ */
+Datum
+has_parameter_privilege_name_name(PG_FUNCTION_ARGS)
+{
+ Name username = PG_GETARG_NAME(0);
+ text *parameter = PG_GETARG_TEXT_PP(1);
+ AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+ Oid roleid = get_role_oid_or_public(NameStr(*username));
+
+ PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_name
+ * Check user privileges on a parameter given text parameter and text priv
+ * name. current_user is assumed
+ */
+Datum
+has_parameter_privilege_name(PG_FUNCTION_ARGS)
+{
+ text *parameter = PG_GETARG_TEXT_PP(0);
+ AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(has_param_priv_byname(GetUserId(), parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_id_name
+ * Check user privileges on a parameter given roleid, text parameter, and
+ * text priv name.
+ */
+Datum
+has_parameter_privilege_id_name(PG_FUNCTION_ARGS)
+{
+ Oid roleid = PG_GETARG_OID(0);
+ text *parameter = PG_GETARG_TEXT_PP(1);
+ AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+
+ PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ * Support routines for has_parameter_privilege family.
+ */
+
+/*
+ * convert_parameter_priv_string
+ * Convert text string to AclMode value.
+ */
+static AclMode
+convert_parameter_priv_string(text *priv_text)
+{
+ static const priv_map parameter_priv_map[] = {
+ {"SET", ACL_SET},
+ {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)},
+ {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+ {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
+ {NULL, 0}
+ };
+
+ return convert_any_priv_string(priv_text, parameter_priv_map);
+}
/*
* pg_has_role variants
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a675877d195..8d265f2d23c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -47,6 +47,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
@@ -574,6 +575,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {ParameterAclRelationId, /* PARAMETERACLNAME */
+ ParameterAclParnameIndexId,
+ 1,
+ {
+ Anum_pg_parameter_acl_parname,
+ 0,
+ 0,
+ 0
+ },
+ 4
+ },
+ {ParameterAclRelationId, /* PARAMETERACLOID */
+ ParameterAclOidIndexId,
+ 1,
+ {
+ Anum_pg_parameter_acl_oid,
+ 0,
+ 0,
+ 0
+ },
+ 4
+ },
{PartitionedRelationId, /* PARTRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9e8ab1420d9..5538465d7d6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -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"
@@ -5714,6 +5715,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.
*
* Note that we cannot read the config file yet, since we have not yet
@@ -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
*/
@@ -8650,6 +8726,29 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
}
/*
+ * 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
*/
if (!resetall)
@@ -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);
}
/*