From 2f869b24c79c3d4b542966a1c5a10b92a71b1bee Mon Sep 17 00:00:00 2001 From: Greg Sabino Mullane Date: Sat, 12 Jul 2025 11:25:58 -0400 Subject: [PATCH] Add new server config cleartext_passwords_action This controls what happens when someone sends a clear text password to the server via CREATE USER or ALTER USER. Three states are allowed: 1. "warn" The current default, this issues a warning if a clear text password is used, but allows the change to proceed. The hint changes to recommend \password if the current application_name is 'psql' 2. "allow" This does nothing, and thus emulates the historical behavior. 3. "disallow". This prevents the use of plain text completely, by throwing an error if a password set or change is attempted. --- .../passwordcheck/expected/passwordcheck.out | 6 ++++ doc/src/sgml/config.sgml | 18 ++++++++++ src/backend/libpq/crypt.c | 34 +++++++++++++++++++ src/backend/utils/misc/guc_tables.c | 17 ++++++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/libpq/crypt.h | 19 +++++++++++ .../ecpg/test/expected/connect-test5.stderr | 16 ++++++--- src/test/regress/expected/create_role.out | 3 ++ src/test/regress/expected/password.out | 33 ++++++++++++++++++ 9 files changed, 143 insertions(+), 4 deletions(-) diff --git a/contrib/passwordcheck/expected/passwordcheck.out b/contrib/passwordcheck/expected/passwordcheck.out index 83472c76d278..602e91c50ac1 100644 --- a/contrib/passwordcheck/expected/passwordcheck.out +++ b/contrib/passwordcheck/expected/passwordcheck.out @@ -3,6 +3,9 @@ LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; -- ok ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- error: too short ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; ERROR: password is too short @@ -10,6 +13,9 @@ DETAIL: password must be at least "passwordcheck.min_password_length" (8) bytes -- ok SET passwordcheck.min_password_length = 6; ALTER USER regress_passwordcheck_user1 PASSWORD 'v_shrt'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- error: contains user name ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; ERROR: password must not contain user name diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 20ccb2d6b544..b7c707b11b96 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1104,6 +1104,24 @@ include_dir 'conf.d' + + cleartext_passwords_action (enum) + + cleartext_passwords_action configuration parameter + + + + + Controls what action to take when a password is sent unencrypted + (i.e. in clear text) to the server via the CREATE ROLE + or ALTER ROLE command. Valid options are + allow, warn, or + disallow. + The default value is warn. + + + + password_encryption (enum) diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index f6b641e726ec..808f6a306557 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -21,12 +21,16 @@ #include "libpq/crypt.h" #include "libpq/scram.h" #include "utils/builtins.h" +#include "utils/guc.h" #include "utils/syscache.h" #include "utils/timestamp.h" /* Enables deprecation warnings for MD5 passwords. */ bool md5_password_warnings = true; +/* Action to take when clear text passwords are used. */ +int cleartext_passwords_action = CLEARTEXT_ACTION_WARN; + /* * Fetch stored password for a user, for authentication. * @@ -131,6 +135,36 @@ encrypt_password(PasswordType target_type, const char *role, } else { + + /* + * We are sending clear text passwords to the server. What should we + * do about that? + */ + if (cleartext_passwords_action == CLEARTEXT_ACTION_WARN) + { + ereport(WARNING, + (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE), + errmsg("using a clear text password"), + errdetail("Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL."), + strncmp(application_name, "psql", 5) == 0 + ? errhint("If using psql, you can set the password with \\password") + : errhint("Use a client that can change the password without sending it in clear text"))); + } + else if (cleartext_passwords_action == CLEARTEXT_ACTION_DISALLOW) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PASSWORD), + errmsg("using a clear text password"), + errdetail("Sending a password using plain text is not allowed."), + strncmp(application_name, "psql", 5) == 0 + ? errhint("If using psql, you can change the password with \\password") + : errhint("Use a client that can change the password without sending it in clear text"))); + } + else + { + /* Silently accept this bad practice. */ + } + switch (target_type) { case PASSWORD_TYPE_MD5: diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index d14b1678e7fe..627ee9b6fcc0 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -417,6 +417,13 @@ static const struct config_enum_entry password_encryption_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry cleartext_action_options[] = { + {"allow", CLEARTEXT_ACTION_ALLOW, false}, + {"warn", CLEARTEXT_ACTION_WARN, false}, + {"disallow", CLEARTEXT_ACTION_DISALLOW, false}, + {NULL, 0, false} +}; + static const struct config_enum_entry ssl_protocol_versions_info[] = { {"", PG_TLS_ANY, false}, {"TLSv1", PG_TLS1_VERSION, false}, @@ -5418,6 +5425,16 @@ struct config_enum ConfigureNamesEnum[] = NULL, assign_io_method, NULL }, + { + {"cleartext_passwords_action", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Action to take when clear text passwords are used."), + }, + &cleartext_passwords_action, + CLEARTEXT_ACTION_WARN, cleartext_action_options, + NULL, NULL, NULL + }, + + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a9d8293474af..978ac20c73df 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -97,6 +97,7 @@ #password_encryption = scram-sha-256 # scram-sha-256 or md5 #scram_iterations = 4096 #md5_password_warnings = on +#cleartext_passwords_action = warn # can be allow, warn, or disallow #oauth_validator_libraries = '' # comma-separated list of trusted validator modules # GSSAPI using Kerberos diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index a1b4b3631433..921de2d8e6fb 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -28,6 +28,9 @@ /* Enables deprecation warnings for MD5 passwords. */ extern PGDLLIMPORT bool md5_password_warnings; +/* Specifies action when clear text passwords are used. */ +extern PGDLLIMPORT int cleartext_passwords_action; + /* * Types of password hashes or secrets. * @@ -44,6 +47,22 @@ typedef enum PasswordType PASSWORD_TYPE_SCRAM_SHA_256, } PasswordType; +/* + * Actions to take when clear text passwords are used. + * + * Passwords that are sent in clear text via the CREATE/ALTER USER + * command can cause a reaction by the server. We can either allow + * (the old behavior), warn (throw a warning and hint), or simply + * disallow (throws an exception). + */ +typedef enum CleartextAction +{ + CLEARTEXT_ACTION_ALLOW = 0, + CLEARTEXT_ACTION_WARN, + CLEARTEXT_ACTION_DISALLOW, +} CleartextAction; + + extern PasswordType get_password_type(const char *shadow_pass); extern char *encrypt_password(PasswordType target_type, const char *role, const char *password); diff --git a/src/interfaces/ecpg/test/expected/connect-test5.stderr b/src/interfaces/ecpg/test/expected/connect-test5.stderr index 037db217586d..d74cc1f1e818 100644 --- a/src/interfaces/ecpg/test/expected/connect-test5.stderr +++ b/src/interfaces/ecpg/test/expected/connect-test5.stderr @@ -4,16 +4,24 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_execute on line 24: query: alter user regress_ecpg_user2 encrypted password 'insecure'; with 0 parameter(s) on connection main [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 24: using PQexec +[NO_PID]: ECPGnoticeReceiver: using a clear text password [NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlcode 0 +[NO_PID]: sqlca: code: 0, state: 01P01 +[NO_PID]: ecpg_execute on line 24: using PQexec +[NO_PID]: sqlca: code: 0, state: 01P01 [NO_PID]: ecpg_process_output on line 24: OK: ALTER ROLE -[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: sqlca: code: 0, state: 01P01 [NO_PID]: ecpg_execute on line 25: query: alter user regress_ecpg_user1 encrypted password 'connectpw'; with 0 parameter(s) on connection main [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 25: using PQexec +[NO_PID]: ECPGnoticeReceiver: using a clear text password [NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlcode 0 +[NO_PID]: sqlca: code: 0, state: 01P01 +[NO_PID]: ecpg_execute on line 25: using PQexec +[NO_PID]: sqlca: code: 0, state: 01P01 [NO_PID]: ecpg_process_output on line 25: OK: ALTER ROLE -[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: sqlca: code: 0, state: 01P01 [NO_PID]: ECPGtrans on line 26: action "commit"; connection "main" [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_finish: connection main closed diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out index 46d4f9efe997..8347205e4aa8 100644 --- a/src/test/regress/expected/create_role.out +++ b/src/test/regress/expected/create_role.out @@ -64,6 +64,9 @@ CREATE ROLE regress_login LOGIN; CREATE ROLE regress_inherit INHERIT; CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text CREATE ROLE regress_password_null PASSWORD NULL; -- ok, backwards compatible noise words should be ignored CREATE ROLE regress_noiseword SYSID 12345; diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out index 9bb3ab2818bf..ec76afcbcef9 100644 --- a/src/test/regress/expected/password.out +++ b/src/test/regress/expected/password.out @@ -14,16 +14,25 @@ SET password_encryption = 'scram-sha-256'; -- ok SET password_encryption = 'md5'; CREATE ROLE regress_passwd1; ALTER ROLE regress_passwd1 PASSWORD 'role_pwd1'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text WARNING: setting an MD5-encrypted password DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. CREATE ROLE regress_passwd2; ALTER ROLE regress_passwd2 PASSWORD 'role_pwd2'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text WARNING: setting an MD5-encrypted password DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. SET password_encryption = 'scram-sha-256'; CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text CREATE ROLE regress_passwd4 PASSWORD NULL; -- check list of created entries -- @@ -63,6 +72,9 @@ ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2; SET password_encryption = 'md5'; -- encrypt with MD5 ALTER ROLE regress_passwd2 PASSWORD 'foo'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text WARNING: setting an MD5-encrypted password DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL. HINT: Refer to the PostgreSQL documentation for details about migrating to another password type. @@ -75,6 +87,9 @@ ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t SET password_encryption = 'scram-sha-256'; -- create SCRAM secret ALTER ROLE regress_passwd4 PASSWORD 'foo'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- already encrypted with MD5, use as it is CREATE ROLE regress_passwd5 PASSWORD 'md5e73a4b11df52a6068f8b39f90be36023'; WARNING: setting an MD5-encrypted password @@ -83,15 +98,27 @@ HINT: Refer to the PostgreSQL documentation for details about migrating to anot -- This looks like a valid SCRAM-SHA-256 secret, but it is not -- so it should be hashed with SCRAM-SHA-256. CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- These may look like valid MD5 secrets, but they are not, so they -- should be hashed with SCRAM-SHA-256. -- trailing garbage at the end CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- invalid length CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- Changing the SCRAM iteration count SET scram_iterations = 1024; CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount'; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked FROM pg_authid WHERE rolname LIKE 'regress_passwd%' @@ -128,7 +155,13 @@ SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty'; -- stored/server keys. They will be re-hashed. CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='; +WARNING: using a clear text password +DETAIL: Sending a password using plain text is deprecated and may be removed in a future release of PostgreSQL. +HINT: Use a client that can change the password without sending it in clear text -- Check that the invalid secrets were re-hashed. A re-hashed secret -- should not contain the original salt. SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed