Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rLIBj-007BAG-9A for pgsql-hackers@arkaria.postgresql.org; Thu, 04 Jan 2024 07:30:47 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1rLIBg-00B1uw-KT for pgsql-hackers@arkaria.postgresql.org; Thu, 04 Jan 2024 07:30:44 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rLIBg-00B1un-2w for pgsql-hackers@lists.postgresql.org; Thu, 04 Jan 2024 07:30:44 +0000 Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1rLIBc-00Df3B-Ow for pgsql-hackers@lists.postgresql.org; Thu, 04 Jan 2024 07:30:42 +0000 Received: by mail-wm1-x32c.google.com with SMTP id 5b1f17b1804b1-40d5f40ce04so2020135e9.2 for ; Wed, 03 Jan 2024 23:30:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1704353438; x=1704958238; darn=lists.postgresql.org; h=user-agent:in-reply-to:content-transfer-encoding :content-disposition:mime-version:references:subject:cc:to:from:date :sender:message-id:from:to:cc:subject:date:message-id:reply-to; bh=Uo2TqJoPs6vJcd9PUSfwdksNRcCePzMQ0uZe5Eg/lpk=; b=QSwV+M8+nbfZxyPYI1tYXjSNFr8em2tdYUWQAqv6cOTvQTgRjGwkU2HbhZFdr5y4TA XQyx6SStl06fPVHlXuxyd4Pk/DYym3iwXo2A1l/tZHeCJS0Ty+fqC2nb9kJvmcZdlzam Cwtef5s5oxizc734f77K5ulp2LjkYX7viZW8HxQdYsb8EzISoKzHiH73fcsooNuI6mkW ulVDQkcnUf5RQ4LjFoiKkpqNcvc9YU0XEXm5Z58Fb3z6+WRx7Z/ycRu3z1GE9FRFq6Bq iep6nSPuvo6fih3xNOU2/ZLWtm5g+j+PRCnu0MpOJfxFyx/LEnmIxCFC/S4lEfi6am6G EDlA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1704353438; x=1704958238; h=user-agent:in-reply-to:content-transfer-encoding :content-disposition:mime-version:references:subject:cc:to:from:date :sender:message-id:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=Uo2TqJoPs6vJcd9PUSfwdksNRcCePzMQ0uZe5Eg/lpk=; b=mr9Q6v9oT9kkNVxZJzN/pzxOaI97khqz4PHgQpHVHcM4ZBUAYQBazAuQ2VYpB3Z6Ga D+tM81KFVFQj0C6/fxMS6Dg6jTUn0LyT0/4sRD2eDgPLDIT8WaQC4lawDw+/Z1ox23Xj gP0AvDogx7piq8XGXX9YtrzWCxxGC/xXPV2RRmeZfUmqrtAIQ9+HPWAv/GiMsFEC2F5j NAKkKvyVDswMZfREuzFmND5/1XlMqAuIRueKEI25deqGGlGzxd/UMU/v7lWXTlvfBV66 mEFRxnno/RZp3KeZG7xjltIdgWIiWMIG6wC+XHxN9voJmk3gakEHi9Dct8KF9sDPJtn3 739Q== X-Gm-Message-State: AOJu0Yxqw+itDWto2B+esiwE5mbBVm07aJ4Q97IK/b2Hvhj7hHWIIU0N qKBuQoWaQgked8J3X2bW4P/2SNF+9g4= X-Google-Smtp-Source: AGHT+IHsZtQCfTghBS1tai4I9KqJVGCAHtzaNz/xkxTugOq8SEFb3R+ZvZiHnstDrwd92Q4pnfJqzA== X-Received: by 2002:a05:600c:3b14:b0:40d:5b7a:e31f with SMTP id m20-20020a05600c3b1400b0040d5b7ae31fmr98839wms.39.1704353437930; Wed, 03 Jan 2024 23:30:37 -0800 (PST) Received: from lightning.caipicrew.dd-dns.de ([2001:a61:10cd:8701:ed11:b7eb:8de7:fbd6]) by smtp.gmail.com with ESMTPSA id je2-20020a05600c1f8200b0040d5a9d6b68sm4765705wmb.6.2024.01.03.23.30.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 03 Jan 2024 23:30:37 -0800 (PST) Message-ID: <65965e9d.050a0220.919a6.f9e1@mx.google.com> X-Google-Original-Message-ID: <20240104073036.GB3613@caipicrew.dd-dns.de;lightning.caipicrew.dd-dns.de> Sender: Michael Banck Received: from mbanck by lightning.caipicrew.dd-dns.de with local (Exim 4.92) (envelope-from ) id 1rLIBY-0007fi-R8; Thu, 04 Jan 2024 08:30:36 +0100 Date: Thu, 4 Jan 2024 08:30:36 +0100 From: Michael Banck To: pgsql-hackers@lists.postgresql.org Cc: =?utf-8?B?5oiQ5LmL54SV?= Subject: Re: [PATCH] Exponential backoff for auth_delay References: <658c4eab.050a0220.731ff.011e@mx.google.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="uQr8t48UFsdbeI+V" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <658c4eab.050a0220.731ff.011e@mx.google.com> User-Agent: Mutt/1.10.1 (2018-07-13) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --uQr8t48UFsdbeI+V Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, On Wed, Dec 27, 2023 at 05:19:54PM +0100, Michael Banck wrote: > This patch adds exponential backoff so that one can choose a small > initial value which gets doubled for each failed authentication attempt > until a maximum wait time (which is 10s by default, but can be disabled > if so desired). Here is a new version, hopefully fixing warnings in the documentation build, per cfbot. Michael --uQr8t48UFsdbeI+V Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v2-0001-Add-optional-exponential-backoff-to-auth_delay-co.patch" Content-Transfer-Encoding: 8bit From 579f6ce8f968464af06a4695b7a3b66ee94716c8 Mon Sep 17 00:00:00 2001 From: Michael Banck Date: Wed, 27 Dec 2023 15:55:39 +0100 Subject: [PATCH v2] Add optional exponential backoff to auth_delay contrib module. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #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. + + It is optionally possible to let auth_delay wait longer + for each successive authentication failure from a particular remote host, if + the configuration parameter auth_delay.exp_backoff is + active. Once an authentication succeeded from a remote host, the + authentication delay is reset to the value of + auth_delay.milliseconds for this host. The parameter + auth_delay.max_seconds sets an upper bound for the delay + in this case. + + In order to function, this module must be loaded via in postgresql.conf. @@ -39,6 +50,34 @@ + + + auth_delay.exp_backoff (bool) + + auth_delay.exp_backoff configuration parameter + + + + + Whether to use exponential backoff per remote host on authentication + failure. The default is off. + + + + + + auth_delay.max_seconds (integer) + + auth_delay.max_seconds configuration parameter + + + + + How many seconds to wait at most if exponential backoff is active. + Setting this parameter to 0 disables it. The default is 10 seconds. + + + @@ -51,6 +90,8 @@ shared_preload_libraries = 'auth_delay' auth_delay.milliseconds = '500' +auth_delay.exp_backoff = 'on' +auth_delay.max_seconds = '20' 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--