Skip to content

Commit

Permalink
[fix](auth)Http api check auth (apache#40688)
Browse files Browse the repository at this point in the history
- add auth for `api/meta/namespaces/internal/databases`
- add auth for `api/show_table_data`
  • Loading branch information
zddr committed Sep 14, 2024
1 parent 55e92da commit 03a5792
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Map<String, Long>> 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.");
Expand All @@ -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<String, Long> tablesEntry = getDataSizeOfTables(db, tableName, singleReplicaBool);
oneEntry.put(ClusterNamespace.getNameFromFullName(db.getFullName()), tablesEntry);
}
Expand Down Expand Up @@ -331,6 +345,12 @@ private Map<String, Long> getDataSizeOfTables(DatabaseIf db, String tableName, b
if (Strings.isNullOrEmpty(tableName)) {
List<Table> 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<String, Long> tableEntry = getDataSizeOfTable(table, singleReplica);
oneEntry.putAll(tableEntry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,28 +86,29 @@ 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<String> dbNames = Env.getCurrentInternalCatalog().getDbNames();
List<String> dbNameSet = Lists.newArrayList();
List<String> filteredDbNames = Lists.newArrayList();
for (String fullName : dbNames) {
final String db = ClusterNamespace.getNameFromFullName(fullName);
if (!Env.getCurrentEnv().getAccessManager()
.checkDbPriv(ConnectContext.get(), InternalCatalog.INTERNAL_CATALOG_NAME, fullName,
PrivPredicate.SHOW)) {
continue;
}
dbNameSet.add(db);
filteredDbNames.add(db);
}

Collections.sort(dbNames);
Collections.sort(filteredDbNames);

// handle limit offset
Pair<Integer, Integer> fromToIndex = getFromToIndex(request, dbNames.size());
return ResponseEntityBuilder.ok(dbNames.subList(fromToIndex.first, fromToIndex.second));
Pair<Integer, Integer> fromToIndex = getFromToIndex(request, filteredDbNames.size());
return ResponseEntityBuilder.ok(filteredDbNames.subList(fromToIndex.first, fromToIndex.second));
}

/** Get all tables of a database
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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"); """
}
}
70 changes: 70 additions & 0 deletions regression-test/suites/auth_p0/test_http_meta_tables_auth.groovy
Original file line number Diff line number Diff line change
@@ -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"); """
}


}
Original file line number Diff line number Diff line change
@@ -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"); """
}
}
Loading

0 comments on commit 03a5792

Please sign in to comment.