Hello, We've got a long tradition of telling people not to use unencrypted passwords in CREATE ROLE and ALTER ROLE because the queries may be logged. We try to encourage them to use \password in psql, and related techniques on other tools. Users usually want us to stop logging passwords, but this is not that easy to do (if it's at all possible and interesting). A few days ago, I read Divya Sharma's interview on postgresql.life [1] and she said:
> Whenever log_statement is set to all (which I understand should be done for a short period of time for troubleshooting purposes only), if we change the password for a user, or create a new user, the passwords would be logged in plain text. From a security point of view, this should not be allowed. Ideally, It should error out (or at least throw a warning) saying “while log_statement is set to ‘all’, you shouldn’t change passwords/create new user with passwords”. While I dislike the idea of throwing an error, I found the idea of a warning message really great. So kudos to her for the idea! I thought about it, and tried to write a patch. I've mostly copied the "Deprecate MD5 passwords" patch/commit from Nathan Bossart. My patch works on current HEAD. Documentation and tests are dealt with. Here is a quick demo: postgres=# show plaintext_password_warnings; plaintext_password_warnings ----------------------------- on (1 row) postgres=# create user foo password 'bar'; WARNING: using a plaintext password in a query DETAIL: plaintext password may be logged. HINT: Refer to the PostgreSQL documentation for details about using encrypted password in queries. CREATE ROLE postgres=# alter role foo password 'bar2'; WARNING: using a plaintext password in a query DETAIL: plaintext password may be logged. HINT: Refer to the PostgreSQL documentation for details about using encrypted password in queries. ALTER ROLE postgres=# set plaintext_password_warnings to off; SET postgres=# alter role foo password 'bar3'; ALTER ROLE postgres=# set plaintext_password_warnings to on; SET postgres=# \password foo Enter new password for user "foo": Enter it again: As I'm writing this email, I'm thinking we could transform the boolean GUC into an enum GUC, allowing the user to get an error or a log message, or no message at all (old behaviour), whatever fits better for him/her. I'm interested in any comments about this. I didn't create a commitfest entry yet, I'm mostly waiting on your comments. Thanks. Regards. [1] https://postgresql.life/post/divya_sharma/ -- Guillaume.
From 6415a0dcf0a2735ee160c648b9452f3aa5d52e9c Mon Sep 17 00:00:00 2001 From: Guillaume Lelarge <guillaume.lela...@dalibo.com> Date: Sat, 7 Dec 2024 10:22:37 +0100 Subject: [PATCH] Add a warning when using plain text passwords With CREATE/ALTER ROLE... PASSWORD, plain text passwords may be logged and can be discovered if someone is listening on the network. We used to tell users not to use plain text passwords, but it would be better to warn the user of the issue if the user tries to add/change a password using a plain text one in the query. CREATE ROLE and ALTER ROLE now emit warnings when using plain text passwords. The warnings can be disabled by setting the plaintext_password_warnings parameter to "off". --- contrib/passwordcheck/expected/passwordcheck.out | 1 + .../passwordcheck/expected/passwordcheck_1.out | 1 + contrib/passwordcheck/sql/passwordcheck.sql | 1 + doc/src/sgml/config.sgml | 16 ++++++++++++++++ src/backend/libpq/crypt.c | 10 ++++++++++ src/backend/utils/misc/guc_tables.c | 9 +++++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/libpq/crypt.h | 3 +++ src/test/regress/expected/create_role.out | 2 ++ src/test/regress/expected/password.out | 7 +++++++ src/test/regress/sql/create_role.sql | 2 ++ src/test/regress/sql/password.sql | 5 +++++ 12 files changed, 58 insertions(+) diff --git a/contrib/passwordcheck/expected/passwordcheck.out b/contrib/passwordcheck/expected/passwordcheck.out index dfb2ccfe00..0f0f3dad9e 100644 --- a/contrib/passwordcheck/expected/passwordcheck.out +++ b/contrib/passwordcheck/expected/passwordcheck.out @@ -1,3 +1,4 @@ +SET plaintext_password_warnings = off; SET md5_password_warnings = off; LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; diff --git a/contrib/passwordcheck/expected/passwordcheck_1.out b/contrib/passwordcheck/expected/passwordcheck_1.out index 9519d60a49..b124011752 100644 --- a/contrib/passwordcheck/expected/passwordcheck_1.out +++ b/contrib/passwordcheck/expected/passwordcheck_1.out @@ -1,3 +1,4 @@ +SET plaintext_password_warnings = off; SET md5_password_warnings = off; LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; diff --git a/contrib/passwordcheck/sql/passwordcheck.sql b/contrib/passwordcheck/sql/passwordcheck.sql index 5953ece5c2..4aece2bed1 100644 --- a/contrib/passwordcheck/sql/passwordcheck.sql +++ b/contrib/passwordcheck/sql/passwordcheck.sql @@ -1,3 +1,4 @@ +SET plaintext_password_warnings = off; SET md5_password_warnings = off; LOAD 'passwordcheck'; diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index e0c8325a39..c162da4a88 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7921,6 +7921,22 @@ log_line_prefix = '%m [%p] %q%u@%d/%a ' </listitem> </varlistentry> + <varlistentry id="guc-plaintext-password-warnings" xreflabel="plaintext_password_warnings"> + <term><varname>plaintext_password_warnings</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>plaintext_password_warnings</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Controls whether a <literal>WARNING</literal> about plaintext password + is produced when a <command>CREATE ROLE</command> or + <command>ALTER ROLE</command> statement uses a plaintext password. + The default value is <literal>on</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-md5-password-warnings" xreflabel="md5_password_warnings"> <term><varname>md5_password_warnings</varname> (<type>boolean</type>) <indexterm> diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index d37c70901b..97fc69e3d3 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -24,6 +24,9 @@ #include "utils/syscache.h" #include "utils/timestamp.h" +/* Enables usage warnings for plaintext passwords. */ +bool plaintext_password_warnings = true; + /* Enables deprecation warnings for MD5 passwords. */ bool md5_password_warnings = true; @@ -176,6 +179,13 @@ encrypt_password(PasswordType target_type, const char *role, MAX_ENCRYPTED_PASSWORD_LEN))); } + if (plaintext_password_warnings && + get_password_type(password) == PASSWORD_TYPE_PLAINTEXT) + ereport(WARNING, + (errmsg("using a plaintext password in a query"), + errdetail("plaintext password may be logged."), + errhint("Refer to the PostgreSQL documentation for details about using encrypted password in queries."))); + if (md5_password_warnings && get_password_type(encrypted_password) == PASSWORD_TYPE_MD5) ereport(WARNING, diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 8cf1afbad2..c43831bf70 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2086,6 +2086,15 @@ struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"plaintext_password_warnings", PGC_USERSET, CONN_AUTH_AUTH, + gettext_noop("Enables usage warnings for plaintext passwords."), + }, + &plaintext_password_warnings, + true, + NULL, NULL, NULL + }, + { {"md5_password_warnings", PGC_USERSET, CONN_AUTH_AUTH, gettext_noop("Enables deprecation warnings for MD5 passwords."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a2ac7575ca..8265ffc9a0 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -96,6 +96,7 @@ #authentication_timeout = 1min # 1s-600s #password_encryption = scram-sha-256 # scram-sha-256 or md5 #scram_iterations = 4096 +#plaintext_password_warnings = on #md5_password_warnings = on # GSSAPI using Kerberos diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index db7ea7bd1f..2f834bdcf0 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -25,6 +25,9 @@ */ #define MAX_ENCRYPTED_PASSWORD_LEN (512) +/* Enables usage warnings for plaintext passwords. */ +extern PGDLLIMPORT bool plaintext_password_warnings; + /* Enables deprecation warnings for MD5 passwords. */ extern PGDLLIMPORT bool md5_password_warnings; diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out index 46d4f9efe9..546fa2f086 100644 --- a/src/test/regress/expected/create_role.out +++ b/src/test/regress/expected/create_role.out @@ -63,8 +63,10 @@ GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION; CREATE ROLE regress_login LOGIN; CREATE ROLE regress_inherit INHERIT; CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; +SET plaintext_password_warnings = off; CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; CREATE ROLE regress_password_null PASSWORD NULL; +SET plaintext_password_warnings = on; -- ok, backwards compatible noise words should be ignored CREATE ROLE regress_noiseword SYSID 12345; NOTICE: SYSID can no longer be specified diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out index 9bb3ab2818..1def5a2bc4 100644 --- a/src/test/regress/expected/password.out +++ b/src/test/regress/expected/password.out @@ -14,9 +14,13 @@ 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 plaintext password in a query +DETAIL: plaintext password may be logged. +HINT: Refer to the PostgreSQL documentation for details about using encrypted password in queries. 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 plaintext_password_warnings = off; CREATE ROLE regress_passwd2; ALTER ROLE regress_passwd2 PASSWORD 'role_pwd2'; WARNING: setting an MD5-encrypted password @@ -67,11 +71,13 @@ 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. -- already encrypted, use as they are +SET plaintext_password_warnings = on; ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70'; 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. ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; +SET plaintext_password_warnings = off; SET password_encryption = 'scram-sha-256'; -- create SCRAM secret ALTER ROLE regress_passwd4 PASSWORD 'foo'; @@ -171,3 +177,4 @@ SELECT rolname, rolpassword ---------+------------- (0 rows) +SET plaintext_password_warnings = on; diff --git a/src/test/regress/sql/create_role.sql b/src/test/regress/sql/create_role.sql index 4491a28a8a..600d428e8e 100644 --- a/src/test/regress/sql/create_role.sql +++ b/src/test/regress/sql/create_role.sql @@ -48,8 +48,10 @@ GRANT CREATE ON DATABASE regression TO regress_createrole WITH GRANT OPTION; CREATE ROLE regress_login LOGIN; CREATE ROLE regress_inherit INHERIT; CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; +SET plaintext_password_warnings = off; CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; CREATE ROLE regress_password_null PASSWORD NULL; +SET plaintext_password_warnings = on; -- ok, backwards compatible noise words should be ignored CREATE ROLE regress_noiseword SYSID 12345; diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql index e7a9804545..618d5368a8 100644 --- a/src/test/regress/sql/password.sql +++ b/src/test/regress/sql/password.sql @@ -12,6 +12,7 @@ SET password_encryption = 'scram-sha-256'; -- ok SET password_encryption = 'md5'; CREATE ROLE regress_passwd1; ALTER ROLE regress_passwd1 PASSWORD 'role_pwd1'; +SET plaintext_password_warnings = off; CREATE ROLE regress_passwd2; ALTER ROLE regress_passwd2 PASSWORD 'role_pwd2'; SET password_encryption = 'scram-sha-256'; @@ -46,8 +47,10 @@ SET password_encryption = 'md5'; -- encrypt with MD5 ALTER ROLE regress_passwd2 PASSWORD 'foo'; -- already encrypted, use as they are +SET plaintext_password_warnings = on; ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70'; ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; +SET plaintext_password_warnings = off; SET password_encryption = 'scram-sha-256'; -- create SCRAM secret @@ -118,3 +121,5 @@ SELECT rolname, rolpassword FROM pg_authid WHERE rolname LIKE 'regress_passwd%' ORDER BY rolname, rolpassword; + +SET plaintext_password_warnings = on; -- 2.47.1