diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java index b8cc93a4a5e38d..b9eefd839ea924 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java @@ -321,7 +321,7 @@ public class Config extends ConfigBase { + "The connection is abandoned if the clock skew is larger than this value."}) public static long max_bdbje_clock_delta_ms = 5000; // 5s - @ConfField(description = {"是否启用所有 http 接口的认证", + @ConfField(mutable = true, description = {"是否启用所有 http 接口的认证", "Whether to enable all http interface authentication"}, varType = VariableAnnotation.EXPERIMENTAL) public static boolean enable_all_http_auth = false; diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java index 1218736a2cbed9..14368c2869b849 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/MetaInfoAction.java @@ -59,6 +59,7 @@ * And meta info like databases, tables and schema */ @RestController +@Deprecated public class MetaInfoAction extends RestBaseController { private static final String NAMESPACES = "namespaces"; diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/ShowAction.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/ShowAction.java index 8d93a440b22183..09ca16e6ad0c54 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/ShowAction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/ShowAction.java @@ -30,6 +30,7 @@ import org.apache.doris.common.proc.ProcNodeInterface; import org.apache.doris.common.proc.ProcResult; import org.apache.doris.common.proc.ProcService; +import org.apache.doris.datasource.InternalCatalog; import org.apache.doris.ha.HAProtocol; import org.apache.doris.httpv2.entity.ResponseEntityBuilder; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -214,16 +215,23 @@ public Object show_data(HttpServletRequest request, HttpServletResponse response public Object show_table_data(HttpServletRequest request, HttpServletResponse response) { if (Config.enable_all_http_auth) { executeCheckPassword(request, response); - checkGlobalAuth(ConnectContext.get().getCurrentUserIdentity(), PrivPredicate.ADMIN); } - String dbName = request.getParameter(DB_KEY); String tableName = request.getParameter(TABLE_KEY); + + if (StringUtils.isEmpty(dbName) && StringUtils.isEmpty(tableName)) { + return ResponseEntityBuilder.okWithCommonError("db and table cannot be empty at the same time"); + } + String singleReplica = request.getParameter(SINGLE_REPLICA_KEY); boolean singleReplicaBool = Boolean.parseBoolean(singleReplica); Map> oneEntry = Maps.newHashMap(); if (dbName != null) { String fullDbName = getFullDbName(dbName); + if (!StringUtils.isEmpty(tableName) && Config.enable_all_http_auth) { + checkTblAuth(ConnectContext.get().getCurrentUserIdentity(), fullDbName, tableName, PrivPredicate.SHOW); + } + DatabaseIf db = Env.getCurrentInternalCatalog().getDbNullable(fullDbName); if (db == null) { return ResponseEntityBuilder.okWithCommonError("database " + fullDbName + " not found."); @@ -236,6 +244,12 @@ public Object show_table_data(HttpServletRequest request, HttpServletResponse re if (db == null || !(db instanceof Database) || ((Database) db) instanceof MysqlCompatibleDatabase) { continue; } + if (Config.enable_all_http_auth && !Env.getCurrentEnv().getAccessManager() + .checkTblPriv(ConnectContext.get().getCurrentUserIdentity(), + InternalCatalog.INTERNAL_CATALOG_NAME, db.getFullName(), tableName, + PrivPredicate.SHOW)) { + continue; + } Map tablesEntry = getDataSizeOfTables(db, tableName, singleReplicaBool); oneEntry.put(ClusterNamespace.getNameFromFullName(db.getFullName()), tablesEntry); } @@ -331,6 +345,12 @@ private Map getDataSizeOfTables(DatabaseIf db, String tableName, b if (Strings.isNullOrEmpty(tableName)) { List tables = db.getTables(); for (Table table : tables) { + if (Config.enable_all_http_auth && !Env.getCurrentEnv().getAccessManager() + .checkTblPriv(ConnectContext.get(), InternalCatalog.INTERNAL_CATALOG_NAME, db.getFullName(), + table.getName(), + PrivPredicate.SHOW)) { + continue; + } Map tableEntry = getDataSizeOfTable(table, singleReplica); oneEntry.putAll(tableEntry); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/httpv2/restv2/MetaInfoActionV2.java b/fe/fe-core/src/main/java/org/apache/doris/httpv2/restv2/MetaInfoActionV2.java index 13a247ba6338e9..436d2611584502 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/restv2/MetaInfoActionV2.java +++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/restv2/MetaInfoActionV2.java @@ -86,13 +86,14 @@ public Object getAllDatabases( HttpServletRequest request, HttpServletResponse response) { checkWithCookie(request, response, false); - if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) { - return ResponseEntityBuilder.badRequest("Only support 'default_cluster' now"); + if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER) && !ns.equalsIgnoreCase( + InternalCatalog.INTERNAL_CATALOG_NAME)) { + return ResponseEntityBuilder.badRequest("Only support 'default_cluster/internal' now"); } // 1. get all database with privilege List dbNames = Env.getCurrentInternalCatalog().getDbNames(); - List dbNameSet = Lists.newArrayList(); + List filteredDbNames = Lists.newArrayList(); for (String fullName : dbNames) { final String db = ClusterNamespace.getNameFromFullName(fullName); if (!Env.getCurrentEnv().getAccessManager() @@ -100,14 +101,14 @@ public Object getAllDatabases( PrivPredicate.SHOW)) { continue; } - dbNameSet.add(db); + filteredDbNames.add(db); } - Collections.sort(dbNames); + Collections.sort(filteredDbNames); // handle limit offset - Pair fromToIndex = getFromToIndex(request, dbNames.size()); - return ResponseEntityBuilder.ok(dbNames.subList(fromToIndex.first, fromToIndex.second)); + Pair fromToIndex = getFromToIndex(request, filteredDbNames.size()); + return ResponseEntityBuilder.ok(filteredDbNames.subList(fromToIndex.first, fromToIndex.second)); } /** Get all tables of a database @@ -129,8 +130,9 @@ public Object getTables( HttpServletRequest request, HttpServletResponse response) { checkWithCookie(request, response, false); - if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) { - return ResponseEntityBuilder.badRequest("Only support 'default_cluster' now"); + if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER) && !ns.equalsIgnoreCase( + InternalCatalog.INTERNAL_CATALOG_NAME)) { + return ResponseEntityBuilder.badRequest("Only support 'default_cluster/internal' now"); } String fullDbName = getFullDbName(dbName); @@ -199,8 +201,9 @@ public Object getTableSchema( HttpServletRequest request, HttpServletResponse response) throws UserException { checkWithCookie(request, response, false); - if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) { - return ResponseEntityBuilder.badRequest("Only support 'default_cluster' now"); + if (!ns.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER) && !ns.equalsIgnoreCase( + InternalCatalog.INTERNAL_CATALOG_NAME)) { + return ResponseEntityBuilder.badRequest("Only support 'default_cluster/internal' now"); } String fullDbName = getFullDbName(dbName); diff --git a/regression-test/suites/auth_p0/test_http_meta_databases_auth.groovy b/regression-test/suites/auth_p0/test_http_meta_databases_auth.groovy new file mode 100644 index 00000000000000..c515b5c83ea722 --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_meta_databases_auth.groovy @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Assert; + +suite("test_http_meta_databases_auth","p0,auth,nonConcurrent") { + String suiteName = "test_http_meta_databases_auth" + String dbName = context.config.getDbNameByFile(context.file) + String tableName = "${suiteName}_table" + String user = "${suiteName}_user" + String pwd = 'C123_567p' + try_sql("DROP USER ${user}") + sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'""" + try { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "true"); """ + def getDatabases = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/rest/v2/api/meta/namespaces/default_cluster/databases" + op "get" + check check_func + } + } + + getDatabases.call() { + respCode, body -> + log.info("body:${body}") + assertFalse("${body}".contains("${dbName}")) + } + + sql """grant select_priv on ${dbName} to ${user}""" + + getDatabases.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("${dbName}")) + } + + try_sql("DROP USER ${user}") + } finally { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "false"); """ + } +} diff --git a/regression-test/suites/auth_p0/test_http_meta_tables_auth.groovy b/regression-test/suites/auth_p0/test_http_meta_tables_auth.groovy new file mode 100644 index 00000000000000..b2fd5914352808 --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_meta_tables_auth.groovy @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Assert; + +suite("test_http_meta_tables_auth","p0,auth,nonConcurrent") { + String suiteName = "test_http_meta_tables_auth" + String dbName = context.config.getDbNameByFile(context.file) + String tableName = "${suiteName}_table" + String user = "${suiteName}_user" + String pwd = 'C123_567p' + try_sql("DROP USER ${user}") + sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'""" + sql """drop table if exists `${tableName}`""" + sql """ + CREATE TABLE `${tableName}` ( + `k1` int, + `k2` int + ) ENGINE=OLAP + DISTRIBUTED BY random BUCKETS auto + PROPERTIES ('replication_num' = '1') ; + """ + try { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "true"); """ + def getTables = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/rest/v2/api/meta/namespaces/default_cluster/databases/${dbName}/tables" + op "get" + check check_func + } + } + + getTables.call() { + respCode, body -> + log.info("body:${body}") + assertFalse("${body}".contains("${tableName}")) + } + + sql """grant select_priv on ${dbName}.${tableName} to ${user}""" + + getTables.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("${tableName}")) + } + + sql """drop table if exists `${tableName}`""" + try_sql("DROP USER ${user}") + } finally { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "false"); """ + } + + +} diff --git a/regression-test/suites/auth_p0/test_http_meta_tables_schema_auth.groovy b/regression-test/suites/auth_p0/test_http_meta_tables_schema_auth.groovy new file mode 100644 index 00000000000000..f03d5a55bd32fb --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_meta_tables_schema_auth.groovy @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Assert; + +suite("test_http_meta_tables_schema_auth","p0,auth,nonConcurrent") { + String suiteName = "test_http_meta_tables_schema_auth" + String dbName = context.config.getDbNameByFile(context.file) + String tableName = "${suiteName}_table" + String user = "${suiteName}_user" + String pwd = 'C123_567p' + try_sql("DROP USER ${user}") + sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'""" + sql """drop table if exists `${tableName}`""" + sql """ + CREATE TABLE `${tableName}` ( + `k1` int, + `k2` int + ) ENGINE=OLAP + DISTRIBUTED BY random BUCKETS auto + PROPERTIES ('replication_num' = '1') ; + """ + + try { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "true"); """ + def getSchema = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/rest/v2/api/meta/namespaces/default_cluster/databases/${dbName}/tables/${tableName}/schema" + op "get" + check check_func + } + } + + getSchema.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("401")) + } + + sql """grant select_priv on ${dbName}.${tableName} to ${user}""" + + getSchema.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("${tableName}")) + } + + sql """drop table if exists `${tableName}`""" + try_sql("DROP USER ${user}") + } finally { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "false"); """ + } +} diff --git a/regression-test/suites/auth_p0/test_http_table_count_auth.groovy b/regression-test/suites/auth_p0/test_http_table_count_auth.groovy new file mode 100644 index 00000000000000..2cf222b1f587a2 --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_table_count_auth.groovy @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Assert; + +suite("test_http_table_count_auth","p0,auth,nonConcurrent") { + String suiteName = "test_http_table_count_auth" + String dbName = context.config.getDbNameByFile(context.file) + String tableName = "${suiteName}_table" + String user = "${suiteName}_user" + String pwd = 'C123_567p' + try_sql("DROP USER ${user}") + sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'""" + sql """drop table if exists `${tableName}`""" + sql """ + CREATE TABLE `${tableName}` ( + `k1` int, + `k2` int + ) ENGINE=OLAP + DISTRIBUTED BY random BUCKETS auto + PROPERTIES ('replication_num' = '1') ; + """ + sql """insert into ${tableName} values(1,1)""" + try { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "true"); """ + def getCount = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/api/${dbName}/${tableName}/_count" + op "get" + check check_func + } + } + + getCount.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("401")) + } + + sql """grant select_priv on ${dbName}.${tableName} to ${user}""" + + getCount.call() { + respCode, body -> + log.info("body:${body}") + assertFalse("${body}".contains("401")) + } + + sql """drop table if exists `${tableName}`""" + try_sql("DROP USER ${user}") + } finally { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "false"); """ + } +} diff --git a/regression-test/suites/auth_p0/test_http_table_data_auth.groovy b/regression-test/suites/auth_p0/test_http_table_data_auth.groovy new file mode 100644 index 00000000000000..3a773894a5659a --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_table_data_auth.groovy @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Assert; + +suite("test_http_table_data_auth","p0,auth,nonConcurrent") { + String suiteName = "test_http_table_data_auth" + String dbName = context.config.getDbNameByFile(context.file) + String tableName = "${suiteName}_table" + String user = "${suiteName}_user" + String pwd = 'C123_567p' + try_sql("DROP USER ${user}") + sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'""" + sql """drop table if exists `${tableName}`""" + sql """ + CREATE TABLE `${tableName}` ( + `k1` int, + `k2` int + ) ENGINE=OLAP + DISTRIBUTED BY random BUCKETS auto + PROPERTIES ('replication_num' = '1') ; + """ + sql """insert into ${tableName} values(1,1)""" + try { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "true"); """ + def getTableData = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/api/show_table_data?db=${dbName}&table=${tableName}" + op "get" + check check_func + } + } + + def getDbData = { check_func -> + httpTest { + basicAuthorization "${user}","${pwd}" + endpoint "${context.config.feHttpAddress}" + uri "/api/show_table_data?db=${dbName}" + op "get" + check check_func + } + } + + getTableData.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("401")) + } + + getDbData.call() { + respCode, body -> + log.info("body:${body}") + assertFalse("${body}".contains("${tableName}")) + } + + sql """grant select_priv on ${dbName}.${tableName} to ${user}""" + + getTableData.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("${tableName}")) + } + + getDbData.call() { + respCode, body -> + log.info("body:${body}") + assertTrue("${body}".contains("${tableName}")) + } + + sql """drop table if exists `${tableName}`""" + try_sql("DROP USER ${user}") + } finally { + sql """ ADMIN SET ALL FRONTENDS CONFIG ("enable_all_http_auth" = "false"); """ + } +}