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 1rQpfc-003oAM-Pg for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jan 2024 14:16:33 +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 1rQpfb-009fiQ-Iz for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Jan 2024 14:16:31 +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 1rQpfb-009fiI-4n for pgsql-hackers@lists.postgresql.org; Fri, 19 Jan 2024 14:16:31 +0000 Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1rQpfX-002KN9-Vg for pgsql-hackers@lists.postgresql.org; Fri, 19 Jan 2024 14:16:29 +0000 Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-40e9ffab5f2so4104885e9.1 for ; Fri, 19 Jan 2024 06:16:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1705673786; x=1706278586; 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=3CCRNfZDGmojs7nwlryRTHZxv2aabl0CMVSJE0jO5d4=; b=DE8AbfVzy0JILbZyWsrsf5+nzVnQR6v1LD3jK2ieg3WAAOWt2ZQZGvzcKilruNrtvo WoD8C68Gn08ha/8xQG6G+rr26JWg6I+Li1iWJYRzdHam+lX3Bp42VnF/zMuLKQEOhJ3x 0PDWyyskMWHOdrnKwwRmRadOvFi6+RUuip+Cn32PJXEgPrrC66rz+j0qzu/vecxaJ7F9 hufZFyKEV7GW9y9dHRZSYaQEwRmNQIt8JYDi2M1JkrwpJtvBlU20ejJBMj2zLOLFx8rF Q512xsZ65nMOowlUDdbT9jDrd6Boe1I4kd4kaLjlQYkXpLxWhlnfoAjKPCBZP59PNb0k 0DnA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1705673786; x=1706278586; 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=3CCRNfZDGmojs7nwlryRTHZxv2aabl0CMVSJE0jO5d4=; b=YMInBwqe4gIKhdKkGaR9fM3o5Xg2NIN5jAyKWiI2pdZm3ibTLCZOcbOGYOiPG6c827 iDbE6RCjGGODZor0iFZUcIupQPZdKcwQoljqzHFddne5JqM0qxZqtBzlAYOITplDYD1k ppTD3V6NZkBdiSrpGwowFn9OLdGaIem3bSi4T3q2tBy6sI59d+sfRjrYrJfNliQUYmBy iFiYiAxL+wDUvyOChQVtm/lZfBwjO6Aoo7cHASgU0JJaJRBj/0I9kAcKDxt807+ZdA43 nBDKkMlvDn54BnIpWQ/SWcU65qdV3xRv2653Y7p8RJk1Yet2W3+JSo0tF0O3YxCnWQiG dNFQ== X-Gm-Message-State: AOJu0Yy6WXRDLEX2SPLCI28JsjJ0LAHf1Qar8zKQRIDqaXWPYmwNhr4M d0LA5q5hM5LoNBtg1KPhCh5ujWyOWJsZhFUx8z3f33mk3yYQK77T X-Google-Smtp-Source: AGHT+IGJpMeORZdKzrhdPdwqeZr3BEEBTEhqj4MK0z8IPCh0bLLN9i3rTHZMBncci4gx/7UlT1+mCg== X-Received: by 2002:a05:600c:2288:b0:40e:4618:d9d0 with SMTP id 8-20020a05600c228800b0040e4618d9d0mr1555206wmf.77.1705673786226; Fri, 19 Jan 2024 06:16:26 -0800 (PST) Received: from lightning.caipicrew.dd-dns.de ([2001:a61:10cd:8701:ed11:b7eb:8de7:fbd6]) by smtp.gmail.com with ESMTPSA id v21-20020a05600c445500b0040d62f89381sm29314143wmn.35.2024.01.19.06.16.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 19 Jan 2024 06:16:25 -0800 (PST) Message-ID: <65aa8439.050a0220.31e77.0071@mx.google.com> X-Google-Original-Message-ID: <20240119141624.GD17618@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 1rQpfU-0004Q0-Uf; Fri, 19 Jan 2024 15:16:24 +0100 Date: Fri, 19 Jan 2024 15:16:24 +0100 From: Michael Banck To: Abhijit Menon-Sen Cc: pgsql-hackers@lists.postgresql.org, =?utf-8?B?5oiQ5LmL54SV?= Subject: Re: [PATCH] Exponential backoff for auth_delay References: <658c4eab.050a0220.731ff.011e@mx.google.com> <65965e9d.050a0220.919a6.f9e1@mx.google.com> <65aa8265.170a0220.1ecef.91df@mx.google.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="KFztAG8eRSV9hGtP" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <65aa8265.170a0220.1ecef.91df@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 --KFztAG8eRSV9hGtP Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Fri, Jan 19, 2024 at 03:08:36PM +0100, Michael Banck wrote: > I went through your comments (a lot of which pertained to the original > larger patch I took code from), attached is a reworked version 2. Oops, we are supposed to be at version 3, attached. Michael --KFztAG8eRSV9hGtP Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v3-0001-Add-optional-exponential-backoff-to-auth_delay-co.patch" Content-Transfer-Encoding: 8bit From e60cdca73d384fa467f8e6becdd85d9a0c530b60 Mon Sep 17 00:00:00 2001 From: Michael Banck Date: Wed, 27 Dec 2023 15:55:39 +0100 Subject: [PATCH v3] 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, 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 #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. + + It is optionally possible to let auth_delay wait longer + on each successive authentication failure if the configuration parameter + auth_delay.exponential_backoff is active. If enabled, + auth_delay will double the delay after each consecutive + authentication failure from a particular host, up to the given + auth_delay.max_seconds (default: 10s). If the host + authenticates successfully, the delay is reset. + + + + In this case, it might be desirable to decrease the configuration parameter + auth_delay.milliseconds from the default of 1 second in + order to not delay as long for single accidental authentication failures. + + In order to function, this module must be loaded via in postgresql.conf. @@ -39,6 +55,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 + + + + + The maximum delay, in seconds, when exponential backoff is + enabled. The default is 10 seconds. + + + @@ -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' 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--