diff --git a/docs/generated/settings/settings.html b/docs/generated/settings/settings.html
index 7da7383631ca..b1b254dffd8e 100644
--- a/docs/generated/settings/settings.html
+++ b/docs/generated/settings/settings.html
@@ -71,6 +71,6 @@
trace.debug.enable | boolean | false | if set, traces for recent requests can be seen in the /debug page |
trace.lightstep.token | string |
| if set, traces go to Lightstep using this token |
trace.zipkin.collector | string |
| if set, traces go to the given Zipkin instance (example: '127.0.0.1:9411'); ignored if trace.lightstep.token is set |
-version | custom validation | 20.1-7 | set the active cluster version in the format '.' |
+version | custom validation | 20.1-8 | set the active cluster version in the format '.' |
diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf
index 201a7b74e2d1..2fc44e5d88d6 100644
--- a/docs/generated/sql/bnf/stmt_block.bnf
+++ b/docs/generated/sql/bnf/stmt_block.bnf
@@ -760,6 +760,7 @@ unreserved_keyword ::=
| 'NO_INDEX_JOIN'
| 'NOCREATEROLE'
| 'NOLOGIN'
+ | 'NOSETPASSWORD'
| 'NOWAIT'
| 'NULLS'
| 'IGNORE_FOREIGN_KEYS'
@@ -813,6 +814,7 @@ unreserved_keyword ::=
| 'ROLLUP'
| 'ROWS'
| 'RULE'
+ | 'SETPASSWORD'
| 'SETTING'
| 'SETTINGS'
| 'STATUS'
@@ -1805,6 +1807,8 @@ role_option ::=
| 'NOCREATEROLE'
| 'LOGIN'
| 'NOLOGIN'
+ | 'SETPASSWORD'
+ | 'NOSETPASSWORD'
| password_clause
| valid_until_clause
diff --git a/pkg/clusterversion/cockroach_versions.go b/pkg/clusterversion/cockroach_versions.go
index 35d339a5dd42..3beea06ca97d 100644
--- a/pkg/clusterversion/cockroach_versions.go
+++ b/pkg/clusterversion/cockroach_versions.go
@@ -67,6 +67,7 @@ const (
VersionAlterColumnTypeGeneral
VersionAlterSystemJobsAddCreatedByColumns
VersionAddScheduledJobsTable
+ VersionSetPasswordPrivilege
// Add new versions here (step one of two).
)
@@ -511,6 +512,13 @@ var versionsSingleton = keyedVersions([]keyedVersion{
Key: VersionAddScheduledJobsTable,
Version: roachpb.Version{Major: 20, Minor: 1, Unstable: 7},
},
+ {
+ // VersionSetPasswordPrivilege is when SETPASSWORD/NOSETPASSWORD are introduced.
+ //
+ // It represents adding password management via SETPASSWORD role option.
+ Key: VersionSetPasswordPrivilege,
+ Version: roachpb.Version{Major: 20, Minor: 1, Unstable: 8},
+ },
// Add new versions here (step two of two).
diff --git a/pkg/clusterversion/versionkey_string.go b/pkg/clusterversion/versionkey_string.go
index 76675efee0be..4040b51fb325 100644
--- a/pkg/clusterversion/versionkey_string.go
+++ b/pkg/clusterversion/versionkey_string.go
@@ -43,11 +43,12 @@ func _() {
_ = x[VersionAlterColumnTypeGeneral-32]
_ = x[VersionAlterSystemJobsAddCreatedByColumns-33]
_ = x[VersionAddScheduledJobsTable-34]
+ _ = x[VersionSetPasswordPrivilege-35]
}
-const _VersionKey_name = "Version19_1VersionStart19_2VersionLearnerReplicasVersionTopLevelForeignKeysVersionAtomicChangeReplicasTriggerVersionAtomicChangeReplicasVersionTableDescModificationTimeFromMVCCVersionPartitionedBackupVersion19_2VersionStart20_1VersionContainsEstimatesCounterVersionChangeReplicasDemotionVersionSecondaryIndexColumnFamiliesVersionNamespaceTableWithSchemasVersionProtectedTimestampsVersionPrimaryKeyChangesVersionAuthLocalAndTrustRejectMethodsVersionPrimaryKeyColumnsOutOfFamilyZeroVersionRootPasswordVersionNoExplicitForeignKeyIndexIDsVersionHashShardedIndexesVersionCreateRolePrivilegeVersionStatementDiagnosticsSystemTablesVersionSchemaChangeJobVersionSavepointsVersionTimeTZTypeVersionTimePrecisionVersion20_1VersionStart20_2VersionGeospatialTypeVersionEnumsVersionRangefeedLeasesVersionAlterColumnTypeGeneralVersionAlterSystemJobsAddCreatedByColumnsVersionAddScheduledJobsTable"
+const _VersionKey_name = "Version19_1VersionStart19_2VersionLearnerReplicasVersionTopLevelForeignKeysVersionAtomicChangeReplicasTriggerVersionAtomicChangeReplicasVersionTableDescModificationTimeFromMVCCVersionPartitionedBackupVersion19_2VersionStart20_1VersionContainsEstimatesCounterVersionChangeReplicasDemotionVersionSecondaryIndexColumnFamiliesVersionNamespaceTableWithSchemasVersionProtectedTimestampsVersionPrimaryKeyChangesVersionAuthLocalAndTrustRejectMethodsVersionPrimaryKeyColumnsOutOfFamilyZeroVersionRootPasswordVersionNoExplicitForeignKeyIndexIDsVersionHashShardedIndexesVersionCreateRolePrivilegeVersionStatementDiagnosticsSystemTablesVersionSchemaChangeJobVersionSavepointsVersionTimeTZTypeVersionTimePrecisionVersion20_1VersionStart20_2VersionGeospatialTypeVersionEnumsVersionRangefeedLeasesVersionAlterColumnTypeGeneralVersionAlterSystemJobsAddCreatedByColumnsVersionAddScheduledJobsTableVersionSetPasswordPrivilege"
-var _VersionKey_index = [...]uint16{0, 11, 27, 49, 75, 109, 136, 176, 200, 211, 227, 258, 287, 322, 354, 380, 404, 441, 480, 499, 534, 559, 585, 624, 646, 663, 680, 700, 711, 727, 748, 760, 782, 811, 852, 880}
+var _VersionKey_index = [...]uint16{0, 11, 27, 49, 75, 109, 136, 176, 200, 211, 227, 258, 287, 322, 354, 380, 404, 441, 480, 499, 534, 559, 585, 624, 646, 663, 680, 700, 711, 727, 748, 760, 782, 811, 852, 880, 907}
func (i VersionKey) String() string {
if i < 0 || i >= VersionKey(len(_VersionKey_index)-1) {
diff --git a/pkg/sql/alter_role.go b/pkg/sql/alter_role.go
index 619d51294bff..4c9e8776a309 100644
--- a/pkg/sql/alter_role.go
+++ b/pkg/sql/alter_role.go
@@ -65,6 +65,13 @@ func (p *planner) AlterRoleNode(
return nil, err
}
+ // Check that the requested combination of
+ // PASSWORD/SETPASSWORD/NOSETPASSWORD is compatible with the user's
+ // own SETPASSWORD privilege.
+ if err := p.checkPasswordOptionConstraints(ctx, roleOptions); err != nil {
+ return nil, err
+ }
+
ua, err := p.getUserAuthInfo(ctx, nameE, opName)
if err != nil {
return nil, err
@@ -78,6 +85,33 @@ func (p *planner) AlterRoleNode(
}, nil
}
+func (p *planner) checkPasswordOptionConstraints(
+ ctx context.Context, roleOptions roleoption.List,
+) error {
+ if !p.EvalContext().Settings.Version.IsActive(ctx, clusterversion.VersionSetPasswordPrivilege) {
+ // TODO(knz): Remove this condition in 21.1.
+ if roleOptions.Contains(roleoption.SETPASSWORD) || roleOptions.Contains(roleoption.NOSETPASSWORD) {
+ return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState,
+ `granting SETPASSWORD or NOSETPASSWORD requires all nodes to be upgraded to %s`,
+ clusterversion.VersionByKey(clusterversion.VersionSetPasswordPrivilege))
+ }
+ } else {
+ if roleOptions.Contains(roleoption.SETPASSWORD) ||
+ roleOptions.Contains(roleoption.NOSETPASSWORD) ||
+ roleOptions.Contains(roleoption.PASSWORD) ||
+ roleOptions.Contains(roleoption.VALIDUNTIL) {
+ // Only a role who has SETPASSWORD itself can grant SETPASSWORD
+ // or NOSETPASSWORD to another role, or set up a password for
+ // authentication, or set up password validity, even if they
+ // have CREATEROLE privilege.
+ if err := p.HasRoleOption(ctx, roleoption.SETPASSWORD); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
func (n *alterRoleNode) startExec(params runParams) error {
var opName string
if n.isRole {
diff --git a/pkg/sql/create_role.go b/pkg/sql/create_role.go
index 3d4a5ba89f09..a5799d670a97 100644
--- a/pkg/sql/create_role.go
+++ b/pkg/sql/create_role.go
@@ -66,6 +66,9 @@ func (p *planner) CreateRoleNode(
return p.TypeAsStringOrNull(ctx, e, op)
}
roleOptions, err := kvOptions.ToRoleOptions(asStringOrNull, opName)
+ if err != nil {
+ return nil, err
+ }
// Using CREATE ROLE syntax enables NOLOGIN by default.
if isRole && !roleOptions.Contains(roleoption.LOGIN) &&
@@ -74,11 +77,14 @@ func (p *planner) CreateRoleNode(
roleoption.RoleOption{Option: roleoption.NOLOGIN, HasValue: false})
}
- if err != nil {
+ if err := roleOptions.CheckRoleOptionConflicts(); err != nil {
return nil, err
}
- if err := roleOptions.CheckRoleOptionConflicts(); err != nil {
+ // Check that the requested combination of
+ // PASSWORD/SETPASSWORD/NOSETPASSWORD is compatible with the user's
+ // own SETPASSWORD privilege.
+ if err := p.checkPasswordOptionConstraints(ctx, roleOptions); err != nil {
return nil, err
}
@@ -112,6 +118,10 @@ func (n *CreateRoleNode) startExec(params runParams) error {
var hashedPassword []byte
if n.roleOptions.Contains(roleoption.PASSWORD) {
+ if err := params.p.HasRoleOption(params.ctx, roleoption.SETPASSWORD); err != nil {
+ return err
+ }
+
hashedPassword, err = n.roleOptions.GetHashedPassword()
if err != nil {
return err
diff --git a/pkg/sql/logictest/testdata/logic_test/drop_user b/pkg/sql/logictest/testdata/logic_test/drop_user
index 298ab91fefe9..7a38d1eacfdc 100644
--- a/pkg/sql/logictest/testdata/logic_test/drop_user
+++ b/pkg/sql/logictest/testdata/logic_test/drop_user
@@ -6,11 +6,11 @@ CREATE USER user1
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user1 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user1 · {}
statement ok
DROP USER user1
@@ -18,10 +18,10 @@ DROP USER user1
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement ok
CREATE USER user1
@@ -29,11 +29,11 @@ CREATE USER user1
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user1 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user1 · {}
statement ok
DROP USER USEr1
@@ -41,10 +41,10 @@ DROP USER USEr1
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement error user user1 does not exist
DROP USER user1
@@ -76,14 +76,14 @@ CREATE USER user4
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user1 · {}
-user2 · {}
-user3 · {}
-user4 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user1 · {}
+user2 · {}
+user3 · {}
+user4 · {}
statement ok
DROP USER user1,user2
@@ -91,12 +91,12 @@ DROP USER user1,user2
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user3 · {}
-user4 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user3 · {}
+user4 · {}
statement error user user1 does not exist
DROP USER user1,user3
@@ -104,12 +104,12 @@ DROP USER user1,user3
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user3 · {}
-user4 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user3 · {}
+user4 · {}
statement ok
CREATE USER user1
@@ -137,10 +137,10 @@ PREPARE du AS DROP USER $1;
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
user testuser
diff --git a/pkg/sql/logictest/testdata/logic_test/role b/pkg/sql/logictest/testdata/logic_test/role
index b6e1ee2e2176..37e7db7b374e 100644
--- a/pkg/sql/logictest/testdata/logic_test/role
+++ b/pkg/sql/logictest/testdata/logic_test/role
@@ -27,11 +27,11 @@ CREATE ROLE myrole
query TTT colnames
SHOW ROLES
----
-username options member_of
-admin CREATEROLE {}
-myrole NOLOGIN {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+myrole NOLOGIN {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement error a role/user named myrole already exists
CREATE ROLE myrole
@@ -57,11 +57,11 @@ DROP ROLE admin, myrole
query TTT colnames
SHOW ROLES
----
-username options member_of
-admin CREATEROLE {}
-myrole NOLOGIN {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+myrole NOLOGIN {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement ok
DROP ROLE myrole
@@ -69,10 +69,10 @@ DROP ROLE myrole
query TTT colnames
SHOW ROLES
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement error pq: role/user myrole does not exist
DROP ROLE myrole
@@ -104,10 +104,10 @@ DROP ROLE rolea, roleb
query TTT colnames
SHOW ROLES
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement ok
CREATE USER testuser2
@@ -449,13 +449,13 @@ roled testuser false
query TTT
SHOW ROLES
----
-admin CREATEROLE {}
-roleb NOLOGIN {}
-roled NOLOGIN {}
-rolee NOLOGIN {}
-root CREATEROLE {admin}
-testuser · {roled}
-testuser2 · {}
+admin CREATEROLE, SETPASSWORD {}
+roleb NOLOGIN {}
+roled NOLOGIN {}
+rolee NOLOGIN {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {roled}
+testuser2 · {}
statement ok
DROP ROLE roleb
@@ -1016,8 +1016,10 @@ CREATE ROLE rolewithlogin LOGIN
query TTT
SELECT * FROM system.role_options
----
-admin CREATEROLE NULL
-root CREATEROLE NULL
+admin CREATEROLE NULL
+admin SETPASSWORD NULL
+root CREATEROLE NULL
+root SETPASSWORD NULL
statement ok
CREATE ROLE rolewithnologin NOLOGIN
@@ -1025,9 +1027,11 @@ CREATE ROLE rolewithnologin NOLOGIN
query TTT
SELECT * FROM system.role_options
----
-admin CREATEROLE NULL
-rolewithnologin NOLOGIN NULL
-root CREATEROLE NULL
+admin CREATEROLE NULL
+admin SETPASSWORD NULL
+rolewithnologin NOLOGIN NULL
+root CREATEROLE NULL
+root SETPASSWORD NULL
statement ok
ALTER ROLE rolewithlogin VALID UNTIL '2020-01-01'
@@ -1036,9 +1040,11 @@ query TTT
SELECT * FROM system.role_options
----
admin CREATEROLE NULL
+admin SETPASSWORD NULL
rolewithlogin VALID UNTIL 2020-01-01 00:00:00+00:00
rolewithnologin NOLOGIN NULL
root CREATEROLE NULL
+root SETPASSWORD NULL
statement ok
ALTER ROLE rolewithlogin VALID UNTIL NULL
@@ -1047,9 +1053,11 @@ query TTT
SELECT * FROM system.role_options
----
admin CREATEROLE NULL
+admin SETPASSWORD NULL
rolewithlogin VALID UNTIL NULL
rolewithnologin NOLOGIN NULL
root CREATEROLE NULL
+root SETPASSWORD NULL
statement ok
DROP ROLE rolewithlogin
@@ -1057,9 +1065,11 @@ DROP ROLE rolewithlogin
query TTT
SELECT * FROM system.role_options
----
-admin CREATEROLE NULL
-rolewithnologin NOLOGIN NULL
-root CREATEROLE NULL
+admin CREATEROLE NULL
+admin SETPASSWORD NULL
+rolewithnologin NOLOGIN NULL
+root CREATEROLE NULL
+root SETPASSWORD NULL
statement error pq: conflicting role options
CREATE ROLE thisshouldntwork LOGIN NOLOGIN
@@ -1200,3 +1210,103 @@ INSERT INTO publicdb.publictable VALUES (1)
query TTT
SHOW TABLES FROM publicdb
----
+
+subtest setpassword_privilege
+
+user root
+
+statement ok
+CREATE ROLE pseudo_admin;
+ ALTER ROLE pseudo_admin CREATEROLE;
+ DROP USER testuser2;
+ DROP USER testuser3;
+ GRANT pseudo_admin TO testuser
+
+user testuser
+
+# By default, a new role does not have privilege SETPASSWORD.
+
+statement ok
+CREATE USER testuser2
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE USER testuser3 WITH PASSWORD 'abc'
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER USER testuser2 WITH PASSWORD 'abc'
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER USER testuser2 VALID UNTIL '2021-01-01'
+
+statement ok
+CREATE ROLE otherrole
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER ROLE otherrole SETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER ROLE otherrole NOSETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE ROLE otherrole2 SETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE ROLE otherrole2 NOSETPASSWORD
+
+user root
+
+statement ok
+ALTER ROLE pseudo_admin SETPASSWORD
+
+user testuser
+
+statement ok
+CREATE USER testuser3 WITH PASSWORD 'abc'
+
+statement ok
+ALTER USER testuser3 WITH PASSWORD 'xyz'
+
+statement ok
+ALTER USER testuser3 VALID UNTIL '2021-01-01'
+
+statement ok
+ALTER ROLE otherrole SETPASSWORD
+
+statement ok
+ALTER ROLE otherrole NOSETPASSWORD
+
+statement ok
+CREATE ROLE otherrole2 SETPASSWORD
+
+statement ok
+CREATE ROLE otherrole3 NOSETPASSWORD
+
+# If SETPASSWORD is revoked, the changes are prevented again.
+
+user root
+
+statement ok
+ALTER ROLE pseudo_admin NOSETPASSWORD
+
+user testuser
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE USER testuser4 WITH PASSWORD 'abc'
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER USER testuser2 WITH PASSWORD 'abc'
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER USER testuser2 VALID UNTIL '2021-01-01'
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER ROLE otherrole SETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+ALTER ROLE otherrole NOSETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE ROLE otherrole4 SETPASSWORD
+
+statement error user testuser does not have SETPASSWORD privilege
+CREATE ROLE otherrole4 NOSETPASSWORD
diff --git a/pkg/sql/logictest/testdata/logic_test/show_source b/pkg/sql/logictest/testdata/logic_test/show_source
index 9eeab31a58e0..bfaaa80c0cc1 100644
--- a/pkg/sql/logictest/testdata/logic_test/show_source
+++ b/pkg/sql/logictest/testdata/logic_test/show_source
@@ -335,10 +335,10 @@ v CREATE VIEW v (id) AS SELECT id FROM system.public.descriptor
query TTT colnames
SELECT * FROM [SHOW USERS] ORDER BY 1
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
query TTTI colnames
diff --git a/pkg/sql/logictest/testdata/logic_test/user b/pkg/sql/logictest/testdata/logic_test/user
index 2e7333f6b289..a6ca00d5cb1c 100644
--- a/pkg/sql/logictest/testdata/logic_test/user
+++ b/pkg/sql/logictest/testdata/logic_test/user
@@ -3,10 +3,10 @@
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
statement ok
CREATE USER user1
@@ -14,11 +14,11 @@ CREATE USER user1
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-root CREATEROLE {admin}
-testuser · {}
-user1 · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user1 · {}
statement error pgcode 42710 a role/user named admin already exists
CREATE USER admin
@@ -79,16 +79,16 @@ EXECUTE chpw('blix', 'blah')
query TTT colnames
SHOW USERS
----
-username options member_of
-admin CREATEROLE {}
-foo · {}
-foo-bar · {}
-root CREATEROLE {admin}
-testuser · {}
-user1 · {}
-user2 · {}
-user3 · {}
-ομηρος · {}
+username options member_of
+admin CREATEROLE, SETPASSWORD {}
+foo · {}
+foo-bar · {}
+root CREATEROLE, SETPASSWORD {admin}
+testuser · {}
+user1 · {}
+user2 · {}
+user3 · {}
+ομηρος · {}
statement error no username specified
CREATE USER ""
@@ -151,11 +151,13 @@ ALTER USER user4 NOLOGIN
query TTT
SELECT * FROM system.role_options
----
-admin CREATEROLE NULL
-root CREATEROLE NULL
-testuser CREATEROLE NULL
-user4 CREATEROLE NULL
-user4 NOLOGIN NULL
+admin CREATEROLE NULL
+admin SETPASSWORD NULL
+root CREATEROLE NULL
+root SETPASSWORD NULL
+testuser CREATEROLE NULL
+user4 CREATEROLE NULL
+user4 NOLOGIN NULL
statement ok
DROP USER user4
diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go
index 6c895742af8e..e7cd42b7c2c0 100644
--- a/pkg/sql/parser/parse_test.go
+++ b/pkg/sql/parser/parse_test.go
@@ -2174,6 +2174,10 @@ $function$`,
`ALTER ROLE 'foo' WITH CREATEROLE`},
{`ALTER ROLE foo CREATEROLE`,
`ALTER ROLE 'foo' WITH CREATEROLE`},
+ {`ALTER ROLE foo SETPASSWORD`,
+ `ALTER ROLE 'foo' WITH SETPASSWORD`},
+ {`ALTER ROLE foo NOSETPASSWORD`,
+ `ALTER ROLE 'foo' WITH NOSETPASSWORD`},
{`DROP ROLE foo, bar`,
`DROP ROLE 'foo', 'bar'`},
{`DROP ROLE IF EXISTS foo, bar`,
diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y
index bf36fd386003..e30cfd1b6511 100644
--- a/pkg/sql/parser/sql.y
+++ b/pkg/sql/parser/sql.y
@@ -600,7 +600,7 @@ func (u *sqlSymUnion) alterTypeAddValuePlacement() *tree.AlterTypeAddValuePlacem
%token MULTILINESTRING MULTIPOINT MULTIPOLYGON
%token NAN NAME NAMES NATURAL NEXT NO NOCREATEROLE NOLOGIN NO_INDEX_JOIN
-%token NONE NORMAL NOT NOTHING NOTNULL NOWAIT NULL NULLIF NULLS NUMERIC
+%token NONE NORMAL NOSETPASSWORD NOT NOTHING NOTNULL NOWAIT NULL NULLIF NULLS NUMERIC
%token OF OFF OFFSET OID OIDS OIDVECTOR ON ONLY OPT OPTION OPTIONS OR
%token ORDER ORDINALITY OTHERS OUT OUTER OVER OVERLAPS OVERLAY OWNED OWNER OPERATOR
@@ -618,7 +618,7 @@ func (u *sqlSymUnion) alterTypeAddValuePlacement() *tree.AlterTypeAddValuePlacem
%token ROLE ROLES ROLLBACK ROLLUP ROW ROWS RSHIFT RULE
%token SAVEPOINT SCATTER SCHEMA SCHEMAS SCRUB SEARCH SECOND SELECT SEQUENCE SEQUENCES
-%token SERIALIZABLE SERVER SESSION SESSIONS SESSION_USER SET SETTING SETTINGS
+%token SERIALIZABLE SERVER SESSION SESSIONS SESSION_USER SET SETPASSWORD SETTING SETTINGS
%token SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SMALLSERIAL SNAPSHOT SOME SPLIT SQL
%token START STATISTICS STATUS STDIN STRICT STRING STORAGE STORE STORED STORING SUBSTRING
@@ -5573,17 +5573,25 @@ role_option:
$$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
}
| NOCREATEROLE
- {
- $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
- }
+ {
+ $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
+ }
| LOGIN
- {
- $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
- }
+ {
+ $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
+ }
| NOLOGIN
- {
- $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
- }
+ {
+ $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
+ }
+| SETPASSWORD
+ {
+ $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
+ }
+| NOSETPASSWORD
+ {
+ $$.val = tree.KVOption{Key: tree.Name($1), Value: nil}
+ }
| password_clause
| valid_until_clause
@@ -10368,6 +10376,7 @@ unreserved_keyword:
| NO_INDEX_JOIN
| NOCREATEROLE
| NOLOGIN
+| NOSETPASSWORD
| NOWAIT
| NULLS
| IGNORE_FOREIGN_KEYS
@@ -10421,6 +10430,7 @@ unreserved_keyword:
| ROLLUP
| ROWS
| RULE
+| SETPASSWORD
| SETTING
| SETTINGS
| STATUS
diff --git a/pkg/sql/pgwire/pgwire_test.go b/pkg/sql/pgwire/pgwire_test.go
index ebe7ee22d6d2..cc256b76ddb6 100644
--- a/pkg/sql/pgwire/pgwire_test.go
+++ b/pkg/sql/pgwire/pgwire_test.go
@@ -570,8 +570,10 @@ func TestPGPreparedQuery(t *testing.T) {
baseTest.SetArgs("woo", "waa"),
}},
{"SHOW USERS", []preparedQueryTest{
- baseTest.Results("abc", "", "{}").Results("admin", "CREATEROLE", "{}").
- Results("root", "CREATEROLE", "{admin}").Results("woo", "", "{}"),
+ baseTest.Results("abc", "", "{}").
+ Results("admin", "CREATEROLE, SETPASSWORD", "{}").
+ Results("root", "CREATEROLE, SETPASSWORD", "{admin}").
+ Results("woo", "", "{}"),
}},
{"DROP USER $1", []preparedQueryTest{
baseTest.SetArgs("abc"),
diff --git a/pkg/sql/roleoption/option_string.go b/pkg/sql/roleoption/option_string.go
index efc2f84f8b3c..11e3e9863459 100644
--- a/pkg/sql/roleoption/option_string.go
+++ b/pkg/sql/roleoption/option_string.go
@@ -14,11 +14,13 @@ func _() {
_ = x[LOGIN-4]
_ = x[NOLOGIN-5]
_ = x[VALIDUNTIL-6]
+ _ = x[SETPASSWORD-7]
+ _ = x[NOSETPASSWORD-8]
}
-const _Option_name = "CREATEROLENOCREATEROLEPASSWORDLOGINNOLOGINVALIDUNTIL"
+const _Option_name = "CREATEROLENOCREATEROLEPASSWORDLOGINNOLOGINVALIDUNTILSETPASSWORDNOSETPASSWORD"
-var _Option_index = [...]uint8{0, 10, 22, 30, 35, 42, 52}
+var _Option_index = [...]uint8{0, 10, 22, 30, 35, 42, 52, 63, 76}
func (i Option) String() string {
i -= 1
diff --git a/pkg/sql/roleoption/role_option.go b/pkg/sql/roleoption/role_option.go
index 82402d8a6082..689f166b8c28 100644
--- a/pkg/sql/roleoption/role_option.go
+++ b/pkg/sql/roleoption/role_option.go
@@ -42,16 +42,20 @@ const (
LOGIN
NOLOGIN
VALIDUNTIL
+ SETPASSWORD
+ NOSETPASSWORD
)
// toSQLStmts is a map of Kind -> SQL statement string for applying the
// option to the role.
var toSQLStmts = map[Option]string{
- CREATEROLE: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'CREATEROLE')`,
- NOCREATEROLE: `DELETE FROM system.role_options WHERE username = $1 AND option = 'CREATEROLE'`,
- LOGIN: `DELETE FROM system.role_options WHERE username = $1 AND option = 'NOLOGIN'`,
- NOLOGIN: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'NOLOGIN')`,
- VALIDUNTIL: `UPSERT INTO system.role_options (username, option, value) VALUES ($1, 'VALID UNTIL', $2::timestamptz::string)`,
+ CREATEROLE: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'CREATEROLE')`,
+ NOCREATEROLE: `DELETE FROM system.role_options WHERE username = $1 AND option = 'CREATEROLE'`,
+ LOGIN: `DELETE FROM system.role_options WHERE username = $1 AND option = 'NOLOGIN'`,
+ NOLOGIN: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'NOLOGIN')`,
+ SETPASSWORD: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'SETPASSWORD')`,
+ NOSETPASSWORD: `DELETE FROM system.role_options WHERE username = $1 AND option = 'SETPASSWORD'`,
+ VALIDUNTIL: `UPSERT INTO system.role_options (username, option, value) VALUES ($1, 'VALID UNTIL', $2::timestamptz::string)`,
}
// Mask returns the bitmask for a given role option.
@@ -61,12 +65,14 @@ func (o Option) Mask() uint32 {
// ByName is a map of string -> kind value.
var ByName = map[string]Option{
- "CREATEROLE": CREATEROLE,
- "NOCREATEROLE": NOCREATEROLE,
- "PASSWORD": PASSWORD,
- "LOGIN": LOGIN,
- "NOLOGIN": NOLOGIN,
- "VALID_UNTIL": VALIDUNTIL,
+ "CREATEROLE": CREATEROLE,
+ "NOCREATEROLE": NOCREATEROLE,
+ "PASSWORD": PASSWORD,
+ "LOGIN": LOGIN,
+ "NOLOGIN": NOLOGIN,
+ "SETPASSWORD": SETPASSWORD,
+ "NOSETPASSWORD": NOSETPASSWORD,
+ "VALID_UNTIL": VALIDUNTIL,
}
// ToOption takes a string and returns the corresponding Option.
@@ -152,10 +158,9 @@ func (rol List) CheckRoleOptionConflicts() error {
return err
}
- if (roleOptionBits&CREATEROLE.Mask() != 0 &&
- roleOptionBits&NOCREATEROLE.Mask() != 0) ||
- (roleOptionBits&LOGIN.Mask() != 0 &&
- roleOptionBits&NOLOGIN.Mask() != 0) {
+ if (roleOptionBits&CREATEROLE.Mask() != 0 && roleOptionBits&NOCREATEROLE.Mask() != 0) ||
+ (roleOptionBits&SETPASSWORD.Mask() != 0 && roleOptionBits&NOSETPASSWORD.Mask() != 0) ||
+ (roleOptionBits&LOGIN.Mask() != 0 && roleOptionBits&NOLOGIN.Mask() != 0) {
return pgerror.Newf(pgcode.Syntax, "conflicting role options")
}
return nil
diff --git a/pkg/sqlmigrations/migrations.go b/pkg/sqlmigrations/migrations.go
index 59ebfe1e6c6a..449687415f2f 100644
--- a/pkg/sqlmigrations/migrations.go
+++ b/pkg/sqlmigrations/migrations.go
@@ -317,7 +317,7 @@ var backwardCompatibleMigrations = []migrationDescriptor{
{
// Introduced in v20.1.
name: "add CREATEROLE privilege to admin/root",
- workFn: addCreateRoleToAdminAndRoot,
+ workFn: func(ctx context.Context, r runner) error { return addOptionToAdminAndRoot(ctx, r, "CREATEROLE") },
},
{
// Introduced in v20.2.
@@ -333,6 +333,11 @@ var backwardCompatibleMigrations = []migrationDescriptor{
includedInBootstrap: clusterversion.VersionByKey(clusterversion.VersionAddScheduledJobsTable),
newDescriptorIDs: staticIDs(keys.ScheduledJobsTableID),
},
+ {
+ // Introduced in v20.2.
+ name: "add SETPASSWORD privilege to admin/root",
+ workFn: func(ctx context.Context, r runner) error { return addOptionToAdminAndRoot(ctx, r, "SETPASSWORD") },
+ },
}
func staticIDs(
@@ -1587,25 +1592,29 @@ func createRoleOptionsTable(ctx context.Context, r runner) error {
return nil
}
-func addCreateRoleToAdminAndRoot(ctx context.Context, r runner) error {
+func addOptionToAdminAndRoot(ctx context.Context, r runner, option string) error {
// Upsert the admin/root roles with CreateRole privilege into the table.
// We intentionally override any existing entry.
const upsertCreateRoleStmt = `
- UPSERT INTO system.role_options (username, option, value) VALUES ($1, 'CREATEROLE', NULL)
+ UPSERT INTO system.role_options (username, option, value) VALUES ($1, $2, NULL)
`
err := r.execAsRootWithRetry(ctx,
- "add role options table and upsert admin with CREATEROLE",
+ "add role options table and upsert admin with "+option,
upsertCreateRoleStmt,
- sqlbase.AdminRole)
+ sqlbase.AdminRole,
+ option,
+ )
if err != nil {
return err
}
return r.execAsRootWithRetry(ctx,
- "add role options table and upsert admin with CREATEROLE",
+ "add role options table and upsert admin with "+option,
upsertCreateRoleStmt,
- security.RootUser)
+ security.RootUser,
+ option,
+ )
}
func createReportsMetaTable(ctx context.Context, r runner) error {