From 257ed4673bb4e38340c1df9f55c97f5de8ad9838 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 9 Mar 2017 21:55:15 +0900 Subject: [PATCH] Add clause PASSWORD (val USING protocol) to CREATE/ALTER ROLE This clause allows users to be able to enforce with which protocol a given password is used with. if the value given is already encrypted, the value is used as-is. This extension is useful to support future protocols, particularly SCRAM-SHA-256. --- doc/src/sgml/ref/alter_role.sgml | 10 +++++ doc/src/sgml/ref/create_role.sgml | 26 +++++++++++ src/backend/commands/user.c | 80 +++++++++++++++++++++++++++++++--- src/backend/parser/gram.y | 7 +++ src/test/regress/expected/password.out | 14 +++++- src/test/regress/sql/password.sql | 9 ++++ 6 files changed, 140 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index da36ad9696..257e3b96bf 100644 --- a/doc/src/sgml/ref/alter_role.sgml +++ b/doc/src/sgml/ref/alter_role.sgml @@ -34,6 +34,7 @@ ALTER ROLE role_specification [ WIT | BYPASSRLS | NOBYPASSRLS | CONNECTION LIMIT connlimit | [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'password' + | PASSWORD 'password' USING 'method' | VALID UNTIL 'timestamp' ALTER ROLE name RENAME TO new_name @@ -169,6 +170,7 @@ ALTER ROLE { role_specification | A NOBYPASSRLS CONNECTION LIMIT connlimit PASSWORD password + PASSWORD 'password' USING 'method' ENCRYPTED UNENCRYPTED VALID UNTIL 'timestamp' @@ -280,6 +282,14 @@ ALTER ROLE davide WITH PASSWORD 'hu8jmn3'; + Change a role's password using MD5 hash: + + +ALTER ROLE lionel WITH PASSWORD 'hu8jmn3' USING 'md5'; + + + + Remove a role's password: diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml index 99d1c8336c..54aa9c524b 100644 --- a/doc/src/sgml/ref/create_role.sgml +++ b/doc/src/sgml/ref/create_role.sgml @@ -34,6 +34,7 @@ CREATE ROLE name [ [ WITH ] connlimit | [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'password' + | PASSWORD 'password' USING 'method' | VALID UNTIL 'timestamp' | IN ROLE role_name [, ...] | IN GROUP role_name [, ...] @@ -247,6 +248,23 @@ CREATE ROLE name [ [ WITH ] + PASSWORD 'password' USING 'method' + + + Sets the role's password using the requested method. (A password + is only of use for roles having the LOGIN + attribute, but you can nonetheless define one for roles without it.) + If you do not plan to use password authentication you can omit this + option. The methods supported are md5 to enforce a password + to be MD5-hashed, scram for a SCRAM-hashed password + and plain for an non-hashed password. If the password + string is already in MD5-hashed or SCRAM-hashed, then it is + stored hashed as-is. + + + + + VALID UNTIL 'timestamp' @@ -428,6 +446,14 @@ CREATE USER davide WITH PASSWORD 'jw8s0F4'; + Create a role with a MD5-hashed password: + + +CREATE USER lionel WITH PASSWORD 'asdh7as' USING 'md5'; + + + + Create a role with a password that is valid until the end of 2004. After one second has ticked in 2005, the password is no longer valid. diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 14b9779144..a8729f494d 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -130,7 +130,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 || - strcmp(defel->defname, "unencryptedPassword") == 0) + strcmp(defel->defname, "unencryptedPassword") == 0 || + strcmp(defel->defname, "methodPassword") == 0) { if (dpassword) ereport(ERROR, @@ -144,9 +145,45 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) password_type = PASSWORD_TYPE_SCRAM; else password_type = PASSWORD_TYPE_MD5; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); } else if (strcmp(defel->defname, "unencryptedPassword") == 0) + { password_type = PASSWORD_TYPE_PLAINTEXT; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); + } + else if (strcmp(defel->defname, "methodPassword") == 0) + { + /* + * This is a list of two elements, the password is first and + * then there is the method wanted by caller. + */ + if (dpassword && dpassword->arg) + { + char *method = strVal(lsecond((List *) dpassword->arg)); + + password = strVal(linitial((List *) dpassword->arg)); + + if (strcmp(method, "md5") == 0) + password_type = PASSWORD_TYPE_MD5; + else if (strcmp(method, "plain") == 0) + password_type = PASSWORD_TYPE_PLAINTEXT; + else if (strcmp(method, "scram") == 0) + password_type = PASSWORD_TYPE_SCRAM; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unsupported password method %s", method))); + } + } + else + { + password_type = Password_encryption; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); + } } else if (strcmp(defel->defname, "sysid") == 0) { @@ -266,8 +303,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) defel->defname); } - if (dpassword && dpassword->arg) - password = strVal(dpassword->arg); if (dissuper) issuper = intVal(dissuper->arg) != 0; if (dinherit) @@ -539,6 +574,7 @@ AlterRole(AlterRoleStmt *stmt) if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 || + strcmp(defel->defname, "methodPassword") == 0 || strcmp(defel->defname, "unencryptedPassword") == 0) { if (dpassword) @@ -552,9 +588,45 @@ AlterRole(AlterRoleStmt *stmt) password_type = PASSWORD_TYPE_SCRAM; else password_type = PASSWORD_TYPE_MD5; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); } else if (strcmp(defel->defname, "unencryptedPassword") == 0) + { password_type = PASSWORD_TYPE_PLAINTEXT; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); + } + else if (strcmp(defel->defname, "methodPassword") == 0) + { + /* + * This is a list of two elements, the password is first and + * then there is the method wanted by caller. + */ + if (dpassword && dpassword->arg) + { + char *method = strVal(lsecond((List *) dpassword->arg)); + + if (strcmp(method, "md5") == 0) + password_type = PASSWORD_TYPE_MD5; + else if (strcmp(method, "plain") == 0) + password_type = PASSWORD_TYPE_PLAINTEXT; + else if (strcmp(method, "scram") == 0) + password_type = PASSWORD_TYPE_SCRAM; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unsupported password method %s", method))); + + password = strVal(linitial((List *) dpassword->arg)); + } + } + else + { + password_type = Password_encryption; + if (dpassword && dpassword->arg) + password = strVal(dpassword->arg); + } } else if (strcmp(defel->defname, "superuser") == 0) { @@ -642,8 +714,6 @@ AlterRole(AlterRoleStmt *stmt) defel->defname); } - if (dpassword && dpassword->arg) - password = strVal(dpassword->arg); if (dissuper) issuper = intVal(dissuper->arg); if (dinherit) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e7acc2d9a2..fbe5ba35d6 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -988,6 +988,13 @@ AlterOptRoleElem: { $$ = makeDefElem("password", NULL, @1); } + | PASSWORD Sconst USING Sconst + { + $$ = makeDefElem("methodPassword", + (Node *)list_make2(makeString($2), + makeString($4)), + @1); + } | ENCRYPTED PASSWORD Sconst { $$ = makeDefElem("encryptedPassword", diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out index c503e43abe..7f18f35eb1 100644 --- a/src/test/regress/expected/password.out +++ b/src/test/regress/expected/password.out @@ -63,6 +63,12 @@ ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'scram-sha-256:VLK4RMaQLCvNtQ==:40 SET password_encryption = 'scram'; ALTER ROLE regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is +-- PASSWORD 'value' USING 'method' +CREATE ROLE regress_passwd7 PASSWORD 'role_pwd7' USING 'plain'; +CREATE ROLE regress_passwd8 PASSWORD 'role_pwd8' USING 'md5'; +CREATE ROLE regress_passwd9 PASSWORD 'role_pwd9' USING 'scram'; +CREATE ROLE regress_passwd10 PASSWORD 'role_pwd10' USING 'novalue'; --error +ERROR: unsupported password method novalue SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1::\3::') as rolpassword_masked FROM pg_authid WHERE rolname LIKE 'regress_passwd%' @@ -75,7 +81,10 @@ SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==): regress_passwd4 | scram-sha-256::4096:: regress_passwd5 | scram-sha-256::4096:: regress_passwd6 | md53725413363ab045e20521bf36b8d8d7f -(6 rows) + regress_passwd7 | role_pwd7 + regress_passwd8 | md571f6e76fa74cf5716b886f35ad945663 + regress_passwd9 | scram-sha-256::4096:: +(9 rows) DROP ROLE regress_passwd1; DROP ROLE regress_passwd2; @@ -83,6 +92,9 @@ DROP ROLE regress_passwd3; DROP ROLE regress_passwd4; DROP ROLE regress_passwd5; DROP ROLE regress_passwd6; +DROP ROLE regress_passwd7; +DROP ROLE regress_passwd8; +DROP ROLE regress_passwd9; -- all entries should have been removed SELECT rolname, rolpassword FROM pg_authid diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql index f4b3a9ac3a..011be018ba 100644 --- a/src/test/regress/sql/password.sql +++ b/src/test/regress/sql/password.sql @@ -54,6 +54,12 @@ SET password_encryption = 'scram'; ALTER ROLE regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is +-- PASSWORD 'value' USING 'method' +CREATE ROLE regress_passwd7 PASSWORD 'role_pwd7' USING 'plain'; +CREATE ROLE regress_passwd8 PASSWORD 'role_pwd8' USING 'md5'; +CREATE ROLE regress_passwd9 PASSWORD 'role_pwd9' USING 'scram'; +CREATE ROLE regress_passwd10 PASSWORD 'role_pwd10' USING 'novalue'; --error + SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1::\3::') as rolpassword_masked FROM pg_authid WHERE rolname LIKE 'regress_passwd%' @@ -65,6 +71,9 @@ DROP ROLE regress_passwd3; DROP ROLE regress_passwd4; DROP ROLE regress_passwd5; DROP ROLE regress_passwd6; +DROP ROLE regress_passwd7; +DROP ROLE regress_passwd8; +DROP ROLE regress_passwd9; -- all entries should have been removed SELECT rolname, rolpassword -- 2.12.0