public inbox for [email protected]help / color / mirror / Atom feed
Re: Custom oauth validator options 7+ messages / 2 participants [nested] [flat]
* Re: Custom oauth validator options @ 2026-01-19 20:30 Zsolt Parragi <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Zsolt Parragi @ 2026-01-19 20:30 UTC (permalink / raw) To: Jacob Champion <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] > I agree it could be, but is it any more confusing than if you were to > set work_mem in postgresql.conf today, and then `ALTER ROLE ALL SET > work_mem` to something completely different? I would say yes, because in the ALTER ROLE case, it's clear that a role specific setting is more specific. But I also understand this reasoning, I'll update the patch to follow this approach. > Right. This goes back to your question upthread as to why I brought > session_preload_libraries into all this -- I thought > session_preload_libraries already had handling for this, but it > doesn't. I looked into the previous idea I mentioned, about using child processes for the purpose, and got that working. * to prevent pg_hba reloads if something is invalid in it * possibly to print out a warning (or error/fatal) during postmaster startup along/instead of the connection warning * possibly to do the same during sighup The "instead of connection warning" (removing the placeholders from postmaster) part is a bit complex or limited, as the postmaster can't use dsm, and there can be any number of variables. This is again a bit of a different topic, but I could make that a proper patch from this prototype. The important part for this thread is that if you would prefer a version which completely verifies the pg_hba configuration before accepting it, it's not that difficult to implement, or at least it's not as complex as I originally imagined it. But even that won't guarantee that the configuration always remains valid, because session preload libraries can change without a server restart/reload... but that's a rare corner case, and it could be a useful check most of the time. ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-20 18:02 Jacob Champion <[email protected]> parent: Zsolt Parragi <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Jacob Champion @ 2026-01-20 18:02 UTC (permalink / raw) To: Zsolt Parragi <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] On Mon, Jan 19, 2026 at 12:30 PM Zsolt Parragi <[email protected]> wrote: > This is again a bit of a different topic, but I could make that a > proper patch from this prototype. Let's separate the "verify session-preloaded GUCs" question from this feature request, yeah. > The important part for this thread is that if you would prefer a > version which completely verifies the pg_hba configuration before > accepting it, it's not that difficult to implement, or at least it's > not as complex as I originally imagined it. But even that won't > guarantee that the configuration always remains valid, because session > preload libraries can change without a server restart/reload... but > that's a rare corner case, and it could be a useful check most of the > time. Right. I've been thinking about strategy here, and I'm not sure I've solidified my thoughts yet, but I don't want to make you wait for that. So here goes: The inability to verify the HBA settings, without actively loading the extension, is a drawback whether we introduce a PGC_HBA or not. I feel pretty strongly that we can't require shared_preload_libraries for this use case. And given the choice between "you cannot modify per-HBA settings at all" and "we can't tell you until you test them whether they're valid or not", most people would probably prefer the latter limitation. Especially since the *values* for many existing HBA parameters cannot truly be "validated" without testing anyway; consider ldapserver etc. Since session_preload_libraries already can't do this, I don't feel too bad about us not doing it for a first version of the feature, but this limitation is likely to remain for a long time. Unless you think that there's a technical solution where the benefit easily outweighs the maintenance cost. And my guess is that this conversation is about to collide at high speed with the Postgres-threading work that's in progress. I like the idea of a PGC_HBA. I think it makes a lot of sense to be able set other GUC overrides here -- authentication_timeout, log_connections, pre/post_auth_delay. It seems architectually sound and generically useful. I'm worried that it's about to make a different decision from the decision that is being made for the pg_hosts.conf file for SNI. So this is not going to feel cohesive at first, and that's only "okay" if it becomes cohesive very quickly, which requires a larger audience. I'm also worried about namespace collisions between GUC and HBA. If we scope it to OAuth then that becomes easier (e.g. just prepend `validator.` or something to the setting name in HBA and then it's obvious what's going on). But if someone decides in PG20 that pam_use_hostname is a good GUC name for something, we're in trouble, because the existing HBA options do not plug into the GUC system. That's a lot of risk. High revert potential without multiple maintainers saying "yes", IMO, and if that happens we will have no improvement here for PG19. So, 1) how close to the sun do you feel like flying today? 2) do you agree with the above? 3) can your option (b) or (c) make enough use of existing GUC infrastructure, so that a future PGC_HBA could easily subsume an OAuth-specific solution, if people want to continue down that path in a less OAuth-centric thread? --Jacob ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-20 20:31 Zsolt Parragi <[email protected]> parent: Jacob Champion <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Zsolt Parragi @ 2026-01-20 20:31 UTC (permalink / raw) To: Jacob Champion <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] > I'm not sure I've solidified my thoughts yet I can wait if you would prefer more time to think about the problem, I can work on other things in the meantime and we still have several months before the code freeze. I mainly wanted to share my findings with this experiment, and that was also a fun side project to try. > 2) do you agree with the above? Yes. I'm fine with not verifying everything perfectly (or as close as perfectly as we can). I like my currently submitted version better than the child process verification version, but I wanted to see if it is doable or not, and to see what challenges there are. > But if someone decides in PG20 that > pam_use_hostname is a good GUC name for something, we're in trouble, > because the existing HBA options do not plug into the GUC system. We could make them reserved names? Or maybe even accessible as GUC variables, even if we leave the current parsing/validation logic as is. Making them proper GUC variables seemed like a clear follow up patch to me, even if not for pg19. > I'm worried that it's about to make a different decision from the > decision that is being made for the pg_hosts.conf file for SNI. I probably should read that thread in more detail, but I assume that your worry is about pg_hosts being a hardcoded configuration instead of using a similarly customizable GUC context? Shouldn't that be fixable in the future similarly? > 3) can your option (b) or (c) make enough use of existing GUC > infrastructure, so that a future PGC_HBA could easily subsume an > OAuth-specific solution, if people want to continue down that path in > a less OAuth-centric thread? I'm not sure about reusing existing GUC infrastructure, but I could make it look similar from the users perspective for example by adding a function DefineCustomValidatorStringVariable that has a similar interface to DefineCustomStringVariable, and in the future, this function could simply forward to DefineCustomStringVariable. That would limit the variable to be only definable in pg_hba, not postgresql.conf, but otherwise should work similarly for validators/users. I think this would be a larger patch than the real PGC_GUC, but it would be limited to the pg_hba parser. > 1) how close to the sun do you feel like flying today? Now that I have tried the PGC_HBA approach, I like how that works and integrates with everything, this is a much better solution than my original ideas. On the other hand, it would be great to get something working in PG19. By that time more libraries/tools should actually start to support oidc, and we will see more use of the feature, and the way we configure these parameters is important for the validators. ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-24 00:04 Jacob Champion <[email protected]> parent: Zsolt Parragi <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Jacob Champion @ 2026-01-24 00:04 UTC (permalink / raw) To: Zsolt Parragi <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] On Tue, Jan 20, 2026 at 12:31 PM Zsolt Parragi <[email protected]> wrote: > > But if someone decides in PG20 that > > pam_use_hostname is a good GUC name for something, we're in trouble, > > because the existing HBA options do not plug into the GUC system. > > We could make them reserved names? I'm wondering if we should maybe do the opposite, and namespace the GUCs instead? The vast majority of settings in an HBA are not going to be GUCs, they're going to be method-specific parameters. So maybe it's okay to have to do more typing to do the uncommon thing, and reference them like `guc.log_connections` or something. > Or maybe even accessible as GUC > variables, even if we leave the current parsing/validation logic as > is. Making them proper GUC variables seemed like a clear follow up > patch to me, even if not for pg19. Hmm... we may want to discuss my (e) option derailment more seriously, if we're planning to go in that direction (and if other people like that direction). > > I'm worried that it's about to make a different decision from the > > decision that is being made for the pg_hosts.conf file for SNI. > > I probably should read that thread in more detail, but I assume that > your worry is about pg_hosts being a hardcoded configuration instead > of using a similarly customizable GUC context? Shouldn't that be > fixable in the future similarly? "Fixable" in what sense? pg_hosts.conf is currently similar to pg_ident.conf in that it has no place for key=value pairs, and if you add them after as an optional "column" for compatibility, you still have to write something for all of those columns that you were trying to replace with the GUC settings. > > 3) can your option (b) or (c) make enough use of existing GUC > > infrastructure, so that a future PGC_HBA could easily subsume an > > OAuth-specific solution, if people want to continue down that path in > > a less OAuth-centric thread? > > I'm not sure about reusing existing GUC infrastructure, but I could > make it look similar from the users perspective for example by adding > a function DefineCustomValidatorStringVariable that has a similar > interface to DefineCustomStringVariable, and in the future, this > function could simply forward to DefineCustomStringVariable. Might work, yeah. --Jacob ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-26 09:51 Zsolt Parragi <[email protected]> parent: Jacob Champion <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Zsolt Parragi @ 2026-01-26 09:51 UTC (permalink / raw) To: Jacob Champion <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] > Hmm... we may want to discuss my (e) option derailment more seriously, > if we're planning to go in that direction (and if other people like > that direction). I know you wrote that you are only half serious about it, and I definitely do not want to go in the "lets completely refactor pg_hba in this patch" direction, but keeping that idea in mind seems like a good idea to me. The choosing authentication method part would already be useful with OAuth, and now Joel also started a thread about fido2, which also brings the question of MFA. Pluggable generic authentication would also require generic GUC variables at this level. Scoping validators to a specific prefix fixes the collision issue, but it also goes in a different direction. Because of this I like the other alternative idea (DefineCustomValidatorStringVariable) better, if we want to go with a smaller change for this, but I still have to implement that and see how it behaves in practice. > "Fixable" in what sense? pg_hosts.conf is currently similar to > pg_ident.conf in that it has no place for key=value pairs, and if you > add them after as an optional "column" for compatibility, you still > have to write something for all of those columns that you were trying > to replace with the GUC settings. pg_hba has the same issue, even if it has custom key=value data already. What I meant is similarly how we could turn currently hard coded pg_hba settings into GUC variables, the same is doable with pg_hosts, either at a separate level or integrating it into the HBA context. And later either both should get a new line style and deprecate the old one, or maybe these settings should be configured completely differently. ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-27 17:40 Jacob Champion <[email protected]> parent: Zsolt Parragi <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Jacob Champion @ 2026-01-27 17:40 UTC (permalink / raw) To: Zsolt Parragi <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] On Mon, Jan 26, 2026 at 1:51 AM Zsolt Parragi <[email protected]> wrote: > The choosing authentication method part would already > be useful with OAuth, and now Joel also started a thread about fido2, > which also brings the question of MFA. Or just the ability to offer a choice between two authentication methods for a single user, yeah. > pg_hba has the same issue, even if it has custom key=value data > already. What I meant is similarly how we could turn currently hard > coded pg_hba settings into GUC variables, the same is doable with > pg_hosts, either at a separate level or integrating it into the HBA > context. And later either both should get a new line style and > deprecate the old one, or maybe these settings should be configured > completely differently. Sure; at this point I think we're violently agreeing. If we suspect the configuration UX needs to be refactored, that's not going to be a decision made unilaterally in this thread, which is why I said I was worried about the scope creep. --Jacob ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Custom oauth validator options @ 2026-01-28 16:04 Zsolt Parragi <[email protected]> parent: Jacob Champion <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Zsolt Parragi @ 2026-01-28 16:04 UTC (permalink / raw) To: Jacob Champion <[email protected]>; +Cc: VASUKI M <[email protected]>; PostgreSQL Hackers <[email protected]>; [email protected]; Robert Haas <[email protected]>; [email protected] I implemented a DefineCustomValidatorStringVariable PoC - I don't like it that much. It adds too much boilerplate code for a very specific feature. If you say we should go with a more limited approach, I think my earlier simple version is better, because it is simple. I'll also try to think about other approaches. And also let me go back to my concern that > Scoping validators to a specific prefix fixes the collision issue, but > it also goes in a different direction. I wrote this because of the simple "guc.some_name" example, as the fixed guc prefix - and previously I also looked into MarkGUCPrefixReserved, and I realized that there's no easy way to use that for enforcing prefixes for a library. And then I realized that maybe that needs an improvement, the behavior of MarkGUCPrefixReserved and DefineCustom*Variable seems like a legacy thing and not something that was intentionally designed that way. What do you think about the following patches? 0001: defines a new guc, guc_prefix_enforcement that potentially changes the behavior of prefix reservation. It has a few modes, based on which missing prefix reservations or variables defined outside the reserved prefix result in warnings or errors during library load time. This is unrelated to pgc_hba, and applies to all custom variables. 0002: the same patch as before, with your comment (su_backend, backend, suset, user can be set in pg_hba) addressed, and also always enforces proper prefix reservation for pg_hba variables using 0001. * We don't have to worry about collisions, because prefixes are always enforced in pg_hba, so people can't "redefine" the fixed key/value pairs or columns * It also introduces the idea of enforcing guc prefixes for extensions. In theory this setting should start with a relaxed default (I would say warning mode), and changed to strict in a later major version, enforcing proper guc rules by default. That way, third party extensions won't be able to define gucs like pam_use_hostname. I realize that 1. This is also scope creep 2. 0001 probably should be a separate thread/discussion But I first wanted to ask your opinion about the idea / what do you think about the interaction of the two patches. Attachments: [application/octet-stream] 0001-Guc-prefix-enforcement.patch (33.9K, 2-0001-Guc-prefix-enforcement.patch) download | inline diff: From 223cb59b2558edcef39e88482c0e0b4f093ba75b Mon Sep 17 00:00:00 2001 From: Zsolt Parragi <[email protected]> Date: Wed, 28 Jan 2026 15:38:25 +0100 Subject: [PATCH 1/2] Guc prefix enforcement This patch introduces a new guc variable, guc_prefix_enforcement. This variable aims to enforce proper naming/structuring of guc variables, by checking the following conditions: * libraries should reserve at least one prefix if they define guc variables * if a library defined one or more prefixes, all variables should be defined within those prefixes * libraries shouldn't define variables in prefixes reserved by other libraries (even if they don't reserve a prefix) It has 4 possible values: * off, which is the existing earlier behavior, it does nothing * warning, in which violation of any of the above conditions results in an appropriate warning message * prefix, in which case the first condition is still only a warning, but the second and third result in an error * strict, in which case any violation results in an error The current patch sets the default of this value to off (or maybe it should be warning?), and later major versions can increase it to strict, after we are sure that most extensions follow these rules properly. --- src/backend/utils/fmgr/dfmgr.c | 38 ++++ src/backend/utils/init/miscinit.c | 4 + src/backend/utils/misc/guc.c | 167 +++++++++++++++++- src/backend/utils/misc/guc_parameters.dat | 9 + src/backend/utils/misc/guc_tables.c | 13 ++ src/include/fmgr.h | 1 + src/include/utils/guc.h | 17 ++ src/include/utils/guc_tables.h | 2 + src/test/modules/Makefile | 3 + src/test/modules/meson.build | 1 + .../test_guc_prefix_enforcement/Makefile | 17 ++ .../test_guc_prefix_enforcement/meson.build | 80 +++++++++ .../t/001_prefix_enforcement.pl | 161 +++++++++++++++++ .../test_guc_no_prefix.c | 45 +++++ .../test_guc_no_reserve.c | 42 +++++ .../test_guc_prefix_enforcement.c | 44 +++++ .../test_guc_wrong_prefix.c | 44 +++++ 17 files changed, 682 insertions(+), 6 deletions(-) create mode 100644 src/test/modules/test_guc_prefix_enforcement/Makefile create mode 100644 src/test/modules/test_guc_prefix_enforcement/meson.build create mode 100644 src/test/modules/test_guc_prefix_enforcement/t/001_prefix_enforcement.pl create mode 100644 src/test/modules/test_guc_prefix_enforcement/test_guc_no_prefix.c create mode 100644 src/test/modules/test_guc_prefix_enforcement/test_guc_no_reserve.c create mode 100644 src/test/modules/test_guc_prefix_enforcement/test_guc_prefix_enforcement.c create mode 100644 src/test/modules/test_guc_prefix_enforcement/test_guc_wrong_prefix.c diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index e636cc81cf8..44408f2f697 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -59,6 +59,12 @@ struct DynamicFileList static DynamicFileList *file_list = NULL; static DynamicFileList *file_tail = NULL; +/* + * Track the library currently being loaded (during _PG_init execution). + * This allows GUC code to know which library is defining custom variables. + */ +static const char *current_loading_library_name = NULL; + /* stat() call under Win32 returns an st_ino field, but it has no meaning */ #ifndef WIN32 #define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device) @@ -293,11 +299,33 @@ internal_load_library(const char *libname) /* * If the library has a _PG_init() function, call it. + * + * Set current_loading_library_name so that GUC code can track which + * library is defining custom variables. Use the module name from the + * magic block if available, otherwise extract from the filename. */ PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init"); if (PG_init) + { + if (file_scanner->magic->name != NULL) + current_loading_library_name = file_scanner->magic->name; + else + { + /* Extract module name from library path */ + const char *basename = strrchr(libname, '/'); + + if (basename) + basename++; + else + basename = libname; + current_loading_library_name = basename; + } + (*PG_init) (); + current_loading_library_name = NULL; + } + /* OK to link it into list */ if (file_list == NULL) file_list = file_scanner; @@ -746,3 +774,13 @@ RestoreLibraryState(char *start_address) start_address += strlen(start_address) + 1; } } + +/* + * Return the name of the library currently being loaded (during _PG_init), + * or NULL if no library is currently being loaded. + */ +const char * +get_current_loading_library_name(void) +{ + return current_loading_library_name; +} diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 563f20374ff..aaffe943b2b 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1856,6 +1856,8 @@ process_shared_preload_libraries(void) false); process_shared_preload_libraries_in_progress = false; process_shared_preload_libraries_done = true; + + check_guc_prefix_reservations(); } /* @@ -1870,6 +1872,8 @@ process_session_preload_libraries(void) load_libraries(local_preload_libraries_string, "local_preload_libraries", true); + + check_guc_prefix_reservations(); } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ae9d5f3fb70..1bd573a7e2a 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -32,6 +32,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/objectaccess.h" +#include "fmgr.h" #include "catalog/pg_authid.h" #include "catalog/pg_parameter_acl.h" #include "catalog/pg_type.h" @@ -76,6 +77,12 @@ static int GUC_check_errcode_value; +typedef struct ReservedGUCPrefix +{ + char *prefix; /* the reserved prefix (e.g., "myext") */ + char *library_name; /* library that reserved this prefix, or NULL */ +} ReservedGUCPrefix; + static List *reserved_class_prefix = NIL; /* global variables for check hook support */ @@ -259,6 +266,8 @@ static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable ** static bool valid_custom_variable_name(const char *name); static bool assignable_custom_variable_name(const char *name, bool skip_errors, int elevel); +static ReservedGUCPrefix *find_reserved_prefix_for_variable(const char *varname); +static bool library_has_reserved_prefix(const char *library_name); static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4); static bool call_bool_check_hook(const struct config_generic *conf, bool *newval, @@ -1022,10 +1031,10 @@ assignable_custom_variable_name(const char *name, bool skip_errors, int elevel) /* ... and it must not match any previously-reserved prefix */ foreach(lc, reserved_class_prefix) { - const char *rcprefix = lfirst(lc); + ReservedGUCPrefix *reservation = (ReservedGUCPrefix *) lfirst(lc); - if (strlen(rcprefix) == classLen && - strncmp(name, rcprefix, classLen) == 0) + if (strlen(reservation->prefix) == classLen && + strncmp(name, reservation->prefix, classLen) == 0) { if (!skip_errors) ereport(elevel, @@ -1033,7 +1042,7 @@ assignable_custom_variable_name(const char *name, bool skip_errors, int elevel) errmsg("invalid configuration parameter name \"%s\"", name), errdetail("\"%s\" is a reserved prefix.", - rcprefix))); + reservation->prefix))); return false; } } @@ -4788,6 +4797,19 @@ init_custom_variable(const char *name, gen->flags = flags; gen->vartype = type; + /* + * Record which library defined this variable, for GUC prefix enforcement. + * This will be NULL for variables defined outside of _PG_init context. + */ + { + const char *library_name = get_current_loading_library_name(); + + if (library_name) + gen->library_name = guc_strdup(FATAL, library_name); + else + gen->library_name = NULL; + } + return gen; } @@ -5143,6 +5165,8 @@ DefineCustomEnumVariable(const char *name, * and then prevents new ones from being created. * Extensions should call this after they've defined all of their custom * GUCs, to help catch misspelled config-file entries. + * + * Also records the library that reserved this prefix for enforcement purposes. */ void MarkGUCPrefixReserved(const char *className) @@ -5151,6 +5175,8 @@ MarkGUCPrefixReserved(const char *className) HASH_SEQ_STATUS status; GUCHashEntry *hentry; MemoryContext oldcontext; + ReservedGUCPrefix *reservation; + const char *library_name = get_current_loading_library_name(); /* * Check for existing placeholders. We must actually remove invalid @@ -5183,12 +5209,141 @@ MarkGUCPrefixReserved(const char *className) } } - /* And remember the name so we can prevent future mistakes. */ + /* + * Remember the prefix and its associated library so we can prevent + * future mistakes and enforce prefix ownership. + */ oldcontext = MemoryContextSwitchTo(GUCMemoryContext); - reserved_class_prefix = lappend(reserved_class_prefix, pstrdup(className)); + reservation = (ReservedGUCPrefix *) palloc(sizeof(ReservedGUCPrefix)); + reservation->prefix = pstrdup(className); + if (library_name) + reservation->library_name = pstrdup(library_name); + else + reservation->library_name = NULL; + reserved_class_prefix = lappend(reserved_class_prefix, reservation); MemoryContextSwitchTo(oldcontext); } +static ReservedGUCPrefix * +find_reserved_prefix_for_variable(const char *varname) +{ + ListCell *lc; + const char *sep; + int classLen; + + /* Find the class (prefix) portion of the variable name */ + sep = strchr(varname, GUC_QUALIFIER_SEPARATOR); + if (sep == NULL) + return NULL; + + classLen = sep - varname; + + foreach(lc, reserved_class_prefix) + { + ReservedGUCPrefix *reservation = (ReservedGUCPrefix *) lfirst(lc); + + if (strlen(reservation->prefix) == classLen && + strncmp(varname, reservation->prefix, classLen) == 0) + return reservation; + } + + return NULL; +} + +static bool +library_has_reserved_prefix(const char *library_name) +{ + ListCell *lc; + + if (library_name == NULL) + return false; + + foreach(lc, reserved_class_prefix) + { + ReservedGUCPrefix *reservation = (ReservedGUCPrefix *) lfirst(lc); + + if (reservation->library_name != NULL && + strcmp(reservation->library_name, library_name) == 0) + return true; + } + + return false; +} + +/* + * Check GUC prefix reservations after library loading. + * + * This function validates that: + * - In "prefix" mode: all custom GUCs are defined under a reserved prefix + * - In "strict" mode: all libraries that define custom GUCs have called + * MarkGUCPrefixReserved() + */ +void +check_guc_prefix_reservations(void) +{ + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + + if (guc_prefix_enforcement == GUC_PREFIX_ENFORCEMENT_OFF) + return; + + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *gconf = hentry->gucvar; + ReservedGUCPrefix *reservation; + int strict_elevel; + int prefix_elevel; + bool has_reserved_prefix; + + /* Skip placeholders and core variables */ + if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + continue; + if (gconf->library_name == NULL) + continue; + + strict_elevel = (guc_prefix_enforcement == GUC_PREFIX_ENFORCEMENT_STRICT) + ? FATAL : WARNING; + prefix_elevel = (guc_prefix_enforcement == GUC_PREFIX_ENFORCEMENT_WARN) + ? WARNING : FATAL; + + has_reserved_prefix = library_has_reserved_prefix(gconf->library_name); + if (!has_reserved_prefix) + { + ereport(strict_elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("extension \"%s\" defines GUC variables without calling MarkGUCPrefixReserved()", + gconf->library_name), + errdetail("Variable \"%s\" was defined without prefix reservation.", + gconf->name), + errhint("Extensions should call MarkGUCPrefixReserved() after defining their GUC variables."))); + } + + reservation = find_reserved_prefix_for_variable(gconf->name); + + if (reservation == NULL) + { + if (has_reserved_prefix) + { + ereport(prefix_elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("extension \"%s\" defines GUC variable \"%s\" outside any reserved prefix", + gconf->library_name, gconf->name), + errhint("Extensions should call MarkGUCPrefixReserved() to reserve a prefix for their variables."))); + } + } + else if (reservation->library_name != NULL && + strcmp(reservation->library_name, gconf->library_name) != 0) + { + /* Variable is under a prefix reserved by a different library */ + ereport(prefix_elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("extension \"%s\" defines GUC variable \"%s\" under prefix reserved by \"%s\"", + gconf->library_name, gconf->name, reservation->library_name))); + } + } +} + /* * Return an array of modified GUC options to show in EXPLAIN. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 7c60b125564..b1ea6121206 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1160,6 +1160,15 @@ boot_val => 'false', }, +{ name => 'guc_prefix_enforcement', type => 'enum', context => 'PGC_SIGHUP', group => 'DEVELOPER_OPTIONS', + short_desc => 'Enforcement mode for GUC prefix reservations.', + long_desc => 'Controls whether violations of GUC prefix reservations generate warnings or errors.', + flags => 'GUC_NOT_IN_SAMPLE', + variable => 'guc_prefix_enforcement', + boot_val => 'GUC_PREFIX_ENFORCEMENT_OFF', + options => 'guc_prefix_enforcement_options', +}, + { name => 'hash_mem_multiplier', type => 'real', context => 'PGC_USERSET', group => 'RESOURCES_MEM', short_desc => 'Multiple of "work_mem" to use for hash tables.', flags => 'GUC_EXPLAIN', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 73ff6ad0a32..0c492fd4fc9 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -491,6 +491,17 @@ static const struct config_enum_entry file_copy_method_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry guc_prefix_enforcement_options[] = { + {"off", GUC_PREFIX_ENFORCEMENT_OFF, false}, + {"warn", GUC_PREFIX_ENFORCEMENT_WARN, false}, + {"prefix", GUC_PREFIX_ENFORCEMENT_PREFIX, false}, + {"strict", GUC_PREFIX_ENFORCEMENT_STRICT, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(guc_prefix_enforcement_options) == (GUC_PREFIX_ENFORCEMENT_STRICT + 2), + "array length mismatch"); + /* * Options for enum values stored in other modules */ @@ -581,6 +592,8 @@ int huge_pages = HUGE_PAGES_TRY; int huge_page_size; int huge_pages_status = HUGE_PAGES_UNKNOWN; +int guc_prefix_enforcement = GUC_PREFIX_ENFORCEMENT_OFF; + /* * These variables are all dummies that don't do anything, except in some * cases provide the value for SHOW to display. The real state is elsewhere diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 22dd6526169..5a4dffc6e30 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -797,6 +797,7 @@ extern void get_loaded_module_details(DynamicFileList *dfptr, const char **module_name, const char **module_version); extern void **find_rendezvous_variable(const char *varName); +extern const char *get_current_loading_library_name(void); extern Size EstimateLibraryStateSpace(void); extern void SerializeLibraryState(Size maxsize, char *start_address); extern void RestoreLibraryState(char *start_address); diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index bf39878c43e..ee02b9aa987 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -126,6 +126,20 @@ typedef enum PGC_S_SESSION, /* SET command */ } GucSource; +/* + * Enforcement modes for GUC prefix reservations. + * + * This controls how strictly we enforce that extensions call + * MarkGUCPrefixReserved() and only define GUCs under their reserved prefix. + */ +typedef enum +{ + GUC_PREFIX_ENFORCEMENT_OFF, /* no enforcement (earlier behavior) */ + GUC_PREFIX_ENFORCEMENT_WARN, /* emit warnings on violations */ + GUC_PREFIX_ENFORCEMENT_PREFIX, /* ERROR if GUC defined outside reserved prefix */ + GUC_PREFIX_ENFORCEMENT_STRICT, /* ERROR if extension doesn't call MarkGUCPrefixReserved */ +} GucPrefixEnforcement; + /* * Parsing the configuration file(s) will return a list of name-value pairs * with source location info. We also abuse this data structure to carry @@ -287,6 +301,8 @@ extern PGDLLIMPORT bool log_statement_stats; extern PGDLLIMPORT bool log_btree_build_stats; extern PGDLLIMPORT char *event_source; +extern PGDLLIMPORT int guc_prefix_enforcement; + extern PGDLLIMPORT bool check_function_bodies; extern PGDLLIMPORT bool current_role_is_superuser; @@ -456,6 +472,7 @@ extern int set_config_with_handle(const char *name, config_handle *handle, GucAction action, bool changeVal, int elevel, bool is_reload); extern config_handle *get_config_handle(const char *name); +extern void check_guc_prefix_reservations(void); extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt); extern char *GetConfigOptionByName(const char *name, const char **varname, bool missing_ok); diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 71a80161961..1bbaa09212f 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -278,6 +278,8 @@ struct config_generic char *sourcefile; /* file current setting is from (NULL if not * set in config file) */ int sourceline; /* line in source file */ + const char *library_name; /* library that defined this variable, or NULL + * for core variables */ /* fields for specific variable types */ union diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 4c6d56d97d8..bf811e2ca7e 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -29,6 +29,9 @@ SUBDIRS = \ test_escape \ test_extensions \ test_ginpostinglist \ + test_guc_prefix_enforcement \ + test_hba_guc \ + test_hba_guc_contexts \ test_int128 \ test_integerset \ test_json_parser \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 1b31c5b98d6..da545219a92 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -29,6 +29,7 @@ subdir('test_dsm_registry') subdir('test_escape') subdir('test_extensions') subdir('test_ginpostinglist') +subdir('test_guc_prefix_enforcement') subdir('test_int128') subdir('test_integerset') subdir('test_json_parser') diff --git a/src/test/modules/test_guc_prefix_enforcement/Makefile b/src/test/modules/test_guc_prefix_enforcement/Makefile new file mode 100644 index 00000000000..5265c6ee37d --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/Makefile @@ -0,0 +1,17 @@ +# src/test/modules/test_guc_prefix_enforcement/Makefile + +MODULES = test_guc_prefix_enforcement test_guc_no_reserve test_guc_wrong_prefix test_guc_no_prefix +PGFILEDESC = "test_guc_prefix_enforcement - test module for GUC prefix reservation enforcement" + +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_guc_prefix_enforcement +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_guc_prefix_enforcement/meson.build b/src/test/modules/test_guc_prefix_enforcement/meson.build new file mode 100644 index 00000000000..ccf59f5330a --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/meson.build @@ -0,0 +1,80 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Main test module (good behavior) +test_guc_prefix_enforcement_sources = files( + 'test_guc_prefix_enforcement.c', +) + +if host_system == 'windows' + test_guc_prefix_enforcement_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_guc_prefix_enforcement', + '--FILEDESC', 'test_guc_prefix_enforcement - test module for GUC prefix reservation enforcement',]) +endif + +test_guc_prefix_enforcement = shared_module('test_guc_prefix_enforcement', + test_guc_prefix_enforcement_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_guc_prefix_enforcement + +# Test module without reservation +test_guc_no_reserve_sources = files( + 'test_guc_no_reserve.c', +) + +if host_system == 'windows' + test_guc_no_reserve_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_guc_no_reserve', + '--FILEDESC', 'test_guc_no_reserve - test module without prefix reservation',]) +endif + +test_guc_no_reserve = shared_module('test_guc_no_reserve', + test_guc_no_reserve_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_guc_no_reserve + +# Test module with wrong prefix +test_guc_wrong_prefix_sources = files( + 'test_guc_wrong_prefix.c', +) + +if host_system == 'windows' + test_guc_wrong_prefix_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_guc_wrong_prefix', + '--FILEDESC', 'test_guc_wrong_prefix - test module with wrong prefix reservation',]) +endif + +test_guc_wrong_prefix = shared_module('test_guc_wrong_prefix', + test_guc_wrong_prefix_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_guc_wrong_prefix + +# Test module with variable without prefix (no dot) +test_guc_no_prefix_sources = files( + 'test_guc_no_prefix.c', +) + +if host_system == 'windows' + test_guc_no_prefix_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_guc_no_prefix', + '--FILEDESC', 'test_guc_no_prefix - test module with variable without prefix',]) +endif + +test_guc_no_prefix = shared_module('test_guc_no_prefix', + test_guc_no_prefix_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_guc_no_prefix + +tests += { + 'name': 'test_guc_prefix_enforcement', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_prefix_enforcement.pl', + ], + }, +} diff --git a/src/test/modules/test_guc_prefix_enforcement/t/001_prefix_enforcement.pl b/src/test/modules/test_guc_prefix_enforcement/t/001_prefix_enforcement.pl new file mode 100644 index 00000000000..273f9b18fa1 --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/t/001_prefix_enforcement.pl @@ -0,0 +1,161 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Test GUC prefix reservation enforcement modes + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# +# Test 1: Default mode (off) - all extensions should load without errors +# +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_prefix_enforcement'"); +$node->start; + +my $result = $node->safe_psql('postgres', 'SHOW test_guc_prefix_enforcement.test_var'); +is($result, 'default', 'good extension loads with default enforcement (off)'); + +$node->stop; + +# +# Test 2: warn mode with good extension - should load with no warnings +# +$node = PostgreSQL::Test::Cluster->new('warn_good'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'warn'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_prefix_enforcement'"); +$node->start; + +$result = $node->safe_psql('postgres', 'SHOW test_guc_prefix_enforcement.test_var'); +is($result, 'default', 'good extension loads with warn enforcement'); + +# Verify no warnings were emitted +my $log = $node->logfile; +my $log_contents = slurp_file($log); +unlike($log_contents, qr/WARNING.*MarkGUCPrefixReserved/, + 'no warning about MarkGUCPrefixReserved for good extension'); +unlike($log_contents, qr/WARNING.*reserved prefix/, + 'no warning about reserved prefix for good extension'); + +$node->stop; + +# +# Test 3: warn mode with no_reserve extension - should load with warning +# +$node = PostgreSQL::Test::Cluster->new('warn_no_reserve'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'warn'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_no_reserve'"); + +# Start should succeed but log warnings +$node->start; +$result = $node->safe_psql('postgres', 'SHOW test_guc_no_reserve.some_var'); +is($result, 'default', 'no_reserve extension loads with warn enforcement'); + +# Check log for warning +$log = $node->logfile; +$log_contents = slurp_file($log); +like($log_contents, qr/WARNING.*without calling MarkGUCPrefixReserved/, + 'warn mode emits warning for extension without prefix reservation'); + +$node->stop; + +# +# Test 4: strict mode with no_reserve extension - should fail to start +# +$node = PostgreSQL::Test::Cluster->new('strict_no_reserve'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'strict'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_no_reserve'"); + +# Start should fail +my $ret = $node->start(fail_ok => 1); +is($ret, 0, 'strict mode prevents startup with non-compliant extension'); + +$log = $node->logfile; +$log_contents = slurp_file($log); +like($log_contents, qr/FATAL.*without calling MarkGUCPrefixReserved/, + 'strict mode emits error for extension without prefix reservation'); + +# +# Test 5: prefix mode with wrong_prefix extension - should fail to start +# +$node = PostgreSQL::Test::Cluster->new('prefix_wrong'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'prefix'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_wrong_prefix'"); + +# Start should fail +$ret = $node->start(fail_ok => 1); +is($ret, 0, 'prefix mode prevents startup with wrong prefix extension'); + +$log = $node->logfile; +$log_contents = slurp_file($log); +like($log_contents, qr/FATAL.*outside any reserved prefix/, + 'prefix mode emits error for extension defining GUC outside reserved prefix'); + +# +# Test 6: warn mode with no_prefix extension - should load with warning +# (extension reserves a prefix but defines variable without any prefix) +# +$node = PostgreSQL::Test::Cluster->new('warn_no_prefix'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'warn'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_no_prefix'"); +$node->start; + +$result = $node->safe_psql('postgres', 'SHOW test_guc_no_prefix_var'); +is($result, 'default', 'no_prefix extension loads with warn enforcement'); + +$log = $node->logfile; +$log_contents = slurp_file($log); +like($log_contents, qr/WARNING.*outside any reserved prefix/, + 'warn mode emits warning for extension defining GUC without prefix'); + +$node->stop; + +# +# Test 7: prefix mode with no_prefix extension - should fail to start +# (extension reserves a prefix but defines variable without any prefix) +# +$node = PostgreSQL::Test::Cluster->new('prefix_no_prefix'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'prefix'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_no_prefix'"); + +# Start should fail +$ret = $node->start(fail_ok => 1); +is($ret, 0, 'prefix mode prevents startup with unprefixed variable'); + +$log = $node->logfile; +$log_contents = slurp_file($log); +like($log_contents, qr/FATAL.*outside any reserved prefix/, + 'prefix mode emits error for extension defining GUC without prefix'); + +# +# Test 8: verify good extension works in strict mode +# +$node = PostgreSQL::Test::Cluster->new('strict_good'); +$node->init; +$node->append_conf('postgresql.conf', "guc_prefix_enforcement = 'strict'"); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_guc_prefix_enforcement'"); +$node->start; + +$result = $node->safe_psql('postgres', 'SHOW test_guc_prefix_enforcement.test_var'); +is($result, 'default', 'good extension loads successfully in strict mode'); + +# Verify no warnings or errors were emitted +$log = $node->logfile; +$log_contents = slurp_file($log); +unlike($log_contents, qr/(WARNING|FATAL).*MarkGUCPrefixReserved/, + 'no warning/error about MarkGUCPrefixReserved for good extension in strict mode'); +unlike($log_contents, qr/(WARNING|FATAL).*reserved prefix/, + 'no warning/error about reserved prefix for good extension in strict mode'); + +$node->stop; + +done_testing(); diff --git a/src/test/modules/test_guc_prefix_enforcement/test_guc_no_prefix.c b/src/test/modules/test_guc_prefix_enforcement/test_guc_no_prefix.c new file mode 100644 index 00000000000..610c9e15430 --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/test_guc_no_prefix.c @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- + * + * test_guc_no_prefix.c + * Test extension that defines a GUC variable without a prefix. + * + * This extension reserves a prefix but also defines a variable without + * a prefix, which should be flagged by prefix enforcement. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_guc_prefix_enforcement/test_guc_no_prefix.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC_EXT( + .name = "test_guc_no_prefix", + .version = PG_VERSION, +); + +static char *test_var = NULL; + +void +_PG_init(void) +{ + /* Define a variable without any prefix */ + DefineCustomStringVariable("test_guc_no_prefix_var", + "Test variable without prefix", + NULL, + &test_var, + "default", + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("test_guc_no_prefix"); +} diff --git a/src/test/modules/test_guc_prefix_enforcement/test_guc_no_reserve.c b/src/test/modules/test_guc_prefix_enforcement/test_guc_no_reserve.c new file mode 100644 index 00000000000..c57887ba88c --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/test_guc_no_reserve.c @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * test_guc_no_reserve.c + * Test module that defines GUCs without calling MarkGUCPrefixReserved + * + * This module intentionally does NOT call MarkGUCPrefixReserved() after + * defining its custom GUC variables. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_guc_prefix_enforcement/test_guc_no_reserve.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC_EXT( + .name = "test_guc_no_reserve", + .version = PG_VERSION, +); + +static char *no_reserve_var = NULL; + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_guc_no_reserve.some_var", + "A variable without prefix reservation", + NULL, + &no_reserve_var, + "default", + PGC_SUSET, + 0, + NULL, + NULL, + NULL); +} diff --git a/src/test/modules/test_guc_prefix_enforcement/test_guc_prefix_enforcement.c b/src/test/modules/test_guc_prefix_enforcement/test_guc_prefix_enforcement.c new file mode 100644 index 00000000000..1ccbbb1fb8e --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/test_guc_prefix_enforcement.c @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- + * + * test_guc_prefix_enforcement.c + * Test extension that properly reserves its GUC prefix. + * + * This extension defines a GUC variable and calls MarkGUCPrefixReserved(), + * demonstrating correct behavior. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_guc_prefix_enforcement/test_guc_prefix_enforcement.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC_EXT( + .name = "test_guc_prefix_enforcement", + .version = PG_VERSION, +); + +static char *test_var = NULL; + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_guc_prefix_enforcement.test_var", + "Test variable", + NULL, + &test_var, + "default", + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("test_guc_prefix_enforcement"); +} diff --git a/src/test/modules/test_guc_prefix_enforcement/test_guc_wrong_prefix.c b/src/test/modules/test_guc_prefix_enforcement/test_guc_wrong_prefix.c new file mode 100644 index 00000000000..4e94b367bb9 --- /dev/null +++ b/src/test/modules/test_guc_prefix_enforcement/test_guc_wrong_prefix.c @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- + * + * test_guc_wrong_prefix.c + * Test module that reserves one prefix but defines GUCs under another + * + * This module reserves the prefix "test_guc_wrong" but defines a variable + * under "test_guc_other". + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_guc_prefix_enforcement/test_guc_wrong_prefix.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC_EXT( + .name = "test_guc_wrong_prefix", + .version = PG_VERSION, +); + +static char *wrong_prefix_var = NULL; + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_guc_other.some_var", + "A variable under a different prefix than reserved", + NULL, + &wrong_prefix_var, + "default", + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("test_guc_wrong"); +} -- 2.43.0 [application/octet-stream] 0002-Introduce-PGC_HBA-GUC-variables-settable-in-pg_hba.c.patch (45.0K, 3-0002-Introduce-PGC_HBA-GUC-variables-settable-in-pg_hba.c.patch) download | inline diff: From 5fc38d2d922c3e201210a42e8546cbeb1587771d Mon Sep 17 00:00:00 2001 From: Zsolt Parragi <[email protected]> Date: Wed, 7 Jan 2026 19:25:11 +0000 Subject: [PATCH 2/2] Introduce PGC_HBA: GUC variables settable in pg_hba.conf Add a new GUC context level PGC_HBA that allows custom variables to be set based on which pg_hba.conf line matches during authentication. This enables authentication plugins and extensions to receive configuration parameters that vary based on HBA matching rules (client address, database, user, authentication method, etc.). Motivation: OAuth support introduced several OAuth-specific configuration parameters to pg_hba.conf, and added support for third-party validator plugins. These plugins often require additional configuration, which is only possible with GUC variables. As these plugins execute before authentication completes, they can only depend on GUC variables defined in postgresql.conf or postgresql.auto.conf. This limitation creates a configuration problem: pg_hba.conf allows administrators to define several different OAuth configurations for a single PostgreSQL instance, but validator plugin-specific variables can only be configured once per server. With the changes in this commit, validator plugins can now define their GUC variables with PGC_HBA context, allowing different settings per HBA line. Another use case is that extensions loaded via shared_preload_libraries or session_preload_libraries can define these variables, allowing administrators to configure authentication/authorization-related settings per HBA line, for example to help with row level security policies. User interface: The new parameters reuse the existing syntax of pg_hba.conf; GUC variables can be defined the same way as existing hardcoded parameters: host all all 192.168.1.0/24 oauth myext.setting=value1 host all all 0.0.0.0/0 oauth myext.setting=value2 This also provides an easy migration path in the future to convert existing pg_hba parameters to GUC variables. Extension interface: We define a new GUC context, PGC_HBA, and a new source, PGC_S_HBA. PGC_HBA variables must be defined during shared_preload_libraries or session_preload_libraries. This ensures all custom variables are registered before connections are accepted. PGC_HBA variables can be set from: - postgresql.conf - postgresql.auto.conf (ALTER SYSTEM) - pg_hba.conf (new) PGC_HBA variables cannot be set via: - ALTER USER SET / ALTER DATABASE SET - SET command - Connection parameters (PGOPTIONS) Additionally to PGC_HBA variables, SU_BACKEND, BACKEND, SUSET and USER variables can also be set in pg_hba. Error handling change: This feature requires a behavior change in pg_hba.conf error handling which may affect users not using the new feature. Since we allow both shared_preload_libraries and session_preload_libraries to define PGC_HBA variables (enabling updates of potentially security-related libraries without a server restart), we can no longer reject unknown parameters in pg_hba.conf at postmaster startup. Instead, unknown parameters are treated as placeholders (similar to custom GUC variables) and error handling is delayed until after session_preload_libraries completes. If any placeholder HBA variables remain undefined at that point, the connection is aborted with a FATAL error, even if authentication previously succeeded. --- src/backend/libpq/hba.c | 59 +++++- src/backend/utils/init/miscinit.c | 7 + src/backend/utils/init/postinit.c | 8 + src/backend/utils/misc/guc.c | 115 +++++++++++ src/backend/utils/misc/guc_tables.c | 2 + src/include/libpq/hba.h | 7 + src/include/miscadmin.h | 2 + src/include/utils/guc.h | 3 + src/test/modules/meson.build | 2 + src/test/modules/test_hba_guc/Makefile | 21 ++ src/test/modules/test_hba_guc/meson.build | 35 ++++ .../test_hba_guc/t/001_hba_guc_variables.pl | 56 ++++++ .../test_hba_guc/t/002_hba_guc_sources.pl | 187 ++++++++++++++++++ .../test_hba_guc/t/003_hba_guc_precedence.pl | 105 ++++++++++ .../test_hba_guc/test_hba_guc--1.0.sql | 22 +++ src/test/modules/test_hba_guc/test_hba_guc.c | 93 +++++++++ .../modules/test_hba_guc/test_hba_guc.conf | 3 + .../modules/test_hba_guc/test_hba_guc.control | 5 + .../modules/test_hba_guc_contexts/Makefile | 18 ++ .../modules/test_hba_guc_contexts/meson.build | 28 +++ .../t/001_context_validation.pl | 85 ++++++++ .../test_hba_guc_contexts.c | 89 +++++++++ 22 files changed, 943 insertions(+), 9 deletions(-) create mode 100644 src/test/modules/test_hba_guc/Makefile create mode 100644 src/test/modules/test_hba_guc/meson.build create mode 100644 src/test/modules/test_hba_guc/t/001_hba_guc_variables.pl create mode 100644 src/test/modules/test_hba_guc/t/002_hba_guc_sources.pl create mode 100644 src/test/modules/test_hba_guc/t/003_hba_guc_precedence.pl create mode 100644 src/test/modules/test_hba_guc/test_hba_guc--1.0.sql create mode 100644 src/test/modules/test_hba_guc/test_hba_guc.c create mode 100644 src/test/modules/test_hba_guc/test_hba_guc.conf create mode 100644 src/test/modules/test_hba_guc/test_hba_guc.control create mode 100644 src/test/modules/test_hba_guc_contexts/Makefile create mode 100644 src/test/modules/test_hba_guc_contexts/meson.build create mode 100644 src/test/modules/test_hba_guc_contexts/t/001_context_validation.pl create mode 100644 src/test/modules/test_hba_guc_contexts/test_hba_guc_contexts.c diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 87ee541e880..6fc4debf567 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2507,15 +2507,21 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, } else { - ereport(elevel, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("unrecognized authentication option name: \"%s\"", - name), - errcontext("line %d of configuration file \"%s\"", - line_num, file_name))); - *err_msg = psprintf("unrecognized authentication option name: \"%s\"", - name); - return false; + /* + * Unrecognized option name - treat it as a potential GUC variable. + * Store the name=value pair in the HbaLine's guc_options list. + * Actual validation (checking if the GUC is defined) happens at + * connection time after session_preload_libraries completes. + */ + HbaOption *opt; + + opt = palloc(sizeof(HbaOption)); + opt->name = pstrdup(name); + opt->value = pstrdup(val); + hbaline->guc_options = lappend(hbaline->guc_options, opt); + + elog(DEBUG2, "pg_hba.conf line %d: storing GUC option %s = %s", + line_num, name, val); } return true; } @@ -3113,11 +3119,44 @@ load_ident(void) +/* + * apply_hba_guc_options + * Apply GUC variable settings from the matched HBA line. + * + * This function processes the guc_options list from the matched pg_hba.conf + * line and applies each GUC setting. Variables will either be set (if already + * defined) or create placeholders (if not yet defined). Validation of + * undefined variables and context checking happens later, after + * session_preload_libraries completes in postinit.c. + */ +static void +apply_hba_guc_options(Port *port) +{ + ListCell *lc; + + if (port->hba->guc_options == NIL) + return; + + foreach(lc, port->hba->guc_options) + { + HbaOption *opt = (HbaOption *) lfirst(lc); + + elog(DEBUG2, "Applying HBA GUC option: %s = %s", opt->name, opt->value); + + (void) set_config_option(opt->name, opt->value, + PGC_HBA, PGC_S_HBA, + GUC_ACTION_SET, true, ERROR, false); + } +} + /* * Determine what authentication method should be used when accessing database * "database" from frontend "raddr", user "user". Return the method and * an optional argument (stored in fields of *port), and STATUS_OK. * + * Also applies all GUC variables from the matched HBA line, as these variables + * might immediately required by authentication plugins. + * * If the file does not contain any entry matching the request, we return * method = uaImplicitReject. */ @@ -3125,6 +3164,8 @@ void hba_getauthmethod(Port *port) { check_hba(port); + + apply_hba_guc_options(port); } diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index aaffe943b2b..2a714d308ce 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -1786,6 +1786,10 @@ char *local_preload_libraries_string = NULL; bool process_shared_preload_libraries_in_progress = false; bool process_shared_preload_libraries_done = false; +/* Flag telling that we are loading session_preload_libraries */ +bool process_session_preload_libraries_in_progress = false; +bool process_session_preload_libraries_done = false; + shmem_request_hook_type shmem_request_hook = NULL; bool process_shmem_requests_in_progress = false; @@ -1866,9 +1870,12 @@ process_shared_preload_libraries(void) void process_session_preload_libraries(void) { + process_session_preload_libraries_in_progress = true; load_libraries(session_preload_libraries_string, "session_preload_libraries", false); + process_session_preload_libraries_in_progress = false; + process_session_preload_libraries_done = true; load_libraries(local_preload_libraries_string, "local_preload_libraries", true); diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 52c05a9d1d5..d3bb88243c6 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -33,6 +33,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/pg_tablespace.h" +#include "lib/stringinfo.h" #include "libpq/auth.h" #include "libpq/libpq-be.h" #include "mb/pg_wchar.h" @@ -1225,6 +1226,13 @@ InitPostgres(const char *in_dbname, Oid dboid, if ((flags & INIT_PG_LOAD_SESSION_LIBS) != 0) process_session_preload_libraries(); + /* + * Now that session_preload_libraries has completed, validate that all + * GUC variables set from pg_hba.conf are properly defined with the + * correct context. + */ + check_hba_guc_variables(); + /* fill in the remainder of this entry in the PgBackendStatus array */ if (!bootstrap) pgstat_bestart_final(); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 1bd573a7e2a..3aef1a4d498 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3403,6 +3403,17 @@ set_config_with_handle(const char *name, config_handle *handle, * signals to individual backends only. */ break; + case PGC_HBA: + if (context != PGC_SIGHUP && context != PGC_POSTMASTER && + source != PGC_S_HBA) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed now", + record->name))); + return 0; + } + break; case PGC_SU_BACKEND: if (context == PGC_BACKEND) { @@ -3456,6 +3467,7 @@ set_config_with_handle(const char *name, config_handle *handle, else if (context != PGC_POSTMASTER && context != PGC_BACKEND && context != PGC_SU_BACKEND && + context != PGC_HBA && source != PGC_S_CLIENT) { ereport(elevel, @@ -4164,6 +4176,87 @@ get_config_handle(const char *name) return NULL; } +/* + * check_hba_guc_variables + * + * Check if any GUC variables set from pg_hba.conf (source = PGC_S_HBA) + * are still placeholders (undefined) or have the wrong context. + * + * Also enforces strict GUC prefix reservation for PGC_HBA variables: + * extensions that define PGC_HBA variables MUST call MarkGUCPrefixReserved() + * and their variables MUST be under the reserved prefix. This is always + * enforced regardless of the guc_prefix_enforcement setting, because + * PGC_HBA variables can affect authentication security. + * + * For each invalid variable, we emit a FATAL error with an appropriate + * message explaining the problem. + * + * This should be called after session_preload_libraries completes to + * ensure extensions have had a chance to define their PGC_HBA variables. + */ +void +check_hba_guc_variables(void) +{ + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *gconf = hentry->gucvar; + + if (gconf->source != PGC_S_HBA) + continue; + + if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + { + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("authentication configuration error"), + errdetail("pg_hba.conf references undefined GUC variable \"%s\"", + gconf->name), + errhint("Ensure the extension defining this variable is loaded in session_preload_libraries or shared_preload_libraries."))); + } + + if (gconf->context < PGC_HBA) + { + ereport(FATAL, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be set in pg_hba.conf", + gconf->name), + errdetail("Only variables with context PGC_HBA or below can be set from pg_hba.conf."), + errhint("This variable has context \"%s\".", + GucContext_Names[gconf->context]))); + } + + if (gconf->library_name != NULL) + { + ReservedGUCPrefix *reservation; + + reservation = find_reserved_prefix_for_variable(gconf->name); + + if (reservation == NULL) + { + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("authentication configuration error"), + errdetail("Extension \"%s\" defines PGC_HBA variable \"%s\" without a reserved prefix.", + gconf->library_name, gconf->name), + errhint("Extensions that define PGC_HBA variables must call MarkGUCPrefixReserved()."))); + } + else if (reservation->library_name != NULL && + strcmp(reservation->library_name, gconf->library_name) != 0) + { + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("authentication configuration error"), + errdetail("Extension \"%s\" defines PGC_HBA variable \"%s\" under prefix reserved by \"%s\".", + gconf->library_name, gconf->name, reservation->library_name))); + } + } + } +} + /* * Set the fields for source file and line number the setting came from. @@ -4765,6 +4858,19 @@ init_custom_variable(const char *name, !process_shared_preload_libraries_in_progress) elog(FATAL, "cannot create PGC_POSTMASTER variables after startup"); + /* + * Only allow custom PGC_HBA variables to be created before + * session_preload_libraries completes. After that point, authentication + * has already occurred and check_hba_guc_variables has validated all + * PGC_HBA variables, so defining new ones would bypass validation. + */ + if (context == PGC_HBA && process_session_preload_libraries_done) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("PGC_HBA variables must be defined before session_preload_libraries completes"), + errdetail("Attempted to define \"%s\" after session preload", name), + errhint("Move the extension defining this variable to shared_preload_libraries or session_preload_libraries"))); + /* * We can't support custom GUC_LIST_QUOTE variables, because the wrong * things would happen if such a variable were set or pg_dump'd when the @@ -6758,6 +6864,15 @@ validate_option_array_item(const char *name, const char *value, (superuser() || pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)) /* ok */ ; + else if (gconf->context == PGC_HBA) + { + if (skipIfNoPermissions) + return false; + ereport(ERROR, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be set by ALTER USER or ALTER DATABASE", name), + errhint("Use postgresql.conf, ALTER SYSTEM, or pg_hba.conf to set this parameter."))); + } else if (skipIfNoPermissions) return false; /* if a permissions error should be thrown, let set_config_option do it */ diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 0c492fd4fc9..58386ecc585 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -671,6 +671,7 @@ const char *const GucContext_Names[] = [PGC_INTERNAL] = "internal", [PGC_POSTMASTER] = "postmaster", [PGC_SIGHUP] = "sighup", + [PGC_HBA] = "hba", [PGC_SU_BACKEND] = "superuser-backend", [PGC_BACKEND] = "backend", [PGC_SUSET] = "superuser", @@ -697,6 +698,7 @@ const char *const GucSource_Names[] = [PGC_S_USER] = "user", [PGC_S_DATABASE_USER] = "database user", [PGC_S_CLIENT] = "client", + [PGC_S_HBA] = "pg_hba.conf", [PGC_S_OVERRIDE] = "override", [PGC_S_INTERACTIVE] = "interactive", [PGC_S_TEST] = "test", diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 7b93ba4a709..e1848ad03d0 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -92,6 +92,12 @@ typedef struct AuthToken regex_t *regex; } AuthToken; +typedef struct HbaOption +{ + char *name; + char *value; +} HbaOption; + typedef struct HbaLine { char *sourcefile; @@ -140,6 +146,7 @@ typedef struct HbaLine char *oauth_scope; char *oauth_validator; bool oauth_skip_usermap; + List *guc_options; } HbaLine; typedef struct IdentLine diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index db559b39c4d..0a5df62f99a 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -512,6 +512,8 @@ extern void BaseInit(void); extern PGDLLIMPORT bool IgnoreSystemIndexes; extern PGDLLIMPORT bool process_shared_preload_libraries_in_progress; extern PGDLLIMPORT bool process_shared_preload_libraries_done; +extern PGDLLIMPORT bool process_session_preload_libraries_in_progress; +extern PGDLLIMPORT bool process_session_preload_libraries_done; extern PGDLLIMPORT bool process_shmem_requests_in_progress; extern PGDLLIMPORT char *session_preload_libraries_string; extern PGDLLIMPORT char *shared_preload_libraries_string; diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ee02b9aa987..26bda29444b 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -73,6 +73,7 @@ typedef enum PGC_INTERNAL, PGC_POSTMASTER, PGC_SIGHUP, + PGC_HBA, PGC_SU_BACKEND, PGC_BACKEND, PGC_SUSET, @@ -115,6 +116,7 @@ typedef enum PGC_S_ENV_VAR, /* postmaster environment variable */ PGC_S_FILE, /* postgresql.conf */ PGC_S_ARGV, /* postmaster command line */ + PGC_S_HBA, /* from pg_hba.conf */ PGC_S_GLOBAL, /* global in-database setting */ PGC_S_DATABASE, /* per-database setting */ PGC_S_USER, /* per-user setting */ @@ -473,6 +475,7 @@ extern int set_config_with_handle(const char *name, config_handle *handle, int elevel, bool is_reload); extern config_handle *get_config_handle(const char *name); extern void check_guc_prefix_reservations(void); +extern void check_hba_guc_variables(void); extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt); extern char *GetConfigOptionByName(const char *name, const char **varname, bool missing_ok); diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index da545219a92..f0e51bb4a5d 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -30,6 +30,8 @@ subdir('test_escape') subdir('test_extensions') subdir('test_ginpostinglist') subdir('test_guc_prefix_enforcement') +subdir('test_hba_guc') +subdir('test_hba_guc_contexts') subdir('test_int128') subdir('test_integerset') subdir('test_json_parser') diff --git a/src/test/modules/test_hba_guc/Makefile b/src/test/modules/test_hba_guc/Makefile new file mode 100644 index 00000000000..2b89088e338 --- /dev/null +++ b/src/test/modules/test_hba_guc/Makefile @@ -0,0 +1,21 @@ +# src/test/modules/test_hba_guc/Makefile + +MODULE_big = test_hba_guc +OBJS = \ + $(WIN32RES) \ + test_hba_guc.o +PGFILEDESC = "test_hba_guc - test module for PGC_HBA GUC variables" + +EXTENSION = test_hba_guc +DATA = test_hba_guc--1.0.sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_hba_guc +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_hba_guc/meson.build b/src/test/modules/test_hba_guc/meson.build new file mode 100644 index 00000000000..d8bf0233056 --- /dev/null +++ b/src/test/modules/test_hba_guc/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +test_hba_guc_sources = files( + 'test_hba_guc.c', +) + +if host_system == 'windows' + test_hba_guc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_hba_guc', + '--FILEDESC', 'test_hba_guc - test module for PGC_HBA GUC variables',]) +endif + +test_hba_guc = shared_module('test_hba_guc', + test_hba_guc_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_hba_guc + +test_install_data += files( + 'test_hba_guc.control', + 'test_hba_guc--1.0.sql', +) + +tests += { + 'name': 'test_hba_guc', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_hba_guc_variables.pl', + 't/002_hba_guc_sources.pl', + 't/003_hba_guc_precedence.pl', + ], + }, +} diff --git a/src/test/modules/test_hba_guc/t/001_hba_guc_variables.pl b/src/test/modules/test_hba_guc/t/001_hba_guc_variables.pl new file mode 100644 index 00000000000..625f3be1e51 --- /dev/null +++ b/src/test/modules/test_hba_guc/t/001_hba_guc_variables.pl @@ -0,0 +1,56 @@ +# Test PGC_HBA GUC variables in pg_hba.conf + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; + +$node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + +my $hba_conf = $node->data_dir . '/pg_hba.conf'; +open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; +print $hba_fh "# Test HBA configuration with GUC variables\n"; +print $hba_fh "local all all trust test_hba_guc.string_var=from_hba test_hba_guc.int_var=999\n"; +close $hba_fh; + +$node->start; +$node->safe_psql('postgres', 'CREATE EXTENSION test_hba_guc;'); +my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var'), current_setting('test_hba_guc.int_var');" +); +is($result, 'from_hba|999', + 'GUC variables set from pg_hba.conf are accessible'); + +$result = $node->safe_psql('postgres', + "SELECT source FROM pg_settings WHERE name = 'test_hba_guc.string_var';" +); +is($result, 'pg_hba.conf', + 'GUC variable source shows pg_hba.conf'); + +my $node_no_ext = PostgreSQL::Test::Cluster->new('no_extension'); +$node_no_ext->init; + +my $hba_conf_no_ext = $node_no_ext->data_dir . '/pg_hba.conf'; +open my $hba_no_ext_fh, '>', $hba_conf_no_ext or die "Could not open $hba_conf_no_ext: $!"; +print $hba_no_ext_fh "# Test HBA configuration with undefined GUC variables\n"; +print $hba_no_ext_fh "local all all trust test_hba_guc.undefined_var=value\n"; +close $hba_no_ext_fh; + +$node_no_ext->start; + +my ($ret, $stdout, $stderr) = $node_no_ext->psql('postgres', 'SELECT 1;'); +isnt($ret, 0, 'Connection rejected when HBA GUC variable is undefined'); +like($stderr, qr/authentication configuration error/, + 'Error message indicates authentication configuration problem'); +like($stderr, qr/undefined GUC variable/, + 'Error message mentions undefined GUC variable'); + +$node_no_ext->stop; +$node->stop; + +done_testing(); diff --git a/src/test/modules/test_hba_guc/t/002_hba_guc_sources.pl b/src/test/modules/test_hba_guc/t/002_hba_guc_sources.pl new file mode 100644 index 00000000000..e762b8a96d8 --- /dev/null +++ b/src/test/modules/test_hba_guc/t/002_hba_guc_sources.pl @@ -0,0 +1,187 @@ +# Test that PGC_HBA variables can only be set from appropriate sources +# +# PGC_HBA variables should be settable from: +# - postgresql.conf +# - postgresql.auto.conf (ALTER SYSTEM) +# - pg_hba.conf +# +# But NOT from: +# - ALTER USER SET +# - ALTER DATABASE SET +# - Connection parameters (PGOPTIONS) + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Test 1: PGC_HBA variable CAN be set in postgresql.conf +{ + my $node = PostgreSQL::Test::Cluster->new('conf_allowed'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + $node->append_conf('postgresql.conf', + "test_hba_guc.string_var = 'from_postgresql_conf'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'from_postgresql_conf', + 'PGC_HBA variable can be set in postgresql.conf'); + + $result = $node->safe_psql('postgres', + "SELECT source FROM pg_settings WHERE name = 'test_hba_guc.string_var';"); + is($result, 'configuration file', + 'Source shows configuration file for postgresql.conf setting'); + + $node->stop; +} + +# Test 2: PGC_HBA variable CAN be set via ALTER SYSTEM (postgresql.auto.conf) +{ + my $node = PostgreSQL::Test::Cluster->new('alter_system_allowed'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + $node->safe_psql('postgres', + "ALTER SYSTEM SET test_hba_guc.string_var = 'from_alter_system';"); + + $node->reload; + + my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'from_alter_system', + 'PGC_HBA variable can be set via ALTER SYSTEM'); + + $result = $node->safe_psql('postgres', + "SELECT source FROM pg_settings WHERE name = 'test_hba_guc.string_var';"); + is($result, 'configuration file', + 'Source shows configuration file for ALTER SYSTEM setting'); + + $node->stop; +} + +# Test 3: PGC_HBA variable CANNOT be set via ALTER USER SET +{ + my $node = PostgreSQL::Test::Cluster->new('alter_user_rejected'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + $node->safe_psql('postgres', "CREATE USER testuser;"); + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + "ALTER USER testuser SET test_hba_guc.string_var = 'from_alter_user';"); + isnt($ret, 0, 'ALTER USER SET rejected for PGC_HBA variable'); + like($stderr, qr/cannot be set by ALTER USER or ALTER DATABASE/, + 'Error message indicates ALTER USER is not allowed for PGC_HBA'); + + $node->stop; +} + +# Test 4: PGC_HBA variable CANNOT be set via ALTER DATABASE SET +{ + my $node = PostgreSQL::Test::Cluster->new('alter_database_rejected'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + "ALTER DATABASE postgres SET test_hba_guc.string_var = 'from_alter_database';"); + isnt($ret, 0, 'ALTER DATABASE SET rejected for PGC_HBA variable'); + like($stderr, qr/cannot be set by ALTER USER or ALTER DATABASE/, + 'Error message indicates ALTER DATABASE is not allowed for PGC_HBA'); + + $node->stop; +} + +# Test 5: PGC_HBA variable CANNOT be set via connection parameter (PGOPTIONS) +{ + my $node = PostgreSQL::Test::Cluster->new('pgoptions_rejected'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + # Connection succeeds but parameter is not set (gets default value) + local $ENV{PGOPTIONS} = '-c test_hba_guc.string_var=from_pgoptions'; + my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'default_value', + 'PGC_HBA variable from PGOPTIONS is not set, uses default value'); + + my $logfile = $node->logfile; + my $log_content = slurp_file($logfile); + like($log_content, qr/parameter "test_hba_guc\.string_var" cannot be changed now/, + 'Warning logged when trying to set PGC_HBA via PGOPTIONS'); + + $node->stop; +} + +# Test 6: PGC_HBA variable CANNOT be set via SET command +{ + my $node = PostgreSQL::Test::Cluster->new('set_rejected'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + close $hba_fh; + + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + "SET test_hba_guc.string_var = 'from_set';"); + isnt($ret, 0, 'SET command rejected for PGC_HBA variable'); + like($stderr, qr/cannot be changed|cannot be set/, + 'Error message indicates SET is not allowed for PGC_HBA'); + + $node->stop; +} + +done_testing(); diff --git a/src/test/modules/test_hba_guc/t/003_hba_guc_precedence.pl b/src/test/modules/test_hba_guc/t/003_hba_guc_precedence.pl new file mode 100644 index 00000000000..4afaad95c73 --- /dev/null +++ b/src/test/modules/test_hba_guc/t/003_hba_guc_precedence.pl @@ -0,0 +1,105 @@ +# Test that PGC_HBA variables from pg_hba.conf respect line precedence +# +# pg_hba.conf is evaluated top-to-bottom, and the first matching line wins. +# This test verifies that GUC values are taken from the correct matching line. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Test 1: First matching line wins (same user, same database, same auth methods) +{ + my $node = PostgreSQL::Test::Cluster->new('first_match_wins'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust test_hba_guc.string_var=first_line\n"; + print $hba_fh "local all all trust test_hba_guc.string_var=second_line\n"; + print $hba_fh "local all all trust test_hba_guc.string_var=third_line\n"; + close $hba_fh; + + $node->start; + + my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'first_line', + 'First matching HBA line wins when multiple lines match'); + + $node->stop; +} + +# Test 2: Database-specific GUC values +{ + my $node = PostgreSQL::Test::Cluster->new('database_specific'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + # Create test databases + $node->start; + $node->safe_psql('postgres', 'CREATE DATABASE testdb1;'); + $node->safe_psql('postgres', 'CREATE DATABASE testdb2;'); + $node->stop; + + # Create pg_hba.conf with database-specific values + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local testdb1 all trust test_hba_guc.string_var=from_testdb1\n"; + print $hba_fh "local testdb2 all trust test_hba_guc.string_var=from_testdb2\n"; + print $hba_fh "local all all trust test_hba_guc.string_var=from_wildcard\n"; + close $hba_fh; + + $node->start; + + my $result = $node->safe_psql('testdb1', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'from_testdb1', + 'Database-specific GUC value applied for testdb1'); + + $result = $node->safe_psql('testdb2', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'from_testdb2', + 'Database-specific GUC value applied for testdb2'); + + $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'from_wildcard', + 'Wildcard GUC value applied for postgres database'); + + $node->stop; +} + +# Test 3: Empty/no GUC options on first match, GUC options on later line +# The first matching line wins even if it has no GUC options +{ + my $node = PostgreSQL::Test::Cluster->new('no_gucs_first'); + $node->init; + + $node->append_conf('postgresql.conf', + "session_preload_libraries = 'test_hba_guc'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust\n"; + print $hba_fh "local all all trust test_hba_guc.string_var=not_used\n"; + close $hba_fh; + + $node->start; + + # Should get default value since first matching line has no GUC options + my $result = $node->safe_psql('postgres', + "SELECT current_setting('test_hba_guc.string_var');"); + is($result, 'default_value', + 'Default GUC value when first matching line has no GUC options'); + + $node->stop; +} + +done_testing(); diff --git a/src/test/modules/test_hba_guc/test_hba_guc--1.0.sql b/src/test/modules/test_hba_guc/test_hba_guc--1.0.sql new file mode 100644 index 00000000000..eb29d174f52 --- /dev/null +++ b/src/test/modules/test_hba_guc/test_hba_guc--1.0.sql @@ -0,0 +1,22 @@ +/* src/test/modules/test_hba_guc/test_hba_guc--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_hba_guc" to load this file. \quit + +-- Function to get current value of test_hba_guc.string_var +CREATE FUNCTION get_test_hba_string() +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- Function to get current value of test_hba_guc.int_var +CREATE FUNCTION get_test_hba_int() +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- Function to get current value of test_hba_guc.bool_var +CREATE FUNCTION get_test_hba_bool() +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; diff --git a/src/test/modules/test_hba_guc/test_hba_guc.c b/src/test/modules/test_hba_guc/test_hba_guc.c new file mode 100644 index 00000000000..74b4a559ac5 --- /dev/null +++ b/src/test/modules/test_hba_guc/test_hba_guc.c @@ -0,0 +1,93 @@ +/*------------------------------------------------------------------------- + * + * test_hba_guc.c + * Test module for PGC_HBA GUC variables + * + * This module tests the PGC_HBA context level for GUC variables, which + * allows variables to be set in pg_hba.conf or postgresql.conf but not + * by client connections. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_hba_guc/test_hba_guc.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/builtins.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +static char *test_hba_string = NULL; +static int test_hba_int = 0; +static bool test_hba_bool = false; + +PG_FUNCTION_INFO_V1(get_test_hba_string); +PG_FUNCTION_INFO_V1(get_test_hba_int); +PG_FUNCTION_INFO_V1(get_test_hba_bool); + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_hba_guc.string_var", + "Test PGC_HBA string variable", + "This variable can only be set in pg_hba.conf or postgresql.conf", + &test_hba_string, + "default_value", + PGC_HBA, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("test_hba_guc.int_var", + "Test PGC_HBA integer variable", + "This variable can only be set in pg_hba.conf or postgresql.conf", + &test_hba_int, + 42, + 0, + 10000, + PGC_HBA, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("test_hba_guc.bool_var", + "Test PGC_HBA boolean variable", + "This variable can only be set in pg_hba.conf or postgresql.conf", + &test_hba_bool, + false, + PGC_HBA, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("test_hba_guc"); +} + +Datum +get_test_hba_string(PG_FUNCTION_ARGS) +{ + if (test_hba_string == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(cstring_to_text(test_hba_string)); +} + +Datum +get_test_hba_int(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(test_hba_int); +} + +Datum +get_test_hba_bool(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(test_hba_bool); +} diff --git a/src/test/modules/test_hba_guc/test_hba_guc.conf b/src/test/modules/test_hba_guc/test_hba_guc.conf new file mode 100644 index 00000000000..29ee434a784 --- /dev/null +++ b/src/test/modules/test_hba_guc/test_hba_guc.conf @@ -0,0 +1,3 @@ +# Configuration for test_hba_guc regression tests +# Load in session_preload_libraries to test the proper validation flow +session_preload_libraries = 'test_hba_guc' diff --git a/src/test/modules/test_hba_guc/test_hba_guc.control b/src/test/modules/test_hba_guc/test_hba_guc.control new file mode 100644 index 00000000000..1f4a99069ba --- /dev/null +++ b/src/test/modules/test_hba_guc/test_hba_guc.control @@ -0,0 +1,5 @@ +# test_hba_guc extension +comment = 'Test module for PGC_HBA GUC variables' +default_version = '1.0' +module_pathname = '$libdir/test_hba_guc' +relocatable = true diff --git a/src/test/modules/test_hba_guc_contexts/Makefile b/src/test/modules/test_hba_guc_contexts/Makefile new file mode 100644 index 00000000000..7f805b0abd8 --- /dev/null +++ b/src/test/modules/test_hba_guc_contexts/Makefile @@ -0,0 +1,18 @@ +# src/test/modules/test_hba_guc_contexts/Makefile + +MODULE_big = test_hba_guc_contexts +OBJS = \ + $(WIN32RES) \ + test_hba_guc_contexts.o +PGFILEDESC = "test_hba_guc_contexts - test module for PGC_HBA context validation" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_hba_guc_contexts +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_hba_guc_contexts/meson.build b/src/test/modules/test_hba_guc_contexts/meson.build new file mode 100644 index 00000000000..e2c0eeed229 --- /dev/null +++ b/src/test/modules/test_hba_guc_contexts/meson.build @@ -0,0 +1,28 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +test_hba_guc_contexts_sources = files( + 'test_hba_guc_contexts.c', +) + +if host_system == 'windows' + test_hba_guc_contexts_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_hba_guc_contexts', + '--FILEDESC', 'test_hba_guc_contexts - test module for PGC_HBA context validation',]) +endif + +test_hba_guc_contexts = shared_module('test_hba_guc_contexts', + test_hba_guc_contexts_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_hba_guc_contexts + +tests += { + 'name': 'test_hba_guc_contexts', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_context_validation.pl', + ], + }, +} diff --git a/src/test/modules/test_hba_guc_contexts/t/001_context_validation.pl b/src/test/modules/test_hba_guc_contexts/t/001_context_validation.pl new file mode 100644 index 00000000000..35d540c04e8 --- /dev/null +++ b/src/test/modules/test_hba_guc_contexts/t/001_context_validation.pl @@ -0,0 +1,85 @@ +# Test that only variables with context PGC_HBA or below can be set from pg_hba.conf + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +sub test_hba_rejected +{ + my ($node_name, $var_name, $test_desc) = @_; + + my $node = PostgreSQL::Test::Cluster->new($node_name); + $node->init; + + $node->append_conf('postgresql.conf', + "shared_preload_libraries = 'test_hba_guc_contexts'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust test_hba_guc_contexts.$var_name=from_hba\n"; + close $hba_fh; + + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', 'SELECT 1;'); + isnt($ret, 0, $test_desc); + like($stderr, qr/cannot be changed|cannot be set/, + 'Error message indicates variable cannot be set from pg_hba.conf'); + + $node->stop; + return; +} + +sub test_hba_accepted +{ + my ($node_name, $var_name, $test_desc) = @_; + + my $node = PostgreSQL::Test::Cluster->new($node_name); + $node->init; + + $node->append_conf('postgresql.conf', + "shared_preload_libraries = 'test_hba_guc_contexts'"); + + my $hba_conf = $node->data_dir . '/pg_hba.conf'; + open my $hba_fh, '>', $hba_conf or die "Could not open $hba_conf: $!"; + print $hba_fh "local all all trust test_hba_guc_contexts.$var_name=from_hba\n"; + close $hba_fh; + + $node->start; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', + "SHOW test_hba_guc_contexts.$var_name;"); + is($ret, 0, $test_desc); + like($stdout, qr/from_hba/, 'Variable was set to value from pg_hba.conf'); + + $node->stop; + return; +} + +# Test 1: PGC_POSTMASTER variable should NOT be settable from pg_hba.conf +test_hba_rejected('postmaster_rejected', 'postmaster_var', + 'Connection rejected when trying to set PGC_POSTMASTER from pg_hba.conf'); + +# Test 2: PGC_SIGHUP variable should NOT be settable from pg_hba.conf +test_hba_rejected('sighup_rejected', 'sighup_var', + 'Connection rejected when trying to set PGC_SIGHUP from pg_hba.conf'); + +# Test 3: PGC_SU_BACKEND variable SHOULD be settable from pg_hba.conf +test_hba_accepted('su_backend_accepted', 'su_backend_var', + 'Connection accepted when setting PGC_SU_BACKEND from pg_hba.conf'); + +# Test 4: PGC_BACKEND variable SHOULD be settable from pg_hba.conf +test_hba_accepted('backend_accepted', 'backend_var', + 'Connection accepted when setting PGC_BACKEND from pg_hba.conf'); + +# Test 5: PGC_SUSET variable SHOULD be settable from pg_hba.conf +test_hba_accepted('suset_accepted', 'suset_var', + 'Connection accepted when setting PGC_SUSET from pg_hba.conf'); + +# Test 6: PGC_USERSET variable SHOULD be settable from pg_hba.conf +test_hba_accepted('userset_accepted', 'userset_var', + 'Connection accepted when setting PGC_USERSET from pg_hba.conf'); + +done_testing(); diff --git a/src/test/modules/test_hba_guc_contexts/test_hba_guc_contexts.c b/src/test/modules/test_hba_guc_contexts/test_hba_guc_contexts.c new file mode 100644 index 00000000000..0859f70174a --- /dev/null +++ b/src/test/modules/test_hba_guc_contexts/test_hba_guc_contexts.c @@ -0,0 +1,89 @@ +/*------------------------------------------------------------------------- + * + * test_hba_guc_contexts.c + * Test module for validating PGC_HBA context restrictions + * + * This module defines GUC variables with different context levels to test + * that only variables with context PGC_HBA or below can be set from pg_hba.conf. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_hba_guc_contexts/test_hba_guc_contexts.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +static char *test_postmaster_var = NULL; +static char *test_sighup_var = NULL; +static char *test_su_backend_var = NULL; +static char *test_backend_var = NULL; +static char *test_suset_var = NULL; +static char *test_userset_var = NULL; + +void +_PG_init(void) +{ + DefineCustomStringVariable("test_hba_guc_contexts.postmaster_var", + "Test PGC_POSTMASTER variable", + "This should NOT be settable from pg_hba.conf", + &test_postmaster_var, + "postmaster_default", + PGC_POSTMASTER, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_hba_guc_contexts.sighup_var", + "Test PGC_SIGHUP variable", + "This should NOT be settable from pg_hba.conf", + &test_sighup_var, + "sighup_default", + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_hba_guc_contexts.su_backend_var", + "Test PGC_SU_BACKEND variable", + "This SHOULD be settable from pg_hba.conf", + &test_su_backend_var, + "su_backend_default", + PGC_SU_BACKEND, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_hba_guc_contexts.backend_var", + "Test PGC_BACKEND variable", + "This SHOULD be settable from pg_hba.conf", + &test_backend_var, + "backend_default", + PGC_BACKEND, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_hba_guc_contexts.suset_var", + "Test PGC_SUSET variable", + "This SHOULD be settable from pg_hba.conf", + &test_suset_var, + "suset_default", + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_hba_guc_contexts.userset_var", + "Test PGC_USERSET variable", + "This SHOULD be settable from pg_hba.conf", + &test_userset_var, + "userset_default", + PGC_USERSET, + 0, + NULL, NULL, NULL); + + MarkGUCPrefixReserved("test_hba_guc_contexts"); +} -- 2.43.0 ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2026-01-28 16:04 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-01-19 20:30 Re: Custom oauth validator options Zsolt Parragi <[email protected]> 2026-01-20 18:02 ` Jacob Champion <[email protected]> 2026-01-20 20:31 ` Zsolt Parragi <[email protected]> 2026-01-24 00:04 ` Jacob Champion <[email protected]> 2026-01-26 09:51 ` Zsolt Parragi <[email protected]> 2026-01-27 17:40 ` Jacob Champion <[email protected]> 2026-01-28 16:04 ` Zsolt Parragi <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox