diff --git a/docs/generated/settings/settings-for-tenants.txt b/docs/generated/settings/settings-for-tenants.txt index 2e534d59dee7..d26e72bb2eac 100644 --- a/docs/generated/settings/settings-for-tenants.txt +++ b/docs/generated/settings/settings-for-tenants.txt @@ -167,4 +167,4 @@ trace.debug.enable boolean false if set, traces for recent requests can be seen trace.jaeger.agent string the address of a Jaeger agent to receive traces using the Jaeger UDP Thrift protocol, as :. If no port is specified, 6381 will be used. trace.opentelemetry.collector string address of an OpenTelemetry trace collector to receive traces using the otel gRPC protocol, as :. If no port is specified, 4317 will be used. trace.zipkin.collector string the address of a Zipkin instance to receive traces, as :. If no port is specified, 9411 will be used. -version version 21.2-12 set the active cluster version in the format '.' +version version 21.2-14 set the active cluster version in the format '.' diff --git a/docs/generated/settings/settings.html b/docs/generated/settings/settings.html index b58e9981d400..ffa56d38fee7 100644 --- a/docs/generated/settings/settings.html +++ b/docs/generated/settings/settings.html @@ -172,6 +172,6 @@ trace.jaeger.agentstringthe address of a Jaeger agent to receive traces using the Jaeger UDP Thrift protocol, as :. If no port is specified, 6381 will be used. trace.opentelemetry.collectorstringaddress of an OpenTelemetry trace collector to receive traces using the otel gRPC protocol, as :. If no port is specified, 4317 will be used. trace.zipkin.collectorstringthe address of a Zipkin instance to receive traces, as :. If no port is specified, 9411 will be used. -versionversion21.2-12set the active cluster version in the format '.' +versionversion21.2-14set the active cluster version in the format '.' diff --git a/docs/generated/sql/bnf/grant_stmt.bnf b/docs/generated/sql/bnf/grant_stmt.bnf index c532da5452f9..dc69eb2944ec 100644 --- a/docs/generated/sql/bnf/grant_stmt.bnf +++ b/docs/generated/sql/bnf/grant_stmt.bnf @@ -1,6 +1,6 @@ grant_stmt ::= - 'GRANT' 'ALL' 'PRIVILEGES' 'ON' targets 'TO' role_spec_list - | 'GRANT' 'ALL' 'ON' targets 'TO' role_spec_list - | 'GRANT' privilege_list 'ON' targets 'TO' role_spec_list + 'GRANT' 'ALL' 'PRIVILEGES' 'ON' targets 'TO' role_spec_list opt_with_grant_option + | 'GRANT' 'ALL' 'ON' targets 'TO' role_spec_list opt_with_grant_option + | 'GRANT' privilege_list 'ON' targets 'TO' role_spec_list opt_with_grant_option | 'GRANT' privilege_list 'TO' role_spec_list | 'GRANT' privilege_list 'TO' role_spec_list 'WITH' 'ADMIN' 'OPTION' diff --git a/docs/generated/sql/bnf/revoke_stmt.bnf b/docs/generated/sql/bnf/revoke_stmt.bnf index 4f23347ed806..a24e96e31624 100644 --- a/docs/generated/sql/bnf/revoke_stmt.bnf +++ b/docs/generated/sql/bnf/revoke_stmt.bnf @@ -2,5 +2,8 @@ revoke_stmt ::= 'REVOKE' 'ALL' 'PRIVILEGES' 'ON' targets 'FROM' role_spec_list | 'REVOKE' 'ALL' 'ON' targets 'FROM' role_spec_list | 'REVOKE' privilege_list 'ON' targets 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' 'ALL' 'PRIVILEGES' 'ON' targets 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' 'ALL' 'ON' targets 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' privilege_list 'ON' targets 'FROM' role_spec_list | 'REVOKE' privilege_list 'FROM' role_spec_list | 'REVOKE' 'ADMIN' 'OPTION' 'FOR' privilege_list 'FROM' role_spec_list diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index f95a931f3590..e6d71bb4d667 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -74,23 +74,27 @@ discard_stmt ::= 'DISCARD' 'ALL' grant_stmt ::= - 'GRANT' privileges 'ON' targets 'TO' role_spec_list + 'GRANT' privileges 'ON' targets 'TO' role_spec_list opt_with_grant_option | 'GRANT' privilege_list 'TO' role_spec_list | 'GRANT' privilege_list 'TO' role_spec_list 'WITH' 'ADMIN' 'OPTION' - | 'GRANT' privileges 'ON' 'TYPE' target_types 'TO' role_spec_list - | 'GRANT' privileges 'ON' 'SCHEMA' schema_name_list 'TO' role_spec_list - | 'GRANT' privileges 'ON' 'ALL' 'TABLES' 'IN' 'SCHEMA' schema_name_list 'TO' role_spec_list + | 'GRANT' privileges 'ON' 'TYPE' target_types 'TO' role_spec_list opt_with_grant_option + | 'GRANT' privileges 'ON' 'SCHEMA' schema_name_list 'TO' role_spec_list opt_with_grant_option + | 'GRANT' privileges 'ON' 'ALL' 'TABLES' 'IN' 'SCHEMA' schema_name_list 'TO' role_spec_list opt_with_grant_option prepare_stmt ::= 'PREPARE' table_alias_name prep_type_clause 'AS' preparable_stmt revoke_stmt ::= 'REVOKE' privileges 'ON' targets 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' privileges 'ON' targets 'FROM' role_spec_list | 'REVOKE' privilege_list 'FROM' role_spec_list | 'REVOKE' 'ADMIN' 'OPTION' 'FOR' privilege_list 'FROM' role_spec_list | 'REVOKE' privileges 'ON' 'TYPE' target_types 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' privileges 'ON' 'TYPE' target_types 'FROM' role_spec_list | 'REVOKE' privileges 'ON' 'SCHEMA' schema_name_list 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' privileges 'ON' 'SCHEMA' schema_name_list 'FROM' role_spec_list | 'REVOKE' privileges 'ON' 'ALL' 'TABLES' 'IN' 'SCHEMA' schema_name_list 'FROM' role_spec_list + | 'REVOKE' 'GRANT' 'OPTION' 'FOR' privileges 'ON' 'ALL' 'TABLES' 'IN' 'SCHEMA' schema_name_list 'FROM' role_spec_list savepoint_stmt ::= 'SAVEPOINT' name @@ -321,6 +325,10 @@ targets ::= role_spec_list ::= ( role_spec ) ( ( ',' role_spec ) )* +opt_with_grant_option ::= + 'WITH' 'GRANT' 'OPTION' + | + privilege_list ::= ( privilege ) ( ( ',' privilege ) )* @@ -2313,10 +2321,6 @@ alter_default_privileges_target_object ::= | 'TYPES' | 'SCHEMAS' -opt_with_grant_option ::= - 'WITH' 'GRANT' 'OPTION' - | - role_option ::= 'CREATEROLE' | 'NOCREATEROLE' diff --git a/pkg/ccl/importccl/import_table_creation.go b/pkg/ccl/importccl/import_table_creation.go index 5bcebfeb1825..5cad5966f397 100644 --- a/pkg/ccl/importccl/import_table_creation.go +++ b/pkg/ccl/importccl/import_table_creation.go @@ -79,6 +79,7 @@ func MakeTestingSimpleTableDescriptor( Privileges: descpb.NewPrivilegeDescriptor( security.PublicRoleName(), privilege.SchemaPrivileges, + privilege.List{}, security.RootUserName(), ), }).BuildCreatedMutableSchema() diff --git a/pkg/cli/testdata/doctor/test_recreate_zipdir b/pkg/cli/testdata/doctor/test_recreate_zipdir index 4ee812f633e6..105cddc706ec 100644 --- a/pkg/cli/testdata/doctor/test_recreate_zipdir +++ b/pkg/cli/testdata/doctor/test_recreate_zipdir @@ -8,20 +8,20 @@ SELECT crdb_internal.unsafe_delete_descriptor(id, true) FROM system.descriptor W SELECT crdb_internal.unsafe_delete_namespace_entry("parentID", "parentSchemaID", name, id) FROM system.namespace WHERE id >= 50; COMMIT; BEGIN; -SELECT crdb_internal.unsafe_upsert_descriptor(50, decode('12340a0964656661756c74646210321a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f7418012200280140004a00', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(50, decode('12380a0964656661756c74646210321a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f7418012200280140004a00', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(0, 0, 'defaultdb', 50, true); -SELECT crdb_internal.unsafe_upsert_descriptor(51, decode('12330a08706f73746772657310331a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f7418012200280140004a00', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(51, decode('12370a08706f73746772657310331a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f7418012200280140004a00', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(0, 0, 'postgres', 51, true); -SELECT crdb_internal.unsafe_upsert_descriptor(53, decode('0ae7040a0575736572731835203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d0807100018003007509308600020003000680070007800800100880100980100422a0a046e616d6510031a0d0807100018003007509308600020013000680070007800800100880100980100422d0a076164647265737310041a0d080710001800300750930860002001300068007000780080010088010098010042310a0b6372656469745f6361726410051a0d08071000180030075093086000200130006800700078008001008801009801004806525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800101880103980100b2013d0a077072696d61727910001a0269641a04636974791a046e616d651a07616464726573731a0b6372656469745f63617264200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200aa02270836100210041802180120352a11666b5f636974795f7265665f75736572733002380040004800aa02270837100210041802180120352a11666b5f636974795f7265665f75736572733002380040004800aa0227083a100110021802180120352a11666b5f636974795f7265665f75736572733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(53, decode('0aeb040a0575736572731835203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d0807100018003007509308600020003000680070007800800100880100980100422a0a046e616d6510031a0d0807100018003007509308600020013000680070007800800100880100980100422d0a076164647265737310041a0d080710001800300750930860002001300068007000780080010088010098010042310a0b6372656469745f6361726410051a0d08071000180030075093086000200130006800700078008001008801009801004806525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800101880103980100b2013d0a077072696d61727910001a0269641a04636974791a046e616d651a07616464726573731a0b6372656469745f63617264200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200aa02270836100210041802180120352a11666b5f636974795f7265665f75736572733002380040004800aa02270837100210041802180120352a11666b5f636974795f7265665f75736572733002380040004800aa0227083a100110021802180120352a11666b5f636974795f7265665f75736572733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'users', 53, true); -SELECT crdb_internal.unsafe_upsert_descriptor(54, decode('0a8a070a0876656869636c65731836203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d0807100018003007509308600020003000680070007800800100880100980100422a0a047479706510031a0d0807100018003007509308600020013000680070007800800100880100980100422e0a086f776e65725f696410041a0d080e10001800300050861760002001300068007000780080010088010098010042330a0d6372656174696f6e5f74696d6510051a0d080510001800300050da08600020013000680070007800800100880100980100422c0a0673746174757310061a0d080710001800300750930860002001300068007000780080010088010098010042360a1063757272656e745f6c6f636174696f6e10071a0d080710001800300750930860002001300068007000780080010088010098010042290a0365787410081a0d081210001800300050da1d6000200130006800700078008001008801009801004809525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a80010a2576656869636c65735f6175746f5f696e6465785f666b5f636974795f7265665f75736572731002180022046369747922086f776e65725f6964300230043801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060036a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800102880103980100b201650a077072696d61727910001a0269641a04636974791a04747970651a086f776e65725f69641a0d6372656174696f6e5f74696d651a067374617475731a1063757272656e745f6c6f636174696f6e1a03657874200120022003200420052006200720082800b80101c20100e80100f2010408001200f801008002009202009a0200a202270836100210041802180120352a11666b5f636974795f7265665f75736572733000380040004800aa02320837100310051802180120362a1c666b5f76656869636c655f636974795f7265665f76656869636c65733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(54, decode('0a8e070a0876656869636c65731836203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d0807100018003007509308600020003000680070007800800100880100980100422a0a047479706510031a0d0807100018003007509308600020013000680070007800800100880100980100422e0a086f776e65725f696410041a0d080e10001800300050861760002001300068007000780080010088010098010042330a0d6372656174696f6e5f74696d6510051a0d080510001800300050da08600020013000680070007800800100880100980100422c0a0673746174757310061a0d080710001800300750930860002001300068007000780080010088010098010042360a1063757272656e745f6c6f636174696f6e10071a0d080710001800300750930860002001300068007000780080010088010098010042290a0365787410081a0d081210001800300050da1d6000200130006800700078008001008801009801004809525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a80010a2576656869636c65735f6175746f5f696e6465785f666b5f636974795f7265665f75736572731002180022046369747922086f776e65725f6964300230043801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060036a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800102880103980100b201650a077072696d61727910001a0269641a04636974791a04747970651a086f776e65725f69641a0d6372656174696f6e5f74696d651a067374617475731a1063757272656e745f6c6f636174696f6e1a03657874200120022003200420052006200720082800b80101c20100e80100f2010408001200f801008002009202009a0200a202270836100210041802180120352a11666b5f636974795f7265665f75736572733000380040004800aa02320837100310051802180120362a1c666b5f76656869636c655f636974795f7265665f76656869636c65733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'vehicles', 54, true); -SELECT crdb_internal.unsafe_upsert_descriptor(55, decode('0a920a0a0572696465731837203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d080710001800300750930860002000300068007000780080010088010098010042320a0c76656869636c655f6369747910031a0d0807100018003007509308600020013000680070007800800100880100980100422e0a0872696465725f696410041a0d080e10001800300050861760002001300068007000780080010088010098010042300a0a76656869636c655f696410051a0d080e10001800300050861760002001300068007000780080010088010098010042330a0d73746172745f6164647265737310061a0d080710001800300750930860002001300068007000780080010088010098010042310a0b656e645f6164647265737310071a0d080710001800300750930860002001300068007000780080010088010098010042300a0a73746172745f74696d6510081a0d080510001800300050da08600020013000680070007800800100880100980100422e0a08656e645f74696d6510091a0d080510001800300050da08600020013000680070007800800100880100980100422d0a07726576656e7565100a1a0d08031002180a300050a40d600020013000680070007800800100880100980100480b525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a7d0a2272696465735f6175746f5f696e6465785f666b5f636974795f7265665f757365727310021800220463697479220872696465725f6964300230043801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a94010a2d72696465735f6175746f5f696e6465785f666b5f76656869636c655f636974795f7265665f76656869636c657310031800220c76656869636c655f63697479220a76656869636c655f69643003300538023801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060046a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800103880103980100a201380a1376656869636c655f63697479203d20636974791217636865636b5f76656869636c655f636974795f6369747918002802280330003800b2018a010a077072696d61727910001a0269641a04636974791a0c76656869636c655f636974791a0872696465725f69641a0a76656869636c655f69641a0d73746172745f616464726573731a0b656e645f616464726573731a0a73746172745f74696d651a08656e645f74696d651a07726576656e7565200120022003200420052006200720082009200a2800b80101c20100e80100f2010408001200f801008002009202009a0200a202270837100210041802180120352a11666b5f636974795f7265665f75736572733000380040004800a202320837100310051802180120362a1c666b5f76656869636c655f636974795f7265665f76656869636c65733000380040004800aa02270838100110021802180120372a11666b5f636974795f7265665f72696465733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(55, decode('0a960a0a0572696465731837203428013a0042280a02696410011a0d080e100018003000508617600020003000680070007800800100880100980100422a0a046369747910021a0d080710001800300750930860002000300068007000780080010088010098010042320a0c76656869636c655f6369747910031a0d0807100018003007509308600020013000680070007800800100880100980100422e0a0872696465725f696410041a0d080e10001800300050861760002001300068007000780080010088010098010042300a0a76656869636c655f696410051a0d080e10001800300050861760002001300068007000780080010088010098010042330a0d73746172745f6164647265737310061a0d080710001800300750930860002001300068007000780080010088010098010042310a0b656e645f6164647265737310071a0d080710001800300750930860002001300068007000780080010088010098010042300a0a73746172745f74696d6510081a0d080510001800300050da08600020013000680070007800800100880100980100422e0a08656e645f74696d6510091a0d080510001800300050da08600020013000680070007800800100880100980100422d0a07726576656e7565100a1a0d08031002180a300050a40d600020013000680070007800800100880100980100480b525a0a077072696d617279100118012204636974792202696430023001400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a7d0a2272696465735f6175746f5f696e6465785f666b5f636974795f7265665f757365727310021800220463697479220872696465725f6964300230043801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c001005a94010a2d72696465735f6175746f5f696e6465785f666b5f76656869636c655f636974795f7265665f76656869636c657310031800220c76656869636c655f63697479220a76656869636c655f69643003300538023801400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060046a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800103880103980100a201380a1376656869636c655f63697479203d20636974791217636865636b5f76656869636c655f636974795f6369747918002802280330003800b2018a010a077072696d61727910001a0269641a04636974791a0c76656869636c655f636974791a0872696465725f69641a0a76656869636c655f69641a0d73746172745f616464726573731a0b656e645f616464726573731a0a73746172745f74696d651a08656e645f74696d651a07726576656e7565200120022003200420052006200720082009200a2800b80101c20100e80100f2010408001200f801008002009202009a0200a202270837100210041802180120352a11666b5f636974795f7265665f75736572733000380040004800a202320837100310051802180120362a1c666b5f76656869636c655f636974795f7265665f76656869636c65733000380040004800aa02270838100110021802180120372a11666b5f636974795f7265665f72696465733002380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'rides', 55, true); -SELECT crdb_internal.unsafe_upsert_descriptor(56, decode('0aba040a1a76656869636c655f6c6f636174696f6e5f686973746f726965731838203428013a00422a0a046369747910011a0d0807100018003007509308600020003000680070007800800100880100980100422d0a07726964655f696410021a0d080e100018003000508617600020003000680070007800800100880100980100422f0a0974696d657374616d7010031a0d080510001800300050da0860002000300068007000780080010088010098010042290a036c617410041a0d080210401800300050bd05600020013000680070007800800100880100980100422a0a046c6f6e6710051a0d080210401800300050bd056000200130006800700078008001008801009801004806526e0a077072696d617279100118012204636974792207726964655f6964220974696d657374616d703001300230034000400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800102880103980100b2013c0a077072696d61727910001a04636974791a07726964655f69641a0974696d657374616d701a036c61741a046c6f6e67200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200a202270838100110021802180120372a11666b5f636974795f7265665f72696465733000380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(56, decode('0abe040a1a76656869636c655f6c6f636174696f6e5f686973746f726965731838203428013a00422a0a046369747910011a0d0807100018003007509308600020003000680070007800800100880100980100422d0a07726964655f696410021a0d080e100018003000508617600020003000680070007800800100880100980100422f0a0974696d657374616d7010031a0d080510001800300050da0860002000300068007000780080010088010098010042290a036c617410041a0d080210401800300050bd05600020013000680070007800800100880100980100422a0a046c6f6e6710051a0d080210401800300050bd056000200130006800700078008001008801009801004806526e0a077072696d617279100118012204636974792207726964655f6964220974696d657374616d703001300230034000400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800102880103980100b2013c0a077072696d61727910001a04636974791a07726964655f69641a0974696d657374616d701a036c61741a046c6f6e67200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200a202270838100110021802180120372a11666b5f636974795f7265665f72696465733000380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'vehicle_location_histories', 56, true); -SELECT crdb_internal.unsafe_upsert_descriptor(57, decode('0a8f040a0b70726f6d6f5f636f6465731839203428013a00422a0a04636f646510011a0d080710001800300750930860002000300068007000780080010088010098010042310a0b6465736372697074696f6e10021a0d080710001800300750930860002001300068007000780080010088010098010042330a0d6372656174696f6e5f74696d6510031a0d080510001800300050da0860002001300068007000780080010088010098010042350a0f65787069726174696f6e5f74696d6510041a0d080510001800300050da08600020013000680070007800800100880100980100422b0a0572756c657310051a0d081210001800300050da1d600020013000680070007800800100880100980100480652520a077072696d617279100118012204636f6465300140004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800101880103980100b201510a077072696d61727910001a04636f64651a0b6465736372697074696f6e1a0d6372656174696f6e5f74696d651a0f65787069726174696f6e5f74696d651a0572756c6573200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(57, decode('0a93040a0b70726f6d6f5f636f6465731839203428013a00422a0a04636f646510011a0d080710001800300750930860002000300068007000780080010088010098010042310a0b6465736372697074696f6e10021a0d080710001800300750930860002001300068007000780080010088010098010042330a0d6372656174696f6e5f74696d6510031a0d080510001800300050da0860002001300068007000780080010088010098010042350a0f65787069726174696f6e5f74696d6510041a0d080510001800300050da08600020013000680070007800800100880100980100422b0a0572756c657310051a0d081210001800300050da1d600020013000680070007800800100880100980100480652520a077072696d617279100118012204636f6465300140004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800101880103980100b201510a077072696d61727910001a04636f64651a0b6465736372697074696f6e1a0d6372656174696f6e5f74696d651a0f65787069726174696f6e5f74696d651a0572756c6573200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'promo_codes', 57, true); -SELECT crdb_internal.unsafe_upsert_descriptor(58, decode('0aba040a10757365725f70726f6d6f5f636f646573183a203428013a00422a0a046369747910011a0d0807100018003007509308600020003000680070007800800100880100980100422d0a07757365725f696410021a0d080e100018003000508617600020003000680070007800800100880100980100422a0a04636f646510031a0d0807100018003007509308600020003000680070007800800100880100980100422f0a0974696d657374616d7010041a0d080510001800300050da0860002001300068007000780080010088010098010042300a0b75736167655f636f756e7410051a0c08011040180030005014600020013000680070007800800100880100980100480652690a077072696d617279100118012204636974792207757365725f69642204636f64653001300230034000400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a1d0a090a0561646d696e10020a080a04726f6f7410021204726f6f741801800102880103980100b201440a077072696d61727910001a04636974791a07757365725f69641a04636f64651a0974696d657374616d701a0b75736167655f636f756e74200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200a20227083a100110021802180120352a11666b5f636974795f7265665f75736572733000380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); +SELECT crdb_internal.unsafe_upsert_descriptor(58, decode('0abe040a10757365725f70726f6d6f5f636f646573183a203428013a00422a0a046369747910011a0d0807100018003007509308600020003000680070007800800100880100980100422d0a07757365725f696410021a0d080e100018003000508617600020003000680070007800800100880100980100422a0a04636f646510031a0d0807100018003007509308600020003000680070007800800100880100980100422f0a0974696d657374616d7010041a0d080510001800300050da0860002001300068007000780080010088010098010042300a0b75736167655f636f756e7410051a0c08011040180030005014600020013000680070007800800100880100980100480652690a077072696d617279100118012204636974792207757365725f69642204636f64653001300230034000400040004a10080010001a00200028003000380040005a007a0408002000800100880100900101980100a20106080012001800a80100b20100ba0100c0010060026a210a0b0a0561646d696e100218000a0a0a04726f6f74100218001204726f6f741801800102880103980100b201440a077072696d61727910001a04636974791a07757365725f69641a04636f64651a0974696d657374616d701a0b75736167655f636f756e74200120022003200420052800b80101c20100e80100f2010408001200f801008002009202009a0200a20227083a100110021802180120352a11666b5f636974795f7265665f75736572733000380040004800b20200b80200c0021dc80200e00200f00200', 'hex'), true); SELECT crdb_internal.unsafe_upsert_namespace_entry(52, 29, 'user_promo_codes', 58, true); COMMIT; diff --git a/pkg/clusterversion/cockroach_versions.go b/pkg/clusterversion/cockroach_versions.go index e3eedde9ae03..fc84ca0d45fa 100644 --- a/pkg/clusterversion/cockroach_versions.go +++ b/pkg/clusterversion/cockroach_versions.go @@ -286,6 +286,10 @@ const ( // table system.table_statistics that contains a new statistic. AlterSystemTableStatisticsAddAvgSizeCol + // ValidateGrantOption checks whether the current user granting privileges to + // another user holds the grant option for those privileges + ValidateGrantOption + // ************************************************* // Step (1): Add new versions here. // Do not add new versions to a patch release. @@ -490,6 +494,10 @@ var versionsSingleton = keyedVersions{ Key: AlterSystemTableStatisticsAddAvgSizeCol, Version: roachpb.Version{Major: 21, Minor: 2, Internal: 12}, }, + { + Key: ValidateGrantOption, + Version: roachpb.Version{Major: 21, Minor: 2, Internal: 14}, + }, // ************************************************* // Step (2): Add new versions here. diff --git a/pkg/clusterversion/key_string.go b/pkg/clusterversion/key_string.go index 9714b56f136d..ab083d3b3b0b 100644 --- a/pkg/clusterversion/key_string.go +++ b/pkg/clusterversion/key_string.go @@ -49,11 +49,12 @@ func _() { _ = x[DrainingNamesMigration-38] _ = x[TraceIDDoesntImplyStructuredRecording-39] _ = x[AlterSystemTableStatisticsAddAvgSizeCol-40] + _ = x[ValidateGrantOption-41] } -const _Key_name = "V21_1Start21_1PLUSStart21_2JoinTokensTableAcquisitionTypeInLeaseHistorySerializeViewUDTsExpressionIndexesDeleteDeprecatedNamespaceTableDescriptorMigrationFixDescriptorsDatabaseRoleSettingsTenantUsageTableSQLInstancesTableNewRetryableRangefeedErrorsAlterSystemWebSessionsCreateIndexesSeparatedIntentsMigrationPostSeparatedIntentsMigrationRetryJobsWithExponentialBackoffRecordsBasedRegistryAutoSpanConfigReconciliationJobDefaultPrivilegesZonesTableForSecondaryTenantsUseKeyEncodeForHashShardedIndexesDatabasePlacementPolicyGeneratedAsIdentityOnUpdateExpressionsSpanConfigurationsTableBoundedStalenessDateAndIntervalStylePebbleFormatVersionedMarkerDataKeysRegistryPebbleSetWithDeleteTenantUsageSingleConsumptionColumnSQLStatsTablesSQLStatsCompactionScheduledJobV21_2Start22_1TargetBytesAvoidExcessAvoidDrainingNamesDrainingNamesMigrationTraceIDDoesntImplyStructuredRecordingAlterSystemTableStatisticsAddAvgSizeCol" +const _Key_name = "V21_1Start21_1PLUSStart21_2JoinTokensTableAcquisitionTypeInLeaseHistorySerializeViewUDTsExpressionIndexesDeleteDeprecatedNamespaceTableDescriptorMigrationFixDescriptorsDatabaseRoleSettingsTenantUsageTableSQLInstancesTableNewRetryableRangefeedErrorsAlterSystemWebSessionsCreateIndexesSeparatedIntentsMigrationPostSeparatedIntentsMigrationRetryJobsWithExponentialBackoffRecordsBasedRegistryAutoSpanConfigReconciliationJobDefaultPrivilegesZonesTableForSecondaryTenantsUseKeyEncodeForHashShardedIndexesDatabasePlacementPolicyGeneratedAsIdentityOnUpdateExpressionsSpanConfigurationsTableBoundedStalenessDateAndIntervalStylePebbleFormatVersionedMarkerDataKeysRegistryPebbleSetWithDeleteTenantUsageSingleConsumptionColumnSQLStatsTablesSQLStatsCompactionScheduledJobV21_2Start22_1TargetBytesAvoidExcessAvoidDrainingNamesDrainingNamesMigrationTraceIDDoesntImplyStructuredRecordingAlterSystemTableStatisticsAddAvgSizeColValidateGrantOption" -var _Key_index = [...]uint16{0, 5, 18, 27, 42, 71, 88, 105, 154, 168, 188, 204, 221, 248, 283, 308, 337, 368, 388, 419, 436, 465, 498, 521, 540, 559, 582, 598, 618, 639, 661, 680, 714, 728, 758, 763, 772, 794, 812, 834, 871, 910} +var _Key_index = [...]uint16{0, 5, 18, 27, 42, 71, 88, 105, 154, 168, 188, 204, 221, 248, 283, 308, 337, 368, 388, 419, 436, 465, 498, 521, 540, 559, 582, 598, 618, 639, 661, 680, 714, 728, 758, 763, 772, 794, 812, 834, 871, 910, 929} func (i Key) String() string { if i < 0 || i >= Key(len(_Key_index)-1) { diff --git a/pkg/sql/alter_default_privileges.go b/pkg/sql/alter_default_privileges.go index b6b739855cce..875534dc3f6c 100644 --- a/pkg/sql/alter_default_privileges.go +++ b/pkg/sql/alter_default_privileges.go @@ -93,10 +93,12 @@ func (n *alterDefaultPrivilegesNode) startExec(params runParams) error { privileges := n.n.Grant.Privileges grantees := n.n.Grant.Grantees objectType := n.n.Grant.Target + grantOption := n.n.Grant.WithGrantOption if !n.n.IsGrant { privileges = n.n.Revoke.Privileges grantees = n.n.Revoke.Grantees objectType = n.n.Revoke.Target + grantOption = n.n.Revoke.GrantOptionFor } granteeSQLUsernames, err := grantees.ToSQLUsernames(params.p.SessionData(), security.UsernameValidation) @@ -165,11 +167,11 @@ func (n *alterDefaultPrivilegesNode) startExec(params runParams) error { for _, role := range roles { if n.n.IsGrant { defaultPrivs.GrantDefaultPrivileges( - role, privileges, granteeSQLUsernames, objectType, + role, privileges, granteeSQLUsernames, objectType, grantOption, ) } else { defaultPrivs.RevokeDefaultPrivileges( - role, privileges, granteeSQLUsernames, objectType, + role, privileges, granteeSQLUsernames, objectType, grantOption, ) } diff --git a/pkg/sql/catalog/catprivilege/default_privilege.go b/pkg/sql/catalog/catprivilege/default_privilege.go index a52c022764ed..0fd823592674 100644 --- a/pkg/sql/catalog/catprivilege/default_privilege.go +++ b/pkg/sql/catalog/catprivilege/default_privilege.go @@ -71,6 +71,7 @@ func (d *Mutable) GrantDefaultPrivileges( privileges privilege.List, grantees []security.SQLUsername, targetObject tree.AlterDefaultPrivilegesTargetObject, + withGrantOption bool, ) { defaultPrivilegesForRole := d.defaultPrivilegeDescriptor.FindOrCreateUser(role) for _, grantee := range grantees { @@ -79,7 +80,7 @@ func (d *Mutable) GrantDefaultPrivileges( // special privilege cases into real privileges on the PrivilegeDescriptor. // foldPrivileges converts the real privileges back into flags. expandPrivileges(defaultPrivilegesForRole, role, &defaultPrivileges, targetObject) - defaultPrivileges.Grant(grantee, privileges) + defaultPrivileges.Grant(grantee, privileges, withGrantOption) foldPrivileges(defaultPrivilegesForRole, role, &defaultPrivileges, targetObject) defaultPrivilegesForRole.DefaultPrivilegesPerObject[targetObject] = defaultPrivileges } @@ -91,6 +92,7 @@ func (d *Mutable) RevokeDefaultPrivileges( privileges privilege.List, grantees []security.SQLUsername, targetObject tree.AlterDefaultPrivilegesTargetObject, + grantOptionFor bool, ) { defaultPrivilegesForRole := d.defaultPrivilegeDescriptor.FindOrCreateUser(role) for _, grantee := range grantees { @@ -99,7 +101,7 @@ func (d *Mutable) RevokeDefaultPrivileges( // special privilege cases into real privileges on the PrivilegeDescriptor. // foldPrivileges converts the real privileges back into flags. expandPrivileges(defaultPrivilegesForRole, role, &defaultPrivileges, targetObject) - defaultPrivileges.Revoke(grantee, privileges, targetObject.ToPrivilegeObjectType()) + defaultPrivileges.Revoke(grantee, privileges, targetObject.ToPrivilegeObjectType(), grantOptionFor) foldPrivileges(defaultPrivilegesForRole, role, &defaultPrivileges, targetObject) defaultPrivilegesForRole.DefaultPrivilegesPerObject[targetObject] = defaultPrivileges @@ -151,18 +153,22 @@ func (d *immutable) CreatePrivilegesFromDefaultPrivileges( // it as the case where the user has all privileges. defaultPrivilegesForCreatorRole := descpb.InitDefaultPrivilegesForRole(role) for _, user := range GetUserPrivilegesForObject(defaultPrivilegesForCreatorRole, targetObject) { - newPrivs.Grant( + granularGrant( + newPrivs, user.UserProto.Decode(), privilege.ListFromBitField(user.Privileges, targetObject.ToPrivilegeObjectType()), + privilege.ListFromBitField(user.WithGrantOption, targetObject.ToPrivilegeObjectType()), ) } } else { // If default privileges were defined for the role, we create privileges // using the default privileges. for _, user := range GetUserPrivilegesForObject(*defaultPrivilegesForRole, targetObject) { - newPrivs.Grant( + granularGrant( + newPrivs, user.UserProto.Decode(), privilege.ListFromBitField(user.Privileges, targetObject.ToPrivilegeObjectType()), + privilege.ListFromBitField(user.WithGrantOption, targetObject.ToPrivilegeObjectType()), ) } } @@ -173,9 +179,11 @@ func (d *immutable) CreatePrivilegesFromDefaultPrivileges( defaultPrivilegesForAllRoles, found := d.GetDefaultPrivilegesForRole(descpb.DefaultPrivilegesRole{ForAllRoles: true}) if found { for _, user := range GetUserPrivilegesForObject(*defaultPrivilegesForAllRoles, targetObject) { - newPrivs.Grant( + granularGrant( + newPrivs, user.UserProto.Decode(), privilege.ListFromBitField(user.Privileges, targetObject.ToPrivilegeObjectType()), + privilege.ListFromBitField(user.WithGrantOption, targetObject.ToPrivilegeObjectType()), ) } } @@ -187,11 +195,21 @@ func (d *immutable) CreatePrivilegesFromDefaultPrivileges( // Issue #67378. if targetObject == tree.Tables || targetObject == tree.Sequences { for _, u := range databasePrivileges.Users { - newPrivs.Grant(u.UserProto.Decode(), privilege.ListFromBitField(u.Privileges, privilege.Table)) + granularGrant( + newPrivs, + u.UserProto.Decode(), + privilege.ListFromBitField(u.Privileges, privilege.Table), + privilege.ListFromBitField(u.WithGrantOption, privilege.Table), + ) } } else if targetObject == tree.Schemas { for _, u := range databasePrivileges.Users { - newPrivs.Grant(u.UserProto.Decode(), privilege.ListFromBitField(u.Privileges, privilege.Schema)) + granularGrant( + newPrivs, + u.UserProto.Decode(), + privilege.ListFromBitField(u.Privileges, privilege.Schema), + privilege.ListFromBitField(u.WithGrantOption, privilege.Schema), + ) } } return newPrivs @@ -249,6 +267,7 @@ func foldPrivileges( security.PublicRoleName(), privilege.List{privilege.USAGE}, privilege.Type, + false, ) } // ForAllRoles cannot be a grantee, nothing left to do. @@ -256,8 +275,11 @@ func foldPrivileges( return } if privileges.HasAllPrivileges(role.Role, targetObject.ToPrivilegeObjectType()) { - setRoleHasAllOnTargetObject(defaultPrivilegesForRole, true, targetObject) - privileges.RemoveUser(role.Role) + user := privileges.FindOrCreateUser(role.Role) + if user.WithGrantOption == 0 { + setRoleHasAllOnTargetObject(defaultPrivilegesForRole, true, targetObject) + privileges.RemoveUser(role.Role) + } } } @@ -273,7 +295,7 @@ func expandPrivileges( targetObject tree.AlterDefaultPrivilegesTargetObject, ) { if targetObject == tree.Types && GetPublicHasUsageOnTypes(defaultPrivilegesForRole) { - privileges.Grant(security.PublicRoleName(), privilege.List{privilege.USAGE}) + privileges.Grant(security.PublicRoleName(), privilege.List{privilege.USAGE}, false) setPublicHasUsageOnTypes(defaultPrivilegesForRole, false) } // ForAllRoles cannot be a grantee, nothing left to do. @@ -281,7 +303,7 @@ func expandPrivileges( return } if GetRoleHasAllPrivilegesOnTargetObject(defaultPrivilegesForRole, targetObject) { - privileges.Grant(defaultPrivilegesForRole.GetExplicitRole().UserProto.Decode(), privilege.List{privilege.ALL}) + privileges.Grant(defaultPrivilegesForRole.GetExplicitRole().UserProto.Decode(), privilege.List{privilege.ALL}, false) setRoleHasAllOnTargetObject(defaultPrivilegesForRole, false, targetObject) } } @@ -297,8 +319,9 @@ func GetUserPrivilegesForObject( } if GetPublicHasUsageOnTypes(&p) && targetObject == tree.Types { userPrivileges = append(userPrivileges, descpb.UserPrivileges{ - UserProto: security.PublicRoleName().EncodeProto(), - Privileges: privilege.USAGE.Mask(), + UserProto: security.PublicRoleName().EncodeProto(), + Privileges: privilege.USAGE.Mask(), + WithGrantOption: privilege.USAGE.Mask(), }) } // If ForAllRoles is specified, we can return early. @@ -310,8 +333,9 @@ func GetUserPrivilegesForObject( userProto := p.GetExplicitRole().UserProto if GetRoleHasAllPrivilegesOnTargetObject(&p, targetObject) { return append(userPrivileges, descpb.UserPrivileges{ - UserProto: userProto, - Privileges: privilege.ALL.Mask(), + UserProto: userProto, + Privileges: privilege.ALL.Mask(), + WithGrantOption: privilege.ALL.Mask(), }) } return userPrivileges @@ -360,6 +384,42 @@ func setPublicHasUsageOnTypes( } } +// granularGrant adds new privileges to this descriptor and new grant options which +// could be different from the privileges. Unlike the normal grant, the privileges +// and the grant options being granted could be different +func granularGrant( + p *descpb.PrivilegeDescriptor, + user security.SQLUsername, + privList privilege.List, + grantOptionList privilege.List, +) { + userPriv := p.FindOrCreateUser(user) + if privilege.ALL.IsSetIn(userPriv.WithGrantOption) { + // User already has 'ALL' privilege: no-op. + // If userPriv.WithGrantOption has ALL, then userPriv.Privileges must also have ALL. + // It is possible however for userPriv.Privileges to have ALL but userPriv.WithGrantOption to not have ALL + return + } + + privBits := privList.ToBitField() + grantBits := grantOptionList.ToBitField() + if privilege.ALL.IsSetIn(privBits) { + userPriv.Privileges = privilege.ALL.Mask() + } else { + if !privilege.ALL.IsSetIn(userPriv.Privileges) { + userPriv.Privileges |= privBits + } + } + + if privilege.ALL.IsSetIn(grantBits) { + userPriv.WithGrantOption = privilege.ALL.Mask() + } else { + if !privilege.ALL.IsSetIn(userPriv.WithGrantOption) { + userPriv.WithGrantOption |= grantBits + } + } +} + func setRoleHasAllOnTargetObject( defaultPrivilegesForRole *descpb.DefaultPrivilegesForRole, roleHasAll bool, diff --git a/pkg/sql/catalog/catprivilege/default_privilege_test.go b/pkg/sql/catalog/catprivilege/default_privilege_test.go index 9a98ba7dcd25..d810b3726e6f 100644 --- a/pkg/sql/catalog/catprivilege/default_privilege_test.go +++ b/pkg/sql/catalog/catprivilege/default_privilege_test.go @@ -164,7 +164,7 @@ func TestGrantDefaultPrivileges(t *testing.T) { defaultPrivilegeDescriptor := MakeNewDefaultPrivilegeDescriptor() defaultPrivileges := NewMutableDefaultPrivileges(defaultPrivilegeDescriptor) - defaultPrivileges.GrantDefaultPrivileges(tc.defaultPrivilegesRole, tc.privileges, tc.grantees, tc.targetObject) + defaultPrivileges.GrantDefaultPrivileges(tc.defaultPrivilegesRole, tc.privileges, tc.grantees, tc.targetObject, false) newPrivileges := defaultPrivileges.CreatePrivilegesFromDefaultPrivileges( nonSystemDatabaseID, tc.objectCreator, tc.targetObject, &descpb.PrivilegeDescriptor{}, @@ -282,8 +282,8 @@ func TestRevokeDefaultPrivileges(t *testing.T) { defaultPrivilegeDescriptor := MakeNewDefaultPrivilegeDescriptor() defaultPrivileges := NewMutableDefaultPrivileges(defaultPrivilegeDescriptor) - defaultPrivileges.GrantDefaultPrivileges(tc.defaultPrivilegesRole, tc.grantPrivileges, tc.grantees, tc.targetObject) - defaultPrivileges.RevokeDefaultPrivileges(tc.defaultPrivilegesRole, tc.revokePrivileges, tc.grantees, tc.targetObject) + defaultPrivileges.GrantDefaultPrivileges(tc.defaultPrivilegesRole, tc.grantPrivileges, tc.grantees, tc.targetObject, false) + defaultPrivileges.RevokeDefaultPrivileges(tc.defaultPrivilegesRole, tc.revokePrivileges, tc.grantees, tc.targetObject, false) newPrivileges := defaultPrivileges.CreatePrivilegesFromDefaultPrivileges( nonSystemDatabaseID, tc.objectCreator, tc.targetObject, &descpb.PrivilegeDescriptor{}, @@ -308,7 +308,7 @@ func TestRevokeDefaultPrivilegesFromEmptyList(t *testing.T) { fooUser := security.MakeSQLUsernameFromPreNormalizedString("foo") defaultPrivileges.RevokeDefaultPrivileges(descpb.DefaultPrivilegesRole{ Role: creatorUser, - }, privilege.List{privilege.ALL}, []security.SQLUsername{fooUser}, tree.Tables) + }, privilege.List{privilege.ALL}, []security.SQLUsername{fooUser}, tree.Tables, false) newPrivileges := defaultPrivileges.CreatePrivilegesFromDefaultPrivileges( nonSystemDatabaseID, creatorUser, tree.Tables, &descpb.PrivilegeDescriptor{}, @@ -534,7 +534,7 @@ func TestDefaultPrivileges(t *testing.T) { descpb.DefaultPrivilegesRole{Role: tc.defaultPrivilegesRole}, userAndGrant.grants, []security.SQLUsername{userAndGrant.user}, - tc.targetObject, + tc.targetObject, false, ) } @@ -595,7 +595,7 @@ func TestModifyDefaultDefaultPrivileges(t *testing.T) { descpb.DefaultPrivilegesRole{Role: creatorUser}, tc.revokeAndGrantPrivileges, []security.SQLUsername{creatorUser}, - tc.targetObject, + tc.targetObject, false, ) if GetRoleHasAllPrivilegesOnTargetObject(defaultPrivilegesForCreator, tc.targetObject) { t.Errorf("expected role to not have ALL privileges on %s", tc.targetObject) @@ -604,7 +604,7 @@ func TestModifyDefaultDefaultPrivileges(t *testing.T) { descpb.DefaultPrivilegesRole{Role: creatorUser}, tc.revokeAndGrantPrivileges, []security.SQLUsername{creatorUser}, - tc.targetObject, + tc.targetObject, false, ) if !GetRoleHasAllPrivilegesOnTargetObject(defaultPrivilegesForCreator, tc.targetObject) { t.Errorf("expected role to have ALL privileges on %s", tc.targetObject) @@ -628,7 +628,7 @@ func TestModifyDefaultDefaultPrivilegesForPublic(t *testing.T) { descpb.DefaultPrivilegesRole{Role: creatorUser}, privilege.List{privilege.USAGE}, []security.SQLUsername{security.PublicRoleName()}, - tree.Types, + tree.Types, false, ) if GetPublicHasUsageOnTypes(defaultPrivilegesForCreator) { t.Errorf("expected public to not have USAGE privilege on types") @@ -637,9 +637,70 @@ func TestModifyDefaultDefaultPrivilegesForPublic(t *testing.T) { descpb.DefaultPrivilegesRole{Role: creatorUser}, privilege.List{privilege.USAGE}, []security.SQLUsername{security.PublicRoleName()}, - tree.Types, + tree.Types, false, ) if !GetPublicHasUsageOnTypes(defaultPrivilegesForCreator) { t.Errorf("expected public to have USAGE privilege on types") } } + +// TestGranularGrant tests whether granting potentially different privileges and grant options +// changes the privilege bits and grant option bits as expected. +func TestGranularGrant(t *testing.T) { + defer leaktest.AfterTest(t)() + + testUser := security.TestUserName() + + testCases := []struct { + pd *descpb.PrivilegeDescriptor + user security.SQLUsername + objectType privilege.ObjectType + grantPrivileges privilege.List + grantGrantOptions privilege.List + expectedPrivileges privilege.List + expectedGrantOption privilege.List + }{ + {descpb.NewPrivilegeDescriptor(testUser, privilege.List{}, privilege.List{}, security.AdminRoleName()), + testUser, privilege.Table, + privilege.List{privilege.SELECT, privilege.INSERT}, + privilege.List{privilege.SELECT, privilege.INSERT}, + privilege.List{privilege.SELECT, privilege.INSERT}, + privilege.List{privilege.SELECT, privilege.INSERT}}, + {descpb.NewPrivilegeDescriptor(testUser, privilege.List{privilege.CREATE, privilege.INSERT}, privilege.List{privilege.INSERT}, security.AdminRoleName()), + testUser, privilege.Table, + privilege.List{privilege.ALL}, + privilege.List{privilege.CREATE, privilege.SELECT}, + privilege.List{privilege.ALL}, + privilege.List{privilege.CREATE, privilege.SELECT, privilege.INSERT}}, + {descpb.NewPrivilegeDescriptor(testUser, privilege.List{privilege.CREATE, privilege.INSERT}, privilege.List{privilege.CREATE}, security.AdminRoleName()), + testUser, privilege.Table, + privilege.List{privilege.ALL}, + privilege.List{privilege.ALL}, + privilege.List{privilege.ALL}, + privilege.List{privilege.ALL}}, + {descpb.NewPrivilegeDescriptor(testUser, privilege.List{privilege.CREATE}, privilege.List{privilege.CREATE}, security.AdminRoleName()), + testUser, privilege.Table, + privilege.List{privilege.CREATE, privilege.SELECT, privilege.INSERT, privilege.UPDATE}, + privilege.List{privilege.SELECT}, + privilege.List{privilege.CREATE, privilege.SELECT, privilege.INSERT, privilege.UPDATE}, + privilege.List{privilege.CREATE, privilege.SELECT}}, + {descpb.NewPrivilegeDescriptor(testUser, privilege.List{privilege.CREATE}, privilege.List{privilege.CREATE}, security.AdminRoleName()), + testUser, privilege.Table, + privilege.List{privilege.ALL, privilege.CREATE, privilege.SELECT, privilege.INSERT, privilege.UPDATE}, + privilege.List{privilege.ALL, privilege.SELECT}, + privilege.List{privilege.ALL}, + privilege.List{privilege.ALL}}, + } + + for tcNum, tc := range testCases { + granularGrant(tc.pd, tc.user, tc.grantPrivileges, tc.grantGrantOptions) + if tc.pd.Users[0].Privileges != tc.expectedPrivileges.ToBitField() { + t.Errorf("#%d: Incorrect privileges, returned %v, expected %v", + tcNum, privilege.ListFromBitField(tc.pd.Users[0].Privileges, tc.objectType), tc.expectedPrivileges) + } + if tc.pd.Users[0].WithGrantOption != tc.expectedGrantOption.ToBitField() { + t.Errorf("#%d: Incorrect grant option, returned %v, expected %v", + tcNum, privilege.ListFromBitField(tc.pd.Users[0].WithGrantOption, tc.objectType), tc.expectedGrantOption) + } + } +} diff --git a/pkg/sql/catalog/catprivilege/fix_test.go b/pkg/sql/catalog/catprivilege/fix_test.go index f55d7f3e993a..81fc56fc4402 100644 --- a/pkg/sql/catalog/catprivilege/fix_test.go +++ b/pkg/sql/catalog/catprivilege/fix_test.go @@ -141,7 +141,7 @@ func TestFixPrivileges(t *testing.T) { for num, testCase := range testCases { desc := &descpb.PrivilegeDescriptor{} for u, p := range testCase.input { - desc.Grant(u, p) + desc.Grant(u, p, false) } MaybeFixPrivileges( @@ -375,7 +375,7 @@ func TestMaybeFixUsageAndZoneConfigPrivilege(t *testing.T) { for num, tc := range testCases { desc := &descpb.PrivilegeDescriptor{Version: tc.privDescVersion} for u, p := range tc.input { - desc.Grant(u, p) + desc.Grant(u, p, false) } modified := MaybeFixUsagePrivForTablesAndDBs(&desc) @@ -482,7 +482,7 @@ func TestMaybeFixSchemaPrivileges(t *testing.T) { for num, tc := range testCases { desc := &descpb.PrivilegeDescriptor{} for u, p := range tc.input { - desc.Grant(u, p) + desc.Grant(u, p, false) } testParentID := descpb.ID(keys.MaxReservedDescID + 1) MaybeFixPrivileges(&desc, diff --git a/pkg/sql/catalog/descpb/privilege.go b/pkg/sql/catalog/descpb/privilege.go index 7fff2690bce9..b9937a72f0e3 100644 --- a/pkg/sql/catalog/descpb/privilege.go +++ b/pkg/sql/catalog/descpb/privilege.go @@ -18,6 +18,8 @@ import ( "github.com/cockroachdb/cockroach/pkg/keys" "github.com/cockroachdb/cockroach/pkg/security" "github.com/cockroachdb/cockroach/pkg/sql/catalog/catconstants" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" "github.com/cockroachdb/cockroach/pkg/sql/privilege" "github.com/cockroachdb/errors" ) @@ -113,10 +115,12 @@ func NewCustomSuperuserPrivilegeDescriptor( { UserProto: security.AdminRoleName().EncodeProto(), Privileges: priv.ToBitField(), + //WithGrantOption: priv.ToBitField(), }, { UserProto: security.RootUserName().EncodeProto(), Privileges: priv.ToBitField(), + //WithGrantOption: priv.ToBitField(), }, }, Version: Version21_2, @@ -128,21 +132,25 @@ func NewCustomSuperuserPrivilegeDescriptor( // used for virtual tables. func NewPublicSelectPrivilegeDescriptor() *PrivilegeDescriptor { return NewPrivilegeDescriptor( - security.PublicRoleName(), privilege.List{privilege.SELECT}, security.NodeUserName(), + security.PublicRoleName(), privilege.List{privilege.SELECT}, privilege.List{}, security.NodeUserName(), ) } // NewPrivilegeDescriptor returns a privilege descriptor for the given // user with the specified list of privileges. func NewPrivilegeDescriptor( - user security.SQLUsername, priv privilege.List, owner security.SQLUsername, + user security.SQLUsername, + priv privilege.List, + grantOption privilege.List, + owner security.SQLUsername, ) *PrivilegeDescriptor { return &PrivilegeDescriptor{ OwnerProto: owner.EncodeProto(), Users: []UserPrivileges{ { - UserProto: user.EncodeProto(), - Privileges: priv.ToBitField(), + UserProto: user.EncodeProto(), + Privileges: priv.ToBitField(), + WithGrantOption: grantOption.ToBitField(), }, }, Version: Version21_2, @@ -164,32 +172,85 @@ func NewBasePrivilegeDescriptor(owner security.SQLUsername) *PrivilegeDescriptor // Here we also add the CONNECT privilege for the database. func NewBaseDatabasePrivilegeDescriptor(owner security.SQLUsername) *PrivilegeDescriptor { p := NewBasePrivilegeDescriptor(owner) - p.Grant(security.PublicRoleName(), privilege.List{privilege.CONNECT}) + p.Grant(security.PublicRoleName(), privilege.List{privilege.CONNECT}, true) return p } +// ValidateGrantPrivileges returns an error if the current user tries to grant a privilege that +// it does not possess grant options for +func (p *PrivilegeDescriptor) ValidateGrantPrivileges( + user security.SQLUsername, privList privilege.List, +) error { + // Always allow the command to go through if performed by a superuser. + if user == security.RootUserName() || user == security.AdminRoleName() { + return nil + } + userPriv, exists := p.FindUser(user) + if !exists { + return nil + } + + // User has ALL WITH GRANT OPTION so they can grant anything. + if privilege.ALL.IsSetIn(userPriv.WithGrantOption) { + return nil + } + + for _, priv := range privList { + if userPriv.WithGrantOption&priv.Mask() == 0 { + return pgerror.Newf(pgcode.InvalidGrantOperation, + "missing WITH GRANT OPTION privilege type %s", priv.String()) + } + } + + return nil +} + // Grant adds new privileges to this descriptor for a given list of users. -func (p *PrivilegeDescriptor) Grant(user security.SQLUsername, privList privilege.List) { +func (p *PrivilegeDescriptor) Grant( + user security.SQLUsername, privList privilege.List, withGrantOption bool, +) { userPriv := p.FindOrCreateUser(user) - if privilege.ALL.IsSetIn(userPriv.Privileges) { + if privilege.ALL.IsSetIn(userPriv.WithGrantOption) { // User already has 'ALL' privilege: no-op. + // If userPriv.WithGrantOption has ALL, then userPriv.Privileges must also have ALL. + // It is possible however for userPriv.Privileges to have ALL but userPriv.WithGrantOption to not have ALL + return + } + + if privilege.ALL.IsSetIn(userPriv.Privileges) && !withGrantOption { + // A user can hold all privileges but not all grant options. + // If a user holds all privileges but withGrantOption is False, + // there is nothing left to be done return } + // We only have to modify grant option. bits := privList.ToBitField() if privilege.ALL.IsSetIn(bits) { // Granting 'ALL' privilege: overwrite. // TODO(marc): the grammar does not allow it, but we should // check if other privileges are being specified and error out. userPriv.Privileges = privilege.ALL.Mask() + if withGrantOption { + userPriv.WithGrantOption = privilege.ALL.Mask() + } return } - userPriv.Privileges |= bits + + if withGrantOption { + userPriv.WithGrantOption |= bits + } + if !privilege.ALL.IsSetIn(userPriv.Privileges) { + userPriv.Privileges |= bits + } } // Revoke removes privileges from this descriptor for a given list of users. func (p *PrivilegeDescriptor) Revoke( - user security.SQLUsername, privList privilege.List, objectType privilege.ObjectType, + user security.SQLUsername, + privList privilege.List, + objectType privilege.ObjectType, + grantOptionFor bool, ) { userPriv, ok := p.FindUser(user) if !ok || userPriv.Privileges == 0 { @@ -199,14 +260,17 @@ func (p *PrivilegeDescriptor) Revoke( bits := privList.ToBitField() if privilege.ALL.IsSetIn(bits) { - // Revoking 'ALL' privilege: remove user. - // TODO(marc): the grammar does not allow it, but we should - // check if other privileges are being specified and error out. - p.RemoveUser(user) + userPriv.WithGrantOption = 0 + if !grantOptionFor { + // Revoking 'ALL' privilege: remove user. + // TODO(marc): the grammar does not allow it, but we should + // check if other privileges are being specified and error out. + p.RemoveUser(user) + } return } - if privilege.ALL.IsSetIn(userPriv.Privileges) { + if privilege.ALL.IsSetIn(userPriv.Privileges) && !grantOptionFor { // User has 'ALL' privilege. Remove it and set // all other privileges one. validPrivs := privilege.GetValidPrivilegesForObject(objectType) @@ -218,12 +282,28 @@ func (p *PrivilegeDescriptor) Revoke( } } + if privilege.ALL.IsSetIn(userPriv.WithGrantOption) { + // User has 'ALL' grant option. Remove it and set + // all other grant options to one. + validPrivs := privilege.GetValidPrivilegesForObject(objectType) + userPriv.WithGrantOption = 0 + for _, v := range validPrivs { + if v != privilege.ALL { + userPriv.WithGrantOption |= v.Mask() + } + } + } + // One doesn't see "AND NOT" very often. - userPriv.Privileges &^= bits + userPriv.WithGrantOption &^= bits + if !grantOptionFor { + userPriv.Privileges &^= bits - if userPriv.Privileges == 0 { - p.RemoveUser(user) + if userPriv.Privileges == 0 { + p.RemoveUser(user) + } } + } // ValidateSuperuserPrivileges ensures that superusers have exactly the maximum diff --git a/pkg/sql/catalog/descpb/privilege.pb.go b/pkg/sql/catalog/descpb/privilege.pb.go index 819e558c5975..8faa36db5050 100644 --- a/pkg/sql/catalog/descpb/privilege.pb.go +++ b/pkg/sql/catalog/descpb/privilege.pb.go @@ -30,7 +30,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type UserPrivileges struct { UserProto github_com_cockroachdb_cockroach_pkg_security.SQLUsernameProto `protobuf:"bytes,1,opt,name=user_proto,json=userProto,casttype=github.com/cockroachdb/cockroach/pkg/security.SQLUsernameProto" json:"user_proto"` // privileges is a bitfield of 1<= 64 { + return ErrIntOverflowPrivilege + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.WithGrantOption |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipPrivilege(dAtA[iNdEx:]) diff --git a/pkg/sql/catalog/descpb/privilege.proto b/pkg/sql/catalog/descpb/privilege.proto index a5ac53137f29..538b88dc1686 100644 --- a/pkg/sql/catalog/descpb/privilege.proto +++ b/pkg/sql/catalog/descpb/privilege.proto @@ -21,6 +21,7 @@ message UserPrivileges { (gogoproto.casttype) = "github.com/cockroachdb/cockroach/pkg/security.SQLUsernameProto"]; // privileges is a bitfield of 1< 53 -batch flow coordinator CPut /Table/3/1/53/2/1 -> database: version:1 privileges: users: users: owner_proto:"root" version:2 > state:PUBLIC offline_reason:"" default_privileges:<> > +batch flow coordinator CPut /Table/3/1/53/2/1 -> database: version:1 privileges: users: users: owner_proto:"root" version:2 > state:PUBLIC offline_reason:"" default_privileges:<> > batch flow coordinator CPut /NamespaceTable/30/1/53/0/"public"/4/1 -> 29 exec stmt rows affected: 0 @@ -39,7 +39,7 @@ WHERE message NOT LIKE '%Z/%' AND operation != 'dist sender send' ---- batch flow coordinator CPut /NamespaceTable/30/1/53/29/"kv"/4/1 -> 54 -batch flow coordinator CPut /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<> temporary:false partition_all_by:false > +batch flow coordinator CPut /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<> temporary:false partition_all_by:false > exec stmt rows affected: 0 # We avoid using the full trace output, because that would make the @@ -65,7 +65,7 @@ WHERE message NOT LIKE '%Z/%' AND message NOT LIKE 'querying next range at%' AND tag NOT LIKE '%IndexBackfiller%' AND operation != 'dist sender send' ---- -batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > mutations: interleave:<> partitioning: type:FORWARD created_explicitly:true encoding_type:0 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > state:DELETE_ONLY direction:ADD mutation_id:1 rollback:false > next_mutation_id:2 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false mutationJobs:<...> new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > +batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > mutations: interleave:<> partitioning: type:FORWARD created_explicitly:true encoding_type:0 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > state:DELETE_ONLY direction:ADD mutation_id:1 rollback:false > next_mutation_id:2 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false mutationJobs:<...> new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > exec stmt rows affected: 0 statement ok @@ -120,7 +120,7 @@ WHERE message NOT LIKE '%Z/%' AND operation != 'dist sender send' ---- batch flow coordinator CPut /NamespaceTable/30/1/53/29/"kv2"/4/1 -> 55 -batch flow coordinator CPut /Table/3/1/55/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:false default_expr:"unique_rowid()" hidden:true inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:4 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:ADD offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"TABLE t.public.kv" create_as_of_time:<> temporary:false partition_all_by:false > +batch flow coordinator CPut /Table/3/1/55/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:false default_expr:"unique_rowid()" hidden:true inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:4 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:ADD offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"TABLE t.public.kv" create_as_of_time:<> temporary:false partition_all_by:false > exec stmt rows affected: 0 statement ok @@ -169,7 +169,7 @@ WHERE message NOT LIKE '%Z/%' AND message NOT LIKE 'querying next range at%' AND operation != 'dist sender send' ---- batch flow coordinator Del /NamespaceTable/30/1/53/29/"kv2"/4/1 -batch flow coordinator Put /Table/3/1/55/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:false default_expr:"unique_rowid()" hidden:true inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:4 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:DROP offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:... replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"TABLE t.public.kv" create_as_of_time:<...> temporary:false partition_all_by:false > +batch flow coordinator Put /Table/3/1/55/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:false default_expr:"unique_rowid()" hidden:true inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:4 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:2 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:1 format_version:3 state:DROP offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:... replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"TABLE t.public.kv" create_as_of_time:<...> temporary:false partition_all_by:false > exec stmt rows affected: 0 statement ok @@ -203,7 +203,7 @@ WHERE message NOT LIKE '%Z/%' AND message NOT LIKE 'querying next range at%' AND tag NOT LIKE '%IndexBackfiller%' AND operation != 'dist sender send' ---- -batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > mutations: interleave:<> partitioning: type:FORWARD created_explicitly:true encoding_type:0 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > state:DELETE_AND_WRITE_ONLY direction:DROP mutation_id:2 rollback:false > next_mutation_id:3 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false mutationJobs:<...> new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > +batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > mutations: interleave:<> partitioning: type:FORWARD created_explicitly:true encoding_type:0 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > state:DELETE_AND_WRITE_ONLY direction:DROP mutation_id:2 rollback:false > next_mutation_id:3 format_version:3 state:PUBLIC offline_reason:"" view_query:"" is_materialized_view:false mutationJobs:<...> new_schema_change_job_id:0 drop_time:0 replacement_of: > audit_mode:DISABLED drop_job_id:0 create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > exec stmt rows affected: 0 statement ok @@ -221,7 +221,7 @@ WHERE message NOT LIKE '%Z/%' AND message NOT LIKE 'querying next range at%' AND operation != 'dist sender send' ---- batch flow coordinator Del /NamespaceTable/30/1/53/29/"kv"/4/1 -batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:3 format_version:3 state:DROP offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:... replacement_of: > audit_mode:DISABLED drop_job_id:0 gc_mutations: create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > +batch flow coordinator Put /Table/3/1/54/2/1 -> table: parent_id:53 unexposed_parent_schema_id:29 columns: nullable:false hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > columns: nullable:true hidden:false inaccessible:false generated_as_identity_type:NOT_IDENTITY_COLUMN virtual:false pg_attribute_num:0 alter_column_type_in_progress:false system_column_kind:NONE > next_column_id:3 families: next_family_id:1 primary_index: interleave:<> partitioning: type:FORWARD created_explicitly:false encoding_type:1 sharded: disabled:false geo_config:<> predicate:"" use_delete_preserving_encoding:false > next_index_id:3 privileges: users: users: owner_proto:"root" version:2 > next_mutation_id:3 format_version:3 state:DROP offline_reason:"" view_query:"" is_materialized_view:false new_schema_change_job_id:0 drop_time:... replacement_of: > audit_mode:DISABLED drop_job_id:0 gc_mutations: create_query:"" create_as_of_time:<...> temporary:false partition_all_by:false > exec stmt rows affected: 0 # Check that session tracing does not inhibit the fast path for inserts & diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index e00f0199767a..082a5e56e0d1 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -4286,9 +4286,9 @@ deallocate_stmt: // // %SeeAlso: REVOKE, WEBDOCS/grant.html grant_stmt: - GRANT privileges ON targets TO role_spec_list + GRANT privileges ON targets TO role_spec_list opt_with_grant_option { - $$.val = &tree.Grant{Privileges: $2.privilegeList(), Grantees: $6.roleSpecList(), Targets: $4.targetList()} + $$.val = &tree.Grant{Privileges: $2.privilegeList(), Grantees: $6.roleSpecList(), Targets: $4.targetList(), WithGrantOption: $7.bool(),} } | GRANT privilege_list TO role_spec_list { @@ -4298,11 +4298,11 @@ grant_stmt: { $$.val = &tree.GrantRole{Roles: $2.nameList(), Members: $4.roleSpecList(), AdminOption: true} } -| GRANT privileges ON TYPE target_types TO role_spec_list +| GRANT privileges ON TYPE target_types TO role_spec_list opt_with_grant_option { - $$.val = &tree.Grant{Privileges: $2.privilegeList(), Targets: $5.targetList(), Grantees: $7.roleSpecList()} + $$.val = &tree.Grant{Privileges: $2.privilegeList(), Targets: $5.targetList(), Grantees: $7.roleSpecList(), WithGrantOption: $8.bool(),} } -| GRANT privileges ON SCHEMA schema_name_list TO role_spec_list +| GRANT privileges ON SCHEMA schema_name_list TO role_spec_list opt_with_grant_option { $$.val = &tree.Grant{ Privileges: $2.privilegeList(), @@ -4310,13 +4310,14 @@ grant_stmt: Schemas: $5.objectNamePrefixList(), }, Grantees: $7.roleSpecList(), + WithGrantOption: $8.bool(), } } | GRANT privileges ON SCHEMA schema_name_list TO role_spec_list WITH error { return unimplemented(sqllex, "grant privileges on schema with") } -| GRANT privileges ON ALL TABLES IN SCHEMA schema_name_list TO role_spec_list +| GRANT privileges ON ALL TABLES IN SCHEMA schema_name_list TO role_spec_list opt_with_grant_option { $$.val = &tree.Grant{ Privileges: $2.privilegeList(), @@ -4325,6 +4326,7 @@ grant_stmt: AllTablesInSchema: true, }, Grantees: $10.roleSpecList(), + WithGrantOption: $11.bool(), } } | GRANT privileges ON SEQUENCE error @@ -4355,7 +4357,11 @@ grant_stmt: revoke_stmt: REVOKE privileges ON targets FROM role_spec_list { - $$.val = &tree.Revoke{Privileges: $2.privilegeList(), Grantees: $6.roleSpecList(), Targets: $4.targetList()} + $$.val = &tree.Revoke{Privileges: $2.privilegeList(), Grantees: $6.roleSpecList(), Targets: $4.targetList(), GrantOptionFor: false} + } +| REVOKE GRANT OPTION FOR privileges ON targets FROM role_spec_list + { + $$.val = &tree.Revoke{Privileges: $5.privilegeList(), Grantees: $9.roleSpecList(), Targets: $7.targetList(), GrantOptionFor: true} } | REVOKE privilege_list FROM role_spec_list { @@ -4367,7 +4373,11 @@ revoke_stmt: } | REVOKE privileges ON TYPE target_types FROM role_spec_list { - $$.val = &tree.Revoke{Privileges: $2.privilegeList(), Targets: $5.targetList(), Grantees: $7.roleSpecList()} + $$.val = &tree.Revoke{Privileges: $2.privilegeList(), Targets: $5.targetList(), Grantees: $7.roleSpecList(), GrantOptionFor: false} + } +| REVOKE GRANT OPTION FOR privileges ON TYPE target_types FROM role_spec_list + { + $$.val = &tree.Revoke{Privileges: $5.privilegeList(), Targets: $8.targetList(), Grantees: $10.roleSpecList(), GrantOptionFor: true} } | REVOKE privileges ON SCHEMA schema_name_list FROM role_spec_list { @@ -4377,6 +4387,18 @@ revoke_stmt: Schemas: $5.objectNamePrefixList(), }, Grantees: $7.roleSpecList(), + GrantOptionFor: false, + } + } +| REVOKE GRANT OPTION FOR privileges ON SCHEMA schema_name_list FROM role_spec_list + { + $$.val = &tree.Revoke{ + Privileges: $5.privilegeList(), + Targets: tree.TargetList{ + Schemas: $8.objectNamePrefixList(), + }, + Grantees: $10.roleSpecList(), + GrantOptionFor: true, } } | REVOKE privileges ON ALL TABLES IN SCHEMA schema_name_list FROM role_spec_list @@ -4388,6 +4410,19 @@ revoke_stmt: AllTablesInSchema: true, }, Grantees: $10.roleSpecList(), + GrantOptionFor: false, + } + } +| REVOKE GRANT OPTION FOR privileges ON ALL TABLES IN SCHEMA schema_name_list FROM role_spec_list + { + $$.val = &tree.Revoke{ + Privileges: $5.privilegeList(), + Targets: tree.TargetList{ + Schemas: $11.objectNamePrefixList(), + AllTablesInSchema: true, + }, + Grantees: $13.roleSpecList(), + GrantOptionFor: true, } } | REVOKE privileges ON SEQUENCE error @@ -4396,6 +4431,7 @@ revoke_stmt: } | REVOKE error // SHOW HELP: REVOKE + // ALL can either be by itself, or with the optional PRIVILEGES keyword (which no-ops) privileges: ALL opt_privileges_clause diff --git a/pkg/sql/pg_catalog.go b/pkg/sql/pg_catalog.go index 49d5ec915fa3..040a135c2a4a 100644 --- a/pkg/sql/pg_catalog.go +++ b/pkg/sql/pg_catalog.go @@ -1245,7 +1245,10 @@ https://www.postgresql.org/docs/13/catalog-pg-default-acl.html`, privileges := privilege.ListFromBitField( userPrivs.Privileges, privilegeObjectType, ) - defaclItem := createDefACLItem(user, privileges, privilegeObjectType) + grantOptions := privilege.ListFromBitField( + userPrivs.WithGrantOption, privilegeObjectType, + ) + defaclItem := createDefACLItem(user, privileges, grantOptions, privilegeObjectType) if err := arr.Append( tree.NewDString(defaclItem)); err != nil { return err @@ -1262,7 +1265,7 @@ https://www.postgresql.org/docs/13/catalog-pg-default-acl.html`, if !catprivilege.GetRoleHasAllPrivilegesOnTargetObject(&defaultPrivilegesForRole, tree.Types) && catprivilege.GetPublicHasUsageOnTypes(&defaultPrivilegesForRole) { defaclItem := createDefACLItem( - "" /* public role */, privilege.List{privilege.USAGE}, privilegeObjectType, + "" /* public role */, privilege.List{privilege.USAGE}, privilege.List{}, privilegeObjectType, ) if err := arr.Append(tree.NewDString(defaclItem)); err != nil { return err @@ -1272,7 +1275,7 @@ https://www.postgresql.org/docs/13/catalog-pg-default-acl.html`, defaultPrivilegesForRole.GetExplicitRole().RoleHasAllPrivilegesOnTypes { defaclItem := createDefACLItem( defaultPrivilegesForRole.GetExplicitRole().UserProto.Decode().Normalized(), - privilege.List{privilege.ALL}, privilegeObjectType, + privilege.List{privilege.ALL}, privilege.List{}, privilegeObjectType, ) if err := arr.Append(tree.NewDString(defaclItem)); err != nil { return err @@ -1315,11 +1318,15 @@ https://www.postgresql.org/docs/13/catalog-pg-default-acl.html`, } func createDefACLItem( - user string, privileges privilege.List, privilegeObjectType privilege.ObjectType, + user string, + privileges privilege.List, + grantOptions privilege.List, + privilegeObjectType privilege.ObjectType, ) string { return fmt.Sprintf(`%s=%s/%s`, user, privileges.ListToACL( + grantOptions, privilegeObjectType, ), // TODO(richardjcai): CockroachDB currently does not track grantors diff --git a/pkg/sql/privilege/privilege.go b/pkg/sql/privilege/privilege.go index 0621733af0c5..ab8be2e48347 100644 --- a/pkg/sql/privilege/privilege.go +++ b/pkg/sql/privilege/privilege.go @@ -256,35 +256,46 @@ func GetValidPrivilegesForObject(objectType ObjectType) List { } } +// privToACL is a map of privilege -> ACL character +var privToACL = map[Kind]string{ + CREATE: "C", + SELECT: "r", + INSERT: "a", + DELETE: "d", + UPDATE: "w", + USAGE: "U", + CONNECT: "c", +} + +// orderedPrivs is the list of privileges sorted in alphanumeric order based on the ACL character -> CUacdrw +var orderedPrivs = List{CREATE, USAGE, INSERT, CONNECT, DELETE, SELECT, UPDATE} + // ListToACL converts a list of privileges to a list of Postgres // ACL items. // See: https://www.postgresql.org/docs/13/ddl-priv.html#PRIVILEGE-ABBREVS-TABLE // for privileges and their ACL abbreviations. -func (pl List) ListToACL(objectType ObjectType) string { +func (pl List) ListToACL(grantOptions List, objectType ObjectType) string { privileges := pl // If ALL is present, explode ALL into the underlying privileges. if pl.Contains(ALL) { privileges = GetValidPrivilegesForObject(objectType) + if grantOptions.Contains(ALL) { + grantOptions = GetValidPrivilegesForObject(objectType) + } } chars := make([]string, len(privileges)) - for _, privilege := range privileges { - switch privilege { - case CREATE: - chars = append(chars, "C") - case SELECT: - chars = append(chars, "r") - case INSERT: - chars = append(chars, "a") - case DELETE: - chars = append(chars, "d") - case UPDATE: - chars = append(chars, "w") - case USAGE: - chars = append(chars, "U") - case CONNECT: - chars = append(chars, "c") + for _, privilege := range orderedPrivs { + if _, ok := privToACL[privilege]; !ok { + panic(errors.AssertionFailedf("unknown privilege type %s", privilege.String())) + } + if privileges.Contains(privilege) { + chars = append(chars, privToACL[privilege]) + } + if grantOptions.Contains(privilege) { + chars = append(chars, "*") } } - sort.Strings(chars) + return strings.Join(chars, "") + } diff --git a/pkg/sql/schemachanger/testdata/drop b/pkg/sql/schemachanger/testdata/drop index 8f28a8d2f188..22208e9f324e 100644 --- a/pkg/sql/schemachanger/testdata/drop +++ b/pkg/sql/schemachanger/testdata/drop @@ -17,7 +17,7 @@ begin transaction #1 ## stage 1 in PreCommitPhase: 1 MutationType ops upsert descriptor #53 ... - userProto: root + withGrantOption: 2 version: 2 - version: "1" + state: DROP @@ -115,21 +115,21 @@ begin transaction #1 ## stage 1 in PreCommitPhase: 3 MutationType ops upsert descriptor #54 ... - userProto: root + withGrantOption: 2 version: 2 - version: "1" + state: DROP + version: "2" upsert descriptor #56 ... - userProto: root + withGrantOption: 2 version: 2 - version: "1" + state: DROP + version: "2" upsert descriptor #57 ... - userProto: root + withGrantOption: 2 version: 2 - version: "1" + state: DROP @@ -221,7 +221,7 @@ upsert descriptor #58 + version: "3" upsert descriptor #59 ... - userProto: root + withGrantOption: 2 version: 2 - version: "1" + state: DROP @@ -392,7 +392,7 @@ upsert descriptor #67 viewQuery: (SELECT n2, n1 FROM db1.sc1.v2) upsert descriptor #68 ... - userProto: root + withGrantOption: 2 version: 2 - referencingDescriptorIds: - - 70 @@ -402,7 +402,7 @@ upsert descriptor #68 + version: "3" upsert descriptor #69 ... - userProto: root + withGrantOption: 2 version: 2 - referencingDescriptorIds: - - 70 diff --git a/pkg/sql/sem/tree/grant.go b/pkg/sql/sem/tree/grant.go index 88ef29dc4367..085629e9883a 100644 --- a/pkg/sql/sem/tree/grant.go +++ b/pkg/sql/sem/tree/grant.go @@ -28,9 +28,10 @@ import ( // Grant represents a GRANT statement. type Grant struct { - Privileges privilege.List - Targets TargetList - Grantees RoleSpecList + Privileges privilege.List + Targets TargetList + Grantees RoleSpecList + WithGrantOption bool } // TargetList represents a list of targets. diff --git a/pkg/sql/sem/tree/revoke.go b/pkg/sql/sem/tree/revoke.go index bac0a86dd1c5..69bf49983216 100644 --- a/pkg/sql/sem/tree/revoke.go +++ b/pkg/sql/sem/tree/revoke.go @@ -24,9 +24,10 @@ import "github.com/cockroachdb/cockroach/pkg/sql/privilege" // Revoke represents a REVOKE statement. // PrivilegeList and TargetList are defined in grant.go type Revoke struct { - Privileges privilege.List - Targets TargetList - Grantees RoleSpecList + Privileges privilege.List + Targets TargetList + Grantees RoleSpecList + GrantOptionFor bool } // Format implements the NodeFormatter interface.