agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v6 4/7] Invalidate parent indexes 7+ messages / 2 participants [nested] [flat]
* [PATCH v6 4/7] Invalidate parent indexes @ 2020-11-06 00:58 Justin Pryzby <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Justin Pryzby @ 2020-11-06 00:58 UTC (permalink / raw) --- src/backend/commands/cluster.c | 21 +++++++++++++++++++++ src/test/regress/expected/cluster.out | 26 ++++++++++++++++++++++++++ src/test/regress/sql/cluster.sql | 8 ++++++++ 3 files changed, 55 insertions(+) diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 078b97fbb9..cdb49985ce 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -594,6 +594,27 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) } list_free(inh); + /* + * Set parent of all indexes as unclustered when a rel is unclustered; and, + * when an index is clustered, set parents of all /other/ indexes as + * unclustered. + */ + indexes = RelationGetIndexList(rel); + foreach (lc, indexes) + { + Oid thisIndexOid = lfirst_oid(lc); + + if (thisIndexOid == indexOid) + continue; + + while (get_rel_relispartition(thisIndexOid)) + { + thisIndexOid = get_partition_parent(thisIndexOid); + set_indisclustered(thisIndexOid, false, pg_index); + } + } + list_free(indexes); + table_close(pg_index, RowExclusiveLock); } diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out index a9fb9f1021..6d88978387 100644 --- a/src/test/regress/expected/cluster.out +++ b/src/test/regress/expected/cluster.out @@ -541,6 +541,32 @@ Indexes: "clstrpart1_idx_2" btree (a) CLUSTER Number of partitions: 2 (Use \d+ to list them.) +-- Check that the parent index is marked not clustered after clustering a partition on a different index: +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +CLUSTER clstrpart1 USING clstrpart1_idx_2; +\d clstrpart + Partitioned table "public.clstrpart" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | +Partition key: RANGE (a) +Indexes: + "clstrpart_idx" btree (a) +Number of partitions: 3 (Use \d+ to list them.) + +-- Check that the parent index is marked not clustered after setting a partition not clustered: +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +ALTER TABLE clstrpart1 SET WITHOUT CLUSTER; +\d clstrpart + Partitioned table "public.clstrpart" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | +Partition key: RANGE (a) +Indexes: + "clstrpart_idx" btree (a) +Number of partitions: 3 (Use \d+ to list them.) + -- Test CLUSTER with external tuplesorting create table clstr_4 as select * from tenk1; create index cluster_sort on clstr_4 (hundred, thousand, tenthous); diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql index d15bd51496..c9bb204a93 100644 --- a/src/test/regress/sql/cluster.sql +++ b/src/test/regress/sql/cluster.sql @@ -231,6 +231,14 @@ CREATE INDEX clstrpart1_idx_2 ON clstrpart1(a); ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; ALTER TABLE clstrpart1 CLUSTER ON clstrpart1_idx_2; \d clstrpart1 +-- Check that the parent index is marked not clustered after clustering a partition on a different index: +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +CLUSTER clstrpart1 USING clstrpart1_idx_2; +\d clstrpart +-- Check that the parent index is marked not clustered after setting a partition not clustered: +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +ALTER TABLE clstrpart1 SET WITHOUT CLUSTER; +\d clstrpart -- Test CLUSTER with external tuplesorting -- 2.17.0 --FsscpQKzF/jJk6ya Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0005-Invalidate-parent-index-cluster-on-attach.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v5] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds the new auth_delay.max_milliseconds GUC. If set (its default is 0), auth_delay adds exponential backoff with this GUC's value as maximum delay. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication or when no authentication attempts have been made for 5*max_milliseconds from that host. Authors: Michael Banck, based on an earlier patch by 成之焕 Reviewed-by: Abhijit Menon-Sen, Tomas Vondra Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 216 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 31 ++++- src/tools/pgindent/typedefs.list | 1 + 3 files changed, 244 insertions(+), 4 deletions(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index ff0e1fd461..5fb123d133 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,50 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/dsm_registry.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 100 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static int auth_delay_max_milliseconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + double sleep_time; /* in milliseconds */ + TimestampTz last_failed_auth; +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *auth_delay_find_acr_for_host(char *remote_host); +static AuthConnRecord *auth_delay_find_free_acr(void); +static double auth_delay_increase_delay_after_failed_conn_auth(Port *port); +static void auth_delay_cleanup_conn_record(Port *port); +static void auth_delay_expire_conn_records(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay = auth_delay_milliseconds; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -39,20 +65,190 @@ auth_delay_checks(Port *port, int status) original_client_auth_hook(port, status); /* - * Inject a short delay if authentication failed. + * We handle both STATUS_ERROR and STATUS_OK - the third option + * (STATUS_EOF) is disregarded. + * + * In case of STATUS_ERROR we inject a short delay, optionally with + * exponential backoff. + */ + if (status == STATUS_ERROR) + { + if (auth_delay_max_milliseconds > 0) + { + /* + * Delay by 2^n seconds after each authentication failure from a + * particular host, where n is the number of consecutive + * authentication failures. + */ + delay = auth_delay_increase_delay_after_failed_conn_auth(port); + + /* + * Clamp delay to a maximum of auth_delay_max_milliseconds. + */ + delay = Min(delay, auth_delay_max_milliseconds); + } + + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds due to auth_delay", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } + + /* + * Expire delays from other hosts after auth_delay_max_milliseconds * + * 5. + */ + auth_delay_expire_conn_records(port); + } + + /* + * Remove host-specific delay if authentication succeeded. + */ + if (status == STATUS_OK) + auth_delay_cleanup_conn_record(port); +} + +static double +auth_delay_increase_delay_after_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = auth_delay_find_acr_for_host(port->remote_host); + + if (!acr) + { + acr = auth_delay_find_free_acr(); + + if (!acr) + { + /* + * No free space, MAX_CONN_RECORDS reached. Wait for the + * configured maximum amount. + */ + elog(LOG, "auth_delay: host connection list full, waiting maximum amount"); + return auth_delay_max_milliseconds; + } + strcpy(acr->remote_host, port->remote_host); + } + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; + + /* + * Set current timestamp for later expiry. */ - if (status != STATUS_OK) + acr->last_failed_auth = GetCurrentTimestamp(); + + return acr->sleep_time; +} + +static AuthConnRecord * +auth_delay_find_acr_for_host(char *remote_host) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static AuthConnRecord * +auth_delay_find_free_acr(void) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].remote_host[0]) + return &acr_array[i]; + } + + return 0; +} + +static void +auth_delay_cleanup_conn_record(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = auth_delay_find_acr_for_host(port->remote_host); + if (acr == NULL) + return; + + port->remote_host[0] = '\0'; + + acr->sleep_time = 0.0; + acr->last_failed_auth = 0.0; +} + +static void +auth_delay_expire_conn_records(Port *port) +{ + int i; + TimestampTz now = GetCurrentTimestamp(); + + for (i = 0; i < MAX_CONN_RECORDS; i++) { - pg_usleep(1000L * auth_delay_milliseconds); + /* + * Do not expire the host from which the current authentication + * failure originated. + */ + if (strcmp(acr_array[i].remote_host, port->remote_host) == 0) + continue; + + if (acr_array[i].last_failed_auth > 0 && (long) ((now - acr_array[i].last_failed_auth) / 1000) > 5 * auth_delay_max_milliseconds) + { + acr_array[i].remote_host[0] = '\0'; + acr_array[i].sleep_time = 0.0; + acr_array[i].last_failed_auth = 0.0; + } } } +/* + * Set up shared memory + */ + +static void +auth_delay_init_state(void *ptr) +{ + Size shm_size; + AuthConnRecord *array = (AuthConnRecord *) ptr; + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + + memset(array, 0, shm_size); +} + +static void +auth_delay_shmem_startup(void) +{ + bool found; + Size shm_size; + + if (shmem_startup_next) + shmem_startup_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + acr_array = GetNamedDSMSegment("auth_delay", shm_size, auth_delay_init_state, &found); +} + /* * Module Load Callback */ void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +262,23 @@ _PG_init(void) NULL, NULL); + DefineCustomIntVariable("auth_delay.max_milliseconds", + "Maximum delay for exponential backoff", + NULL, + &auth_delay_max_milliseconds, + 0, + 0, INT_MAX / 1000, + PGC_SIGHUP, + GUC_UNIT_MS, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..e3c182cd45 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,19 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + on each successive authentication failure if the configuration parameter + <varname>auth_delay.max_milliseconds</varname> is set. In this case, + <filename>auth_delay</filename> will start with a delay of + <varname>auth_delay.milliseconds</varname> and double the delay after each + consecutive authentication failure from a particular host, up to the given + <varname>auth_delay.max_milliseconds</varname>. If the host authenticates + successfully or after a timeout of five times + <varname>auth_delay.max_milliseconds</varname>, the delay is reset to + <varname>auth_delay.milliseconds</varname>. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +52,21 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_milliseconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_milliseconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + The maximum delay in milliseconds, implying exponential backoff between + each successive failed attempt. The default is 0, meaning exponential + backoff is not active. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -50,7 +78,8 @@ # postgresql.conf shared_preload_libraries = 'auth_delay' -auth_delay.milliseconds = '500' +auth_delay.milliseconds = '125' +auth_delay.max_milliseconds = '20000' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 95ae7845d8..dd9fb9e530 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -166,6 +166,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --hoZxPH4CaxYzWscb-- ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v3] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds two new GUCs for auth_delay, exponential_backoff and max_seconds. The former controls whether exponential backoff should be used or not, the latter sets an maximum delay (default is 10s) in case exponential backoff is active. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication from that host. This patch is partly based on a larger (but ultimately rejected) patch by 成之焕. Authors: Michael Banck, 成之焕 Reviewed-by: Abhijit Menon-Sen Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 197 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 48 +++++++- src/tools/pgindent/typedefs.list | 1 + 3 files changed, 243 insertions(+), 3 deletions(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index ff0e1fd461..06d2f5f280 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,49 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 50 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static bool auth_delay_exp_backoff = false; +static int auth_delay_max_seconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + double sleep_time; /* in milliseconds */ +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static shmem_request_hook_type shmem_request_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *find_acr_for_host(char *remote_host); +static AuthConnRecord *find_free_acr(void); +static double increase_delay_after_failed_conn_auth(Port *port); +static void cleanup_conn_record(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay = auth_delay_milliseconds; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -41,10 +66,146 @@ auth_delay_checks(Port *port, int status) /* * Inject a short delay if authentication failed. */ - if (status != STATUS_OK) + if (status == STATUS_ERROR) { - pg_usleep(1000L * auth_delay_milliseconds); + if (auth_delay_exp_backoff) + { + /* + * Delay by 2^n seconds after each authentication failure from a + * particular host, where n is the number of consecutive + * authentication failures. + */ + delay = increase_delay_after_failed_conn_auth(port); + + /* + * Clamp delay to a maximum of auth_delay_max_seconds. + */ + if (auth_delay_max_seconds > 0) { + delay = Min(delay, 1000L * auth_delay_max_seconds); + } + } + + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds due to auth_delay", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } } + + /* + * Remove host-specific delay if authentication succeeded. + */ + if (status == STATUS_OK) + cleanup_conn_record(port); +} + +static double +increase_delay_after_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = find_acr_for_host(port->remote_host); + + if (!acr) + { + acr = find_free_acr(); + + if (!acr) + { + /* + * No free space, MAX_CONN_RECORDS reached. Wait for the + * configured maximum amount. + */ + return 1000L * auth_delay_max_seconds; + } + strcpy(acr->remote_host, port->remote_host); + } + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; + + return acr->sleep_time; +} + +static AuthConnRecord * +find_acr_for_host(char *remote_host) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static AuthConnRecord * +find_free_acr(void) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].remote_host[0]) + return &acr_array[i]; + } + + return 0; +} + +static void +cleanup_conn_record(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = find_acr_for_host(port->remote_host); + if (acr == NULL) + return; + + port->remote_host[0] = '\0'; + + acr->sleep_time = 0.0; +} + +/* + * Set up shared memory + */ + +static void +auth_delay_shmem_request(void) +{ + Size shm_size; + + if (shmem_request_next) + shmem_request_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + shm_size += sizeof(int); + RequestAddinShmemSpace(shm_size); +} + +static void +auth_delay_shmem_startup(void) +{ + bool found; + Size shm_size; + + if (shmem_startup_next) + shmem_startup_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + acr_array = ShmemInitStruct("Array of AuthConnRecord", shm_size, &found); + if (!found) + { + /* First time through ... */ + memset(acr_array, 0, shm_size); + } + LWLockRelease(AddinShmemInitLock); } /* @@ -53,6 +214,11 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +232,36 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auth_delay.exponential_backoff", + "Double the delay after each authentication failure from a particular host", + NULL, + &auth_delay_exp_backoff, + false, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auth_delay.max_seconds", + "Maximum delay when exponential backoff is enabled", + NULL, + &auth_delay_max_seconds, + 10, + 0, INT_MAX, + PGC_SIGHUP, + GUC_UNIT_S, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_request_next = shmem_request_hook; + shmem_request_hook = auth_delay_shmem_request; + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..89585ea3c8 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,22 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + on each successive authentication failure if the configuration parameter + <varname>auth_delay.exponential_backoff</varname> is active. If enabled, + <filename>auth_delay</filename> will double the delay after each consecutive + authentication failure from a particular host, up to the given + <varname>auth_delay.max_seconds</varname> (default: 10s). If the host + authenticates successfully, the delay is reset. + </para> + + <para> + In this case, it might be desirable to decrease the configuration parameter + <varname>auth_delay.milliseconds</varname> from the default of 1 second in + order to not delay as long for single accidental authentication failures. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +55,34 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.exp_backoff</varname> (<type>bool</type>) + <indexterm> + <primary><varname>auth_delay.exp_backoff</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_seconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_seconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + The maximum delay, in seconds, when exponential backoff is + enabled. The default is 10 seconds. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -50,7 +94,9 @@ # postgresql.conf shared_preload_libraries = 'auth_delay' -auth_delay.milliseconds = '500' +auth_delay.milliseconds = '125' +auth_delay.exp_backoff = 'on' +auth_delay.max_seconds = '20' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 29fd1cae64..ee30969f0c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -165,6 +165,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --KFztAG8eRSV9hGtP-- ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v2] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds two new GUCs for auth_delay, exponential_backoff and max_seconds. The former controls whether exponential backoff should be used or not, the latter sets an maximum delay (default is 10s) in case exponential backoff is active. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication from that host. This patch is partly based on a larger (but ultimately rejected) patch by 成之焕. Authors: Michael Banck, 成之焕 Reviewed-by: Abhijit Menon-Sen Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 197 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 48 +++++++- src/tools/pgindent/typedefs.list | 1 + 3 files changed, 243 insertions(+), 3 deletions(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index ff0e1fd461..06d2f5f280 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,49 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 50 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static bool auth_delay_exp_backoff = false; +static int auth_delay_max_seconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + double sleep_time; /* in milliseconds */ +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static shmem_request_hook_type shmem_request_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *find_acr_for_host(char *remote_host); +static AuthConnRecord *find_free_acr(void); +static double increase_delay_after_failed_conn_auth(Port *port); +static void cleanup_conn_record(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay = auth_delay_milliseconds; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -41,10 +66,146 @@ auth_delay_checks(Port *port, int status) /* * Inject a short delay if authentication failed. */ - if (status != STATUS_OK) + if (status == STATUS_ERROR) { - pg_usleep(1000L * auth_delay_milliseconds); + if (auth_delay_exp_backoff) + { + /* + * Delay by 2^n seconds after each authentication failure from a + * particular host, where n is the number of consecutive + * authentication failures. + */ + delay = increase_delay_after_failed_conn_auth(port); + + /* + * Clamp delay to a maximum of auth_delay_max_seconds. + */ + if (auth_delay_max_seconds > 0) { + delay = Min(delay, 1000L * auth_delay_max_seconds); + } + } + + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds due to auth_delay", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } } + + /* + * Remove host-specific delay if authentication succeeded. + */ + if (status == STATUS_OK) + cleanup_conn_record(port); +} + +static double +increase_delay_after_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = find_acr_for_host(port->remote_host); + + if (!acr) + { + acr = find_free_acr(); + + if (!acr) + { + /* + * No free space, MAX_CONN_RECORDS reached. Wait for the + * configured maximum amount. + */ + return 1000L * auth_delay_max_seconds; + } + strcpy(acr->remote_host, port->remote_host); + } + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; + + return acr->sleep_time; +} + +static AuthConnRecord * +find_acr_for_host(char *remote_host) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static AuthConnRecord * +find_free_acr(void) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].remote_host[0]) + return &acr_array[i]; + } + + return 0; +} + +static void +cleanup_conn_record(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = find_acr_for_host(port->remote_host); + if (acr == NULL) + return; + + port->remote_host[0] = '\0'; + + acr->sleep_time = 0.0; +} + +/* + * Set up shared memory + */ + +static void +auth_delay_shmem_request(void) +{ + Size shm_size; + + if (shmem_request_next) + shmem_request_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + shm_size += sizeof(int); + RequestAddinShmemSpace(shm_size); +} + +static void +auth_delay_shmem_startup(void) +{ + bool found; + Size shm_size; + + if (shmem_startup_next) + shmem_startup_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + acr_array = ShmemInitStruct("Array of AuthConnRecord", shm_size, &found); + if (!found) + { + /* First time through ... */ + memset(acr_array, 0, shm_size); + } + LWLockRelease(AddinShmemInitLock); } /* @@ -53,6 +214,11 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +232,36 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auth_delay.exponential_backoff", + "Double the delay after each authentication failure from a particular host", + NULL, + &auth_delay_exp_backoff, + false, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auth_delay.max_seconds", + "Maximum delay when exponential backoff is enabled", + NULL, + &auth_delay_max_seconds, + 10, + 0, INT_MAX, + PGC_SIGHUP, + GUC_UNIT_S, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_request_next = shmem_request_hook; + shmem_request_hook = auth_delay_shmem_request; + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..89585ea3c8 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,22 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + on each successive authentication failure if the configuration parameter + <varname>auth_delay.exponential_backoff</varname> is active. If enabled, + <filename>auth_delay</filename> will double the delay after each consecutive + authentication failure from a particular host, up to the given + <varname>auth_delay.max_seconds</varname> (default: 10s). If the host + authenticates successfully, the delay is reset. + </para> + + <para> + In this case, it might be desirable to decrease the configuration parameter + <varname>auth_delay.milliseconds</varname> from the default of 1 second in + order to not delay as long for single accidental authentication failures. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +55,34 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.exp_backoff</varname> (<type>bool</type>) + <indexterm> + <primary><varname>auth_delay.exp_backoff</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_seconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_seconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + The maximum delay, in seconds, when exponential backoff is + enabled. The default is 10 seconds. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -50,7 +94,9 @@ # postgresql.conf shared_preload_libraries = 'auth_delay' -auth_delay.milliseconds = '500' +auth_delay.milliseconds = '125' +auth_delay.exp_backoff = 'on' +auth_delay.max_seconds = '20' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 29fd1cae64..ee30969f0c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -165,6 +165,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v4] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds two new GUCs for auth_delay, exponential_backoff and max_milliseconds. The former controls whether exponential backoff should be used or not, the latter sets an maximum delay (default is 10s) in case exponential backoff is active. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication or when no authentication attempts have been made for 5*max_milliseconds from that host. Authors: Michael Banck, based on an earlier patch by 成之焕 Reviewed-by: Abhijit Menon-Sen, Tomas Vondra Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 231 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 45 +++++- src/tools/pgindent/typedefs.list | 1 + 3 files changed, 273 insertions(+), 4 deletions(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index ff0e1fd461..647aa4e1cd 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,51 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/dsm_registry.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 100 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static bool auth_delay_exp_backoff = false; +static int auth_delay_max_milliseconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + double sleep_time; /* in milliseconds */ + TimestampTz last_failed_auth; +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *auth_delay_find_acr_for_host(char *remote_host); +static AuthConnRecord *auth_delay_find_free_acr(void); +static double auth_delay_increase_delay_after_failed_conn_auth(Port *port); +static void auth_delay_cleanup_conn_record(Port *port); +static void auth_delay_expire_conn_records(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay = auth_delay_milliseconds; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -39,20 +66,193 @@ auth_delay_checks(Port *port, int status) original_client_auth_hook(port, status); /* - * Inject a short delay if authentication failed. + * We handle both STATUS_ERROR and STATUS_OK - the third option + * (STATUS_EOF) is disregarded. + * + * In case of STATUS_ERROR we inject a short delay, optionally with + * exponential backoff. + */ + if (status == STATUS_ERROR) + { + if (auth_delay_exp_backoff) + { + /* + * Delay by 2^n seconds after each authentication failure from a + * particular host, where n is the number of consecutive + * authentication failures. + */ + delay = auth_delay_increase_delay_after_failed_conn_auth(port); + + /* + * Clamp delay to a maximum of auth_delay_max_milliseconds. + */ + if (auth_delay_max_milliseconds > 0) + { + delay = Min(delay, auth_delay_max_milliseconds); + } + } + + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds due to auth_delay", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } + + /* + * Expire delays from other hosts after auth_delay_max_milliseconds * + * 5. + */ + auth_delay_expire_conn_records(port); + } + + /* + * Remove host-specific delay if authentication succeeded. + */ + if (status == STATUS_OK) + auth_delay_cleanup_conn_record(port); +} + +static double +auth_delay_increase_delay_after_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = auth_delay_find_acr_for_host(port->remote_host); + + if (!acr) + { + acr = auth_delay_find_free_acr(); + + if (!acr) + { + /* + * No free space, MAX_CONN_RECORDS reached. Wait for the + * configured maximum amount. + */ + elog(LOG, "auth_delay: host connection list full, waiting maximum amount"); + return auth_delay_max_milliseconds; + } + strcpy(acr->remote_host, port->remote_host); + } + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; + + /* + * Set current timestamp for later expiry. */ - if (status != STATUS_OK) + acr->last_failed_auth = GetCurrentTimestamp(); + + return acr->sleep_time; +} + +static AuthConnRecord * +auth_delay_find_acr_for_host(char *remote_host) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static AuthConnRecord * +auth_delay_find_free_acr(void) +{ + int i; + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].remote_host[0]) + return &acr_array[i]; + } + + return 0; +} + +static void +auth_delay_cleanup_conn_record(Port *port) +{ + AuthConnRecord *acr = NULL; + + acr = auth_delay_find_acr_for_host(port->remote_host); + if (acr == NULL) + return; + + port->remote_host[0] = '\0'; + + acr->sleep_time = 0.0; + acr->last_failed_auth = 0.0; +} + +static void +auth_delay_expire_conn_records(Port *port) +{ + int i; + TimestampTz now = GetCurrentTimestamp(); + + for (i = 0; i < MAX_CONN_RECORDS; i++) { - pg_usleep(1000L * auth_delay_milliseconds); + /* + * Do not expire the host from which the current authentication + * failure originated. + */ + if (strcmp(acr_array[i].remote_host, port->remote_host) == 0) + continue; + + if (acr_array[i].last_failed_auth > 0 && (long) ((now - acr_array[i].last_failed_auth) / 1000) > 5 * auth_delay_max_milliseconds) + { + acr_array[i].remote_host[0] = '\0'; + acr_array[i].sleep_time = 0.0; + acr_array[i].last_failed_auth = 0.0; + } } } +/* + * Set up shared memory + */ + +static void +auth_delay_init_state(void *ptr) +{ + Size shm_size; + AuthConnRecord *array = (AuthConnRecord *) ptr; + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + + memset(array, 0, shm_size); +} + +static void +auth_delay_shmem_startup(void) +{ + bool found; + Size shm_size; + + if (shmem_startup_next) + shmem_startup_next(); + + shm_size = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + acr_array = GetNamedDSMSegment("auth_delay", shm_size, auth_delay_init_state, &found); +} + /* * Module Load Callback */ void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +266,34 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auth_delay.exponential_backoff", + "Double the delay after each authentication failure from a particular host", + NULL, + &auth_delay_exp_backoff, + false, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auth_delay.max_milliseconds", + "Maximum delay when exponential backoff is enabled", + NULL, + &auth_delay_max_milliseconds, + 10000, + 0, INT_MAX / 1000, + PGC_SIGHUP, + GUC_UNIT_MS, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..b02b6b0b30 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,18 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + on each successive authentication failure if the configuration parameter + <varname>auth_delay.exponential_backoff</varname> is active. If enabled, + <filename>auth_delay</filename> will start with a delay of + <varname>auth_delay.milliseconds</varname> and double the delay after each + consecutive authentication failure from a particular host, up to the given + <varname>auth_delay.max_milliseconds</varname> (default: 10s). If the host + authenticates successfully or after a timeout of five times + <varname>auth_delay.max_milliseconds</varname>, the delay is reset. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +51,35 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.exponential_backoff</varname> (<type>bool</type>) + <indexterm> + <primary><varname>auth_delay.exponential_backoff</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_milliseconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_milliseconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + The maximum delay, in milliseconds, when exponential backoff is + enabled. The default is 10 seconds. If set to 0, authentication delays + will increase without limit. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -50,7 +91,9 @@ # postgresql.conf shared_preload_libraries = 'auth_delay' -auth_delay.milliseconds = '500' +auth_delay.milliseconds = '125' +auth_delay.exponential_backoff = 'on' +auth_delay.max_milliseconds = '20000' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 95ae7845d8..dd9fb9e530 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -166,6 +166,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --iFRdW5/EC4oqxDHL-- ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v1] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds two new GUCs for auth_delay, exp_backoff and max_seconds. The former controls whether exponential backoff should be used or not, the latter sets an maximum delay (default is 10s) in case exponential backoff is active. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication from that host. This patch is partly based on a larger (but ultimately rejected) patch by 成之焕. Authors: Michael Banck, 成之焕 Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 202 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 41 +++++++ src/tools/pgindent/typedefs.list | 1 + 3 files changed, 243 insertions(+), 1 deletion(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index 8d6e4d2778..95e56db6ec 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,50 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/ipc.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 50 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static bool auth_delay_exp_backoff = false; +static int auth_delay_max_seconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + bool used; + double sleep_time; /* in milliseconds */ +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static shmem_request_hook_type shmem_request_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *find_conn_record(char *remote_host, int *free_index); +static double record_failed_conn_auth(Port *port); +static double find_conn_max_delay(void); +static void record_conn_failure(AuthConnRecord *acr); +static void cleanup_conn_record(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -43,8 +69,150 @@ auth_delay_checks(Port *port, int status) */ if (status != STATUS_OK) { - pg_usleep(1000L * auth_delay_milliseconds); + if (auth_delay_exp_backoff) + { + /* + * Exponential backoff per remote host. + */ + delay = record_failed_conn_auth(port); + if (auth_delay_max_seconds > 0) + delay = Min(delay, 1000L * auth_delay_max_seconds); + } + else + delay = auth_delay_milliseconds; + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } + } + else + { + cleanup_conn_record(port); + } +} + +static double +record_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + int j = -1; + + acr = find_conn_record(port->remote_host, &j); + + if (!acr) + { + if (j == -1) + + /* + * No free space, MAX_CONN_RECORDS reached. Wait as long as the + * largest delay for any remote host. + */ + return find_conn_max_delay(); + acr = &acr_array[j]; + strcpy(acr->remote_host, port->remote_host); + acr->used = true; + elog(DEBUG1, "new connection: %s, index: %d", acr->remote_host, j); + } + + record_conn_failure(acr); + return acr->sleep_time; +} + +static AuthConnRecord * +find_conn_record(char *remote_host, int *free_index) +{ + int i; + + *free_index = -1; + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].used) + { + if (*free_index == -1) + /* record unused element */ + *free_index = i; + continue; + } + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static double +find_conn_max_delay(void) +{ + int i; + double max_delay = 0.0; + + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (acr_array[i].used && acr_array[i].sleep_time > max_delay) + max_delay = acr_array[i].sleep_time; } + + return max_delay; +} + +static void +record_conn_failure(AuthConnRecord *acr) +{ + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; +} + +static void +cleanup_conn_record(Port *port) +{ + int free_index; + AuthConnRecord *acr = NULL; + + acr = find_conn_record(port->remote_host, &free_index); + if (acr == NULL) + return; + + acr->used = false; + acr->sleep_time = 0.0; +} + +/* + * Set up shared memory + */ + +static void +auth_delay_shmem_request(void) +{ + Size required; + + if (shmem_request_next) + shmem_request_next(); + + required = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + required += sizeof(int); + RequestAddinShmemSpace(required); +} + +static void +auth_delay_shmem_startup(void) +{ + Size required; + bool found; + + if (shmem_startup_next) + shmem_startup_next(); + + required = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + acr_array = ShmemInitStruct("Array of AuthConnRecord", required, &found); + if (found) + /* this should not happen ? */ + elog(DEBUG1, "variable acr_array already exists"); + /* all fileds are set to 0 */ + memset(acr_array, 0, required); } /* @@ -53,6 +221,11 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +239,36 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auth_delay.exp_backoff", + "Exponential backoff for failed connections, per remote host", + NULL, + &auth_delay_exp_backoff, + false, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auth_delay.max_seconds", + "Maximum seconds to wait when login fails during exponential backoff", + NULL, + &auth_delay_max_seconds, + 10, + 0, INT_MAX, + PGC_SIGHUP, + GUC_UNIT_S, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_request_next = shmem_request_hook; + shmem_request_hook = auth_delay_shmem_request; + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..2a6efad851 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,17 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + for each successive authentication failure from a particular remote host, if + the configuration parameter <varname>auth_delay.exp_backoff</varname> is + active. Once an authentication succeeded from a remote host, the + authentication delay is reset to the value of + <varname>auth_delay.milliseconds</varname> for this host. The parmaeter + <primary><varname>auth_delay.max_seconds</varname> sets an upper bound for + the delay in this case. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +50,34 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.exp_backoff</varname> (<type>bool</type>) + <indexterm> + <primary><varname>auth_delay.exp_backoff</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_seconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_seconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + How many seconds to wait at most if exponential backoff is active. + Setting this parameter to 0 disables it. The default is 10 seconds. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -51,6 +90,8 @@ shared_preload_libraries = 'auth_delay' auth_delay.milliseconds = '500' +auth_delay.exp_backoff = 'on' +auth_delay.max_seconds = '20' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e37ef9aa76..9b62945f28 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -164,6 +164,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --ZPt4rx8FFjLCG7dd-- ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v2] Add optional exponential backoff to auth_delay contrib module. @ 2023-12-27 14:55 Michael Banck <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Michael Banck @ 2023-12-27 14:55 UTC (permalink / raw) This adds two new GUCs for auth_delay, exp_backoff and max_seconds. The former controls whether exponential backoff should be used or not, the latter sets an maximum delay (default is 10s) in case exponential backoff is active. The exponential backoff is tracked per remote host and doubled for every failed login attempt (i.e., wrong password, not just missing pg_hba line or database) and reset to auth_delay.milliseconds after a successful authentication from that host. This patch is partly based on a larger (but ultimately rejected) patch by 成之焕. Authors: Michael Banck, 成之焕 Discussion: https://postgr.es/m/AHwAxACqIwIVOEhs5YejpqoG.1.1668569845751.Hmail.zhcheng@ceresdata.com --- contrib/auth_delay/auth_delay.c | 202 ++++++++++++++++++++++++++++++- doc/src/sgml/auth-delay.sgml | 41 +++++++ src/tools/pgindent/typedefs.list | 1 + 3 files changed, 243 insertions(+), 1 deletion(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index 8d6e4d2778..95e56db6ec 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -14,24 +14,50 @@ #include <limits.h> #include "libpq/auth.h" +#include "miscadmin.h" #include "port.h" +#include "storage/ipc.h" +#include "storage/shmem.h" #include "utils/guc.h" #include "utils/timestamp.h" PG_MODULE_MAGIC; +#define MAX_CONN_RECORDS 50 + /* GUC Variables */ static int auth_delay_milliseconds = 0; +static bool auth_delay_exp_backoff = false; +static int auth_delay_max_seconds = 0; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NI_MAXHOST]; + bool used; + double sleep_time; /* in milliseconds */ +} AuthConnRecord; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static shmem_request_hook_type shmem_request_next = NULL; +static AuthConnRecord *acr_array = NULL; + +static AuthConnRecord *find_conn_record(char *remote_host, int *free_index); +static double record_failed_conn_auth(Port *port); +static double find_conn_max_delay(void); +static void record_conn_failure(AuthConnRecord *acr); +static void cleanup_conn_record(Port *port); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { + double delay; + /* * Any other plugins which use ClientAuthentication_hook. */ @@ -43,8 +69,150 @@ auth_delay_checks(Port *port, int status) */ if (status != STATUS_OK) { - pg_usleep(1000L * auth_delay_milliseconds); + if (auth_delay_exp_backoff) + { + /* + * Exponential backoff per remote host. + */ + delay = record_failed_conn_auth(port); + if (auth_delay_max_seconds > 0) + delay = Min(delay, 1000L * auth_delay_max_seconds); + } + else + delay = auth_delay_milliseconds; + if (delay > 0) + { + elog(DEBUG1, "Authentication delayed for %g seconds", delay / 1000.0); + pg_usleep(1000L * (long) delay); + } + } + else + { + cleanup_conn_record(port); + } +} + +static double +record_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + int j = -1; + + acr = find_conn_record(port->remote_host, &j); + + if (!acr) + { + if (j == -1) + + /* + * No free space, MAX_CONN_RECORDS reached. Wait as long as the + * largest delay for any remote host. + */ + return find_conn_max_delay(); + acr = &acr_array[j]; + strcpy(acr->remote_host, port->remote_host); + acr->used = true; + elog(DEBUG1, "new connection: %s, index: %d", acr->remote_host, j); + } + + record_conn_failure(acr); + return acr->sleep_time; +} + +static AuthConnRecord * +find_conn_record(char *remote_host, int *free_index) +{ + int i; + + *free_index = -1; + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].used) + { + if (*free_index == -1) + /* record unused element */ + *free_index = i; + continue; + } + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static double +find_conn_max_delay(void) +{ + int i; + double max_delay = 0.0; + + + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (acr_array[i].used && acr_array[i].sleep_time > max_delay) + max_delay = acr_array[i].sleep_time; } + + return max_delay; +} + +static void +record_conn_failure(AuthConnRecord *acr) +{ + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= 2; +} + +static void +cleanup_conn_record(Port *port) +{ + int free_index; + AuthConnRecord *acr = NULL; + + acr = find_conn_record(port->remote_host, &free_index); + if (acr == NULL) + return; + + acr->used = false; + acr->sleep_time = 0.0; +} + +/* + * Set up shared memory + */ + +static void +auth_delay_shmem_request(void) +{ + Size required; + + if (shmem_request_next) + shmem_request_next(); + + required = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + required += sizeof(int); + RequestAddinShmemSpace(required); +} + +static void +auth_delay_shmem_startup(void) +{ + Size required; + bool found; + + if (shmem_startup_next) + shmem_startup_next(); + + required = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + acr_array = ShmemInitStruct("Array of AuthConnRecord", required, &found); + if (found) + /* this should not happen ? */ + elog(DEBUG1, "variable acr_array already exists"); + /* all fileds are set to 0 */ + memset(acr_array, 0, required); } /* @@ -53,6 +221,11 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -66,9 +239,36 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auth_delay.exp_backoff", + "Exponential backoff for failed connections, per remote host", + NULL, + &auth_delay_exp_backoff, + false, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auth_delay.max_seconds", + "Maximum seconds to wait when login fails during exponential backoff", + NULL, + &auth_delay_max_seconds, + 10, + 0, INT_MAX, + PGC_SIGHUP, + GUC_UNIT_S, + NULL, NULL, NULL); + MarkGUCPrefixReserved("auth_delay"); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_request_next = shmem_request_hook; + shmem_request_hook = auth_delay_shmem_request; + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; } diff --git a/doc/src/sgml/auth-delay.sgml b/doc/src/sgml/auth-delay.sgml index 0571f2a99d..2ca9528011 100644 --- a/doc/src/sgml/auth-delay.sgml +++ b/doc/src/sgml/auth-delay.sgml @@ -16,6 +16,17 @@ connection slots. </para> + <para> + It is optionally possible to let <filename>auth_delay</filename> wait longer + for each successive authentication failure from a particular remote host, if + the configuration parameter <varname>auth_delay.exp_backoff</varname> is + active. Once an authentication succeeded from a remote host, the + authentication delay is reset to the value of + <varname>auth_delay.milliseconds</varname> for this host. The parameter + <varname>auth_delay.max_seconds</varname> sets an upper bound for the delay + in this case. + </para> + <para> In order to function, this module must be loaded via <xref linkend="guc-shared-preload-libraries"/> in <filename>postgresql.conf</filename>. @@ -39,6 +50,34 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.exp_backoff</varname> (<type>bool</type>) + <indexterm> + <primary><varname>auth_delay.exp_backoff</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>auth_delay.max_seconds</varname> (<type>integer</type>) + <indexterm> + <primary><varname>auth_delay.max_seconds</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + How many seconds to wait at most if exponential backoff is active. + Setting this parameter to 0 disables it. The default is 10 seconds. + </para> + </listitem> + </varlistentry> </variablelist> <para> @@ -51,6 +90,8 @@ shared_preload_libraries = 'auth_delay' auth_delay.milliseconds = '500' +auth_delay.exp_backoff = 'on' +auth_delay.max_seconds = '20' </programlisting> </sect2> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e37ef9aa76..9b62945f28 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -164,6 +164,7 @@ AttrMap AttrMissing AttrNumber AttributeOpts +AuthConnRecord AuthRequest AuthToken AutoPrewarmSharedState -- 2.39.2 --uQr8t48UFsdbeI+V-- ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2023-12-27 14:55 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-11-06 00:58 [PATCH v6 4/7] Invalidate parent indexes Justin Pryzby <[email protected]> 2023-12-27 14:55 [PATCH v5] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[email protected]> 2023-12-27 14:55 [PATCH v3] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[email protected]> 2023-12-27 14:55 [PATCH v2] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[email protected]> 2023-12-27 14:55 [PATCH v4] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[email protected]> 2023-12-27 14:55 [PATCH v1] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[email protected]> 2023-12-27 14:55 [PATCH v2] Add optional exponential backoff to auth_delay contrib module. Michael Banck <[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