This is an automated email from the ASF dual-hosted git repository.
casion pushed a commit to branch dev-1.3.1
in repository https://gitbox.apache.org/repos/asf/linkis.git
The following commit(s) were added to refs/heads/dev-1.3.1 by this push:
new a65227696 [feat:4107] Mysql connection security check extract utils
(#4108)
a65227696 is described below
commit a652276961e39a6e0d6f79698e2b082a72f160d1
Author: aiceflower <[email protected]>
AuthorDate: Thu Jan 12 11:27:19 2023 +0800
[feat:4107] Mysql connection security check extract utils (#4108)
* mysql connection security check extract utils
---
CONTRIBUTING.md | 2 +-
CONTRIBUTING_CN.md | 2 +-
docs/info-1.3.1.md | 10 +-
.../common/exception/LinkisSecurityException.java | 30 ++++
.../apache/linkis/common/utils/SecurityUtils.java | 192 +++++++++++++++++++++
.../linkis/common/utils/SecurityUtilsTest.java | 169 ++++++++++++++++++
linkis-dist/package/conf/linkis.properties | 2 +
.../engineplugin/jdbc/utils/JdbcParamUtils.java | 76 +-------
.../jdbc/utils/JdbcParamUtilsTest.java | 83 +++++----
.../query/service/mysql/SqlConnection.java | 62 +------
10 files changed, 462 insertions(+), 166 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4514f4b45..2eb3e45e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -145,7 +145,7 @@ git push origin dev-fix dev-fix
- If you still don’t know how to initiate a PR to an open source project,
please refer to [About pull
requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
Whether it is a bug fix or a new feature development, please submit a PR to
the dev-* branch
-- PR and submission name follow the principle of `<type>(<scope>): <subject>`,
for details, please refer to [Commit message and Change log writing
guide](https://linkis.apache.org/community/development-specification/commit-message)
+- PR and submission name follow the principle of `<type>(<scope>): <subject>`,
for details, please refer to [Commit message and Change log writing
guide](https://linkis.apache.org/docs/1.3.1/development/development-specification/commit-message)
- If the PR contains new features, the document update should be included in
this PR
- If this PR is not ready to merge, please add [WIP] prefix to the head of the
name (WIP = work-in-progress)
- All submissions to dev-* branches need to go through at least one review
before they can be merged
diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md
index a16ebf102..e6b67f492 100644
--- a/CONTRIBUTING_CN.md
+++ b/CONTRIBUTING_CN.md
@@ -139,7 +139,7 @@ git push origin dev-fix dev-fix
- 如果您还不知道怎样向开源项目发起 PR,请参考[About pull
requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
- 无论是 Bug 修复,还是新功能开发,请将 PR 提交到 dev-* 分支
-- PR 和提交名称遵循 `<type>(<scope>): <subject>` 原则,详情可以参考[Commit message 和 Change
log
编写指南](https://linkis.apache.org/zh-CN/community/development-specification/commit-message)
+- PR 和提交名称遵循 `<type>(<scope>): <subject>` 原则,详情可以参考[Commit message 和 Change
log
编写指南](https://linkis.apache.org/zh-CN/docs/1.3.1/development/development-specification/commit-message)
- 如果 PR 中包含新功能,理应将文档更新包含在本次 PR 中
- 如果本次 PR 尚未准备好合并,请在名称头部加上 [WIP] 前缀(WIP = work-in-progress)
- 所有提交到 dev-* 分支的提交至少需要经过一次 Review 才可以被合并
diff --git a/docs/info-1.3.1.md b/docs/info-1.3.1.md
index 835d83651..73c3630a8 100644
--- a/docs/info-1.3.1.md
+++ b/docs/info-1.3.1.md
@@ -1,6 +1,8 @@
## 参数变化
-| 模块名(服务名) | 类型 | 参数名
| 默认值 | 描述
|
-|---------------------------------------------------|-----|----------------------------------------------------------------------|-------|
------------------------------------------------------- |
-| ps-linkismanager | 修改 |
pipeline.output.isoverwtite <br/>-><br/> pipeline.output.isoverwrite | true
|取值范围:true或false|
-| linkis-engineconn-plugins <br/> linkis-datasource | 新增 |
linkis.mysql.strong.security.enable | false |取值范围:true或false|
+| 模块名(服务名) | 类型 | 参数名
| 默认值 | 描述 |
+|---------------------------------------------------|-----|----------------------------------------------------------------------|-------|-----------------|
+| ps-linkismanager | 修改 |
pipeline.output.isoverwtite <br/>-><br/> pipeline.output.isoverwrite | true |
取值范围:true或false |
+| linkis-engineconn-plugins <br/> linkis-datasource | 新增 |
linkis.mysql.strong.security.enable | false | 取值范围:true或false |
+| linkis-common | 新增 |
linkis.mysql.force.params |
allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false
| mysql连接强制携带参数 |
+| linkis-common | 新增 | linkis.mysql.sensitive.params |
allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,# |
mysql连接安全校验参数 |
diff --git
a/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/LinkisSecurityException.java
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/LinkisSecurityException.java
new file mode 100644
index 000000000..e38ab6acd
--- /dev/null
+++
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/LinkisSecurityException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.common.exception;
+
+public class LinkisSecurityException extends LinkisRuntimeException {
+
+ @Override
+ public ExceptionLevel getLevel() {
+ return null;
+ }
+
+ public LinkisSecurityException(int errCode, String desc) {
+ super(errCode, desc);
+ }
+}
diff --git
a/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/SecurityUtils.java
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/SecurityUtils.java
new file mode 100644
index 000000000..5333b2432
--- /dev/null
+++
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/SecurityUtils.java
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.common.utils;
+
+import org.apache.linkis.common.conf.CommonVars;
+import org.apache.linkis.common.conf.CommonVars$;
+import org.apache.linkis.common.exception.LinkisSecurityException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class SecurityUtils {
+
+ private static final Logger logger =
LoggerFactory.getLogger(SecurityUtils.class);
+
+ private static final String COMMA = ",";
+
+ private static final String EQUAL_SIGN = "=";
+
+ private static final String AND_SYMBOL = "&";
+
+ private static final String QUESTION_MARK = "?";
+
+ /** allowLoadLocalInfile,allowLoadLocalInfiled,# */
+ public static final CommonVars<String> MYSQL_SENSITIVE_PARAMS =
+ CommonVars$.MODULE$.apply(
+ "linkis.mysql.sensitive.params",
+
"allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,#");
+
+ /**
+ *
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false"
+ */
+ public static final CommonVars<String> MYSQL_FORCE_PARAMS =
+ CommonVars$.MODULE$.apply(
+ "linkis.mysql.force.params",
+
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false");
+
+ public static final CommonVars<String> MYSQL_STRONG_SECURITY_ENABLE =
+ CommonVars$.MODULE$.apply("linkis.mysql.strong.security.enable",
"false");
+
+ /**
+ * mysql url append force params
+ *
+ * @param url
+ * @return
+ */
+ public static String appendMysqlForceParams(String url) {
+ if (StringUtils.isBlank(url)) {
+ return "";
+ }
+
+ String extraParamString = MYSQL_FORCE_PARAMS.getValue();
+
+ if (url.endsWith(QUESTION_MARK)) {
+ url = url + extraParamString;
+ } else if (url.lastIndexOf(QUESTION_MARK) < 0) {
+ url = url + QUESTION_MARK + extraParamString;
+ } else {
+ url = url + AND_SYMBOL + extraParamString;
+ }
+ return url;
+ }
+
+ public static void appendMysqlForceParams(Map<String, Object> extraParams) {
+
extraParams.putAll(parseMysqlUrlParamsToMap(MYSQL_FORCE_PARAMS.getValue()));
+ }
+
+ public static String checkJdbcSecurity(String url) {
+ logger.info("checkJdbcSecurity origin url: {}", url);
+ if (StringUtils.isBlank(url)) {
+ throw new LinkisSecurityException(35000, "Invalid mysql connection cul,
url is empty");
+ }
+ if (url.endsWith(QUESTION_MARK) || !url.contains(QUESTION_MARK)) {
+ logger.info("checkJdbcSecurity target url: {}", url);
+ return url;
+ }
+ String[] items = url.split("\\?");
+ if (items.length != 2) {
+ logger.warn("Invalid url: {}", url);
+ throw new LinkisSecurityException(35000, "Invalid mysql connection cul:
" + url);
+ }
+ Map<String, Object> params = parseMysqlUrlParamsToMap(items[1]);
+ Map<String, Object> securityMap = checkJdbcSecurity(params);
+ String paramUrl = parseParamsMapToMysqlParamUrl(securityMap);
+ url = items[0] + QUESTION_MARK + paramUrl;
+ logger.info("checkJdbcSecurity target url: {}", url);
+ return url;
+ }
+
+ /**
+ * check jdbc params
+ *
+ * @param paramsMap
+ */
+ public static Map<String, Object> checkJdbcSecurity(Map<String, Object>
paramsMap) {
+ if (paramsMap == null) {
+ return new HashMap<>();
+ }
+
+ // mysql url strong security
+ if (Boolean.valueOf(MYSQL_STRONG_SECURITY_ENABLE.getValue())) {
+ paramsMap.clear();
+ return paramsMap;
+ }
+
+ Iterator<Map.Entry<String, Object>> iterator =
paramsMap.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, Object> entry = iterator.next();
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (StringUtils.isBlank(key) || value == null ||
StringUtils.isBlank(value.toString())) {
+ logger.warn("Invalid parameter key or value is blank.");
+ iterator.remove();
+ continue;
+ }
+ if (isNotSecurity(key, value.toString())) {
+ logger.warn("Sensitive param : key={} and value={}", key, value);
+ throw new LinkisSecurityException(
+ 35000,
+ "Invalid mysql connection parameters: " +
parseParamsMapToMysqlParamUrl(paramsMap));
+ }
+ }
+ return paramsMap;
+ }
+
+ public static String parseParamsMapToMysqlParamUrl(Map<String, Object>
forceParams) {
+ if (forceParams == null) {
+ return "";
+ }
+ return forceParams.entrySet().stream()
+ .map(e -> String.join(EQUAL_SIGN, e.getKey(),
String.valueOf(e.getValue())))
+ .collect(Collectors.joining(AND_SYMBOL));
+ }
+
+ private static Map<String, Object> parseMysqlUrlParamsToMap(String
paramsUrl) {
+ String[] params = paramsUrl.split(AND_SYMBOL);
+ Map<String, Object> map = new LinkedHashMap<>(params.length);
+ for (String param : params) {
+ String[] item = param.split(EQUAL_SIGN);
+ if (item.length != 2) {
+ logger.warn("mysql force param {} error.", param);
+ continue;
+ }
+ map.put(item[0], item[1]);
+ }
+ return map;
+ }
+
+ private static boolean isNotSecurity(String key, String value) {
+ boolean res = true;
+ String sensitiveParamsStr = MYSQL_SENSITIVE_PARAMS.getValue();
+ if (StringUtils.isBlank(sensitiveParamsStr)) {
+ return false;
+ }
+ String[] forceParams = sensitiveParamsStr.split(COMMA);
+ for (String forceParam : forceParams) {
+ if (isNotSecurity(key, value, forceParam)) {
+ res = false;
+ break;
+ }
+ }
+ return !res;
+ }
+
+ private static boolean isNotSecurity(String key, String value, String param)
{
+ return key.toLowerCase().contains(param.toLowerCase())
+ || value.toLowerCase().contains(param.toLowerCase());
+ }
+}
diff --git
a/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/utils/SecurityUtilsTest.java
b/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/utils/SecurityUtilsTest.java
new file mode 100644
index 000000000..9d4893e46
--- /dev/null
+++
b/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/utils/SecurityUtilsTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.common.utils;
+
+import org.apache.linkis.common.exception.LinkisSecurityException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** SecurityUtils Tester */
+public class SecurityUtilsTest {
+
+ @Test
+ public void testAppendMysqlForceParamsUrl() throws Exception {
+ //
allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false
+ String url = "jdbc:mysql://127.0.0.1:10000/db_name";
+ String newUrl = SecurityUtils.appendMysqlForceParams(url);
+ Assertions.assertEquals(
+ url
+ +
"?allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
+ newUrl);
+
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?";
+ newUrl = SecurityUtils.appendMysqlForceParams(url);
+ Assertions.assertEquals(
+ url
+ +
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
+ newUrl);
+
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1";
+ newUrl = SecurityUtils.appendMysqlForceParams(url);
+ Assertions.assertEquals(
+ url
+ + "&"
+ +
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
+ newUrl);
+ }
+
+ @Test
+ public void testAppendMysqlForceParamsExtraParams() throws Exception {
+ Map<String, Object> extraParams = new HashMap<>();
+ extraParams.put("testKey", "testValue");
+ SecurityUtils.appendMysqlForceParams(extraParams);
+ Assertions.assertEquals("false", extraParams.get("allowLoadLocalInfile"));
+ Assertions.assertEquals("false", extraParams.get("autoDeserialize"));
+ Assertions.assertEquals("false", extraParams.get("allowLocalInfile"));
+ Assertions.assertEquals("false", extraParams.get("allowUrlInLocalInfile"));
+ Assertions.assertEquals("testValue", extraParams.get("testKey"));
+ Assertions.assertEquals(null, extraParams.get("otherKey"));
+ }
+
+ @Test
+ public void testCheckJdbcSecurityUrl() throws Exception {
+ String url = "jdbc:mysql://127.0.0.1:10000/db_name";
+ String newUrl = SecurityUtils.checkJdbcSecurity(url);
+ Assertions.assertEquals(url, newUrl);
+
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?";
+ newUrl = SecurityUtils.checkJdbcSecurity(url);
+ Assertions.assertEquals(url, newUrl);
+
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1";
+ newUrl = SecurityUtils.checkJdbcSecurity(url);
+ Assertions.assertEquals(url, newUrl);
+
+ // key is not security
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1&allowLocalInfile=true";
+ AtomicReference<String> atomUrl = new AtomicReference<>(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(atomUrl.get());
+ });
+
+ // value is not security
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=allowLocalInfile";
+ atomUrl.set(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(atomUrl.get());
+ });
+
+ // contains #
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1&#p2=v2";
+ atomUrl.set(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(atomUrl.get());
+ });
+ }
+
+ @Test
+ public void testCheckJdbcSecurityParamsMap() throws Exception {
+ Map<String, Object> paramsMap = new HashMap<>();
+ paramsMap.put("p1", "v1");
+ Map<String, Object> newMap = SecurityUtils.checkJdbcSecurity(paramsMap);
+ Assertions.assertEquals("v1", newMap.get("p1"));
+
+ // key not security
+ paramsMap.put("allowLocalInfile", "false");
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(paramsMap);
+ });
+
+ // value not security
+ paramsMap.clear();
+ paramsMap.put("p1", "allowLocalInfile");
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(paramsMap);
+ });
+
+ // contains #
+ paramsMap.clear();
+ paramsMap.put("p1#", "v1");
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(paramsMap);
+ });
+
+ paramsMap.clear();
+ paramsMap.put("p1", "v1#");
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ SecurityUtils.checkJdbcSecurity(paramsMap);
+ });
+ }
+
+ @Test
+ public void testMapToString() throws Exception {
+ Map<String, Object> paramsMap = new HashMap<>();
+ paramsMap.put("p1", "v1");
+ String str = SecurityUtils.parseParamsMapToMysqlParamUrl(paramsMap);
+ Assertions.assertEquals("p1=v1", str);
+
+ paramsMap.clear();
+ str = SecurityUtils.parseParamsMapToMysqlParamUrl(paramsMap);
+ Assertions.assertEquals("", str);
+
+ str = SecurityUtils.parseParamsMapToMysqlParamUrl(null);
+ Assertions.assertEquals("", str);
+ }
+}
diff --git a/linkis-dist/package/conf/linkis.properties
b/linkis-dist/package/conf/linkis.properties
index d4a602ecc..2f6ef7fc1 100644
--- a/linkis-dist/package/conf/linkis.properties
+++ b/linkis-dist/package/conf/linkis.properties
@@ -28,6 +28,8 @@ wds.linkis.server.mybatis.datasource.password=
# mysql
wds.linkis.mysql.is.encrypt=false
linkis.mysql.strong.security.enable=false
+linkis.mysql.force.params=allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false
+linkis.mysql.sensitive.params=allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,#
#hadoop/hive/spark config
#hadoop.config.dir=/appcom/config/hadoop-config
diff --git
a/linkis-engineconn-plugins/jdbc/src/main/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtils.java
b/linkis-engineconn-plugins/jdbc/src/main/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtils.java
index 32518d1a2..40e6a2ae9 100644
---
a/linkis-engineconn-plugins/jdbc/src/main/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtils.java
+++
b/linkis-engineconn-plugins/jdbc/src/main/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtils.java
@@ -19,15 +19,14 @@ package org.apache.linkis.manager.engineplugin.jdbc.utils;
import org.apache.linkis.common.conf.CommonVars;
import org.apache.linkis.common.conf.CommonVars$;
+import org.apache.linkis.common.utils.SecurityUtils;
import org.apache.linkis.manager.engineplugin.jdbc.JDBCPropertiesParser;
import
org.apache.linkis.manager.engineplugin.jdbc.constant.JDBCEngineConnConstant;
import
org.apache.linkis.manager.engineplugin.jdbc.exception.JDBCParamsIllegalException;
import org.apache.commons.lang3.StringUtils;
-import java.util.HashMap;
import java.util.Map;
-import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,6 +70,9 @@ public class JdbcParamUtils {
}
public static String filterJdbcUrl(String url) {
+
+ LOG.info("the filter source url is: {}", url);
+
if (StringUtils.isBlank(url)) {
return url;
}
@@ -79,78 +81,16 @@ public class JdbcParamUtils {
return url;
}
- // no params
- if (!url.contains(String.valueOf(QUESTION_MARK))) {
- return url + QUESTION_MARK + APPEND_PARAMS;
- }
+ // security check
+ url = SecurityUtils.checkJdbcSecurity(url);
- // enable strong security
- if (Boolean.valueOf(MYSQL_STRONG_SECURITY_ENABLE.getValue())) {
- LOG.info("mysql engine use strong security configuration. Remove all
connection parameters.");
- return url + QUESTION_MARK + APPEND_PARAMS;
- }
+ // append force params
+ url = SecurityUtils.appendMysqlForceParams(url);
- // deal with params
- String[] items = url.split("\\?");
- // params error: multiple question marks
- if (items.length != 2) {
- LOG.warn("JDBC params error, the url is : " + url);
- return items[0];
- }
-
- String[] params = items[1].split("&");
- Map<String, String> paramsMap = new HashMap<>(params.length);
- for (String param : params) {
- String[] keyAndValues = param.split("=");
- // params error: key and value error
- if (keyAndValues.length != 2) {
- continue;
- }
- String key = keyAndValues[0];
- String value = keyAndValues[1];
- // key and value is blank
- if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
- continue;
- }
- if (isSecurity(key, value)) {
- paramsMap.put(key, value);
- } else {
- LOG.warn("Sensitive param : key={} and value={}", key, value);
- }
- }
- String extraParamString =
- paramsMap.entrySet().stream()
- .map(e -> String.join("=", e.getKey(),
String.valueOf(e.getValue())))
- .collect(Collectors.joining("&"));
-
- if (StringUtils.isBlank(extraParamString)) {
- url = items[0];
- } else {
- url = items[0] + String.valueOf(QUESTION_MARK) + extraParamString;
- }
- if (url.endsWith(String.valueOf(QUESTION_MARK))) {
- url = url + APPEND_PARAMS;
- } else if (url.lastIndexOf(QUESTION_MARK) < 0) {
- url = url + QUESTION_MARK + APPEND_PARAMS;
- } else {
- url = url + AND_SYMBOL + APPEND_PARAMS;
- }
LOG.info("The filtered jdbc url is: {}", url);
return url;
}
- private static boolean isSecurity(String key, String value) {
- return !(isNotSecurity(key) || isNotSecurity(value));
- }
-
- private static boolean isNotSecurity(String key) {
- return key.toLowerCase().contains("allowLoadLocalInfile".toLowerCase())
- || key.toLowerCase().contains("autoDeserialize".toLowerCase())
- || key.toLowerCase().contains("allowLocalInfile".toLowerCase())
- || key.toLowerCase().contains("allowUrlInLocalInfile".toLowerCase())
- || key.toLowerCase().contains("#".toLowerCase());
- }
-
public static String getJdbcUsername(Map<String, String> properties)
throws JDBCParamsIllegalException {
String username =
diff --git
a/linkis-engineconn-plugins/jdbc/src/test/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtilsTest.java
b/linkis-engineconn-plugins/jdbc/src/test/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtilsTest.java
index fbaa6c3f2..26d7ed424 100644
---
a/linkis-engineconn-plugins/jdbc/src/test/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtilsTest.java
+++
b/linkis-engineconn-plugins/jdbc/src/test/java/org/apache/linkis/manager/engineplugin/jdbc/utils/JdbcParamUtilsTest.java
@@ -17,11 +17,13 @@
package org.apache.linkis.manager.engineplugin.jdbc.utils;
+import org.apache.linkis.common.exception.LinkisSecurityException;
import
org.apache.linkis.manager.engineplugin.jdbc.constant.JDBCEngineConnConstant;
import
org.apache.linkis.manager.engineplugin.jdbc.exception.JDBCParamsIllegalException;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
@@ -31,49 +33,56 @@ public class JdbcParamUtilsTest {
@Test
@DisplayName("testFilterJdbcUrl")
public void testFilterJdbcUrl() {
- String url =
-
"jdbc:mysql://localhost:3306/db_name?allowLoadLocalInfile=true&autoDeserialize=true&#sk=13&p1=v1";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?p1=v1&allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ String securityParam =
+
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false";
+ String url = "jdbc:mysql://127.0.0.1:10000/db_name";
+ String newUrl = JdbcParamUtils.filterJdbcUrl(url);
+ Assertions.assertEquals(url + "?" + securityParam, newUrl);
- url =
-
"jdbc:mysql://localhost:3306/db_name?\\#allowLoadLocalInfile=true&autoDeserialize=true&#sk=13&p1=v1";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?p1=v1&allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ // not mysql url
+ url = "h2:mysql";
+ newUrl = JdbcParamUtils.filterJdbcUrl(url);
+ Assertions.assertEquals(url, newUrl);
- url = "jdbc:mysql://localhost:3306/db_name";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ // start with JDBC
+ url = "JDBC:mysql://127.0.0.1:10000/db_name?";
+ newUrl = JdbcParamUtils.filterJdbcUrl(url);
+ Assertions.assertEquals(url + securityParam, newUrl);
- url = "jdbc:mysql://localhost:3306/db_name?p1=v1";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?p1=v1&allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?";
+ newUrl = JdbcParamUtils.filterJdbcUrl(url);
+ Assertions.assertEquals(url + securityParam, newUrl);
- url = "jdbc:mysql://localhost:3306/db_name?autoDeserialize=true";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1";
+ newUrl = JdbcParamUtils.filterJdbcUrl(url);
+ Assertions.assertEquals(url + "&" + securityParam, newUrl);
- url = "jdbc:mysql://localhost:3306/db_name?p1=v1&autoDeserialize=true";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?p1=v1&allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ // key is not security
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1&allowLocalInfile=true";
+ AtomicReference<String> atomUrl = new AtomicReference<>(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ JdbcParamUtils.filterJdbcUrl(atomUrl.get());
+ });
- url =
"jdbc:mysql://localhost:3306/db_name?p1=v1&autoDeserialize=true&p2=v2";
- url = JdbcParamUtils.filterJdbcUrl(url);
- Assertions.assertEquals(
-
"jdbc:mysql://localhost:3306/db_name?p1=v1&p2=v2&allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false",
- url);
+ // value is not security
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=allowLocalInfile";
+ atomUrl.set(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ JdbcParamUtils.filterJdbcUrl(atomUrl.get());
+ });
+
+ // contains #
+ url = "jdbc:mysql://127.0.0.1:10000/db_name?p1=v1&#p2=v2";
+ atomUrl.set(url);
+ Assertions.assertThrows(
+ LinkisSecurityException.class,
+ () -> {
+ JdbcParamUtils.filterJdbcUrl(atomUrl.get());
+ });
}
@Test
diff --git
a/linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java
b/linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java
index dc2819a2a..0b5099cce 100644
---
a/linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java
+++
b/linkis-public-enhancements/linkis-datasource/linkis-metadata-query/service/jdbc/src/main/java/org/apache/linkis/metadata/query/service/mysql/SqlConnection.java
@@ -18,18 +18,15 @@
package org.apache.linkis.metadata.query.service.mysql;
import org.apache.linkis.common.conf.CommonVars;
+import org.apache.linkis.common.utils.SecurityUtils;
import org.apache.linkis.metadata.query.common.domain.MetaColumnInfo;
-import org.apache.commons.lang.StringUtils;
-
import java.io.Closeable;
import java.io.IOException;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
-import scala.annotation.meta.param;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,60 +80,15 @@ public class SqlConnection implements Closeable {
return;
}
- // enable strong security
- if (MYSQL_STRONG_SECURITY_ENABLE.getValue()) {
- LOG.info(
- "mysql metadata use strong security configuration. Remove all
connection parameters.");
- extraParams.clear();
- }
-
- // Delete suspected vulnerability parameters
- Iterator<Map.Entry<String, Object>> iterator =
extraParams.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<String, Object> entry = iterator.next();
- String key = entry.getKey();
- if (StringUtils.isBlank(key)
- || entry.getValue() == null
- || StringUtils.isBlank(entry.getValue().toString())) {
- iterator.remove();
- continue;
- }
- String value = entry.getValue().toString();
- if (keyAndValueIsNotSecurity(key, value, "allowLoadLocalInfile")
- || keyAndValueIsNotSecurity(key, value, "autoDeserialize")
- || keyAndValueIsNotSecurity(key, value, "allowLocalInfile")
- || keyAndValueIsNotSecurity(key, value, "allowUrlInLocalInfile")
- || keyAndValueIsNotSecurity(key, value, "#")) {
- iterator.remove();
- LOG.warn("mysql metadata sensitive param : key={} and value={}", key,
value);
- }
- }
+ // security check
+ SecurityUtils.checkJdbcSecurity(extraParams);
- // Set all vulnerability parameters to false
- extraParams.put("allowLoadLocalInfile", "false");
- extraParams.put("autoDeserialize", "false");
- extraParams.put("allowLocalInfile", "false");
- extraParams.put("allowUrlInLocalInfile", "false");
+ // append force params
+ SecurityUtils.appendMysqlForceParams(extraParams);
// print extraParams
- StringBuilder sb = new StringBuilder("mysql metadata url extraParams: [ ");
- for (Map.Entry<String, Object> paramEntry : extraParams.entrySet()) {
-
sb.append(paramEntry.getKey()).append("=").append(paramEntry.getValue()).append("
,");
- }
- sb.deleteCharAt(sb.length() - 1);
- sb.append("]");
- LOG.info(sb.toString());
- }
-
- private boolean keyAndValueIsNotSecurity(String key, String value, String
param) {
- return !(isSecurity(key, param) && isSecurity(value, param));
- }
-
- private boolean isSecurity(String noSecurityKey, String param) {
- if (StringUtils.isBlank(param) || StringUtils.isBlank(noSecurityKey)) {
- return true;
- }
- return !noSecurityKey.toLowerCase().contains(param.toLowerCase());
+ String logStr = SecurityUtils.parseParamsMapToMysqlParamUrl(extraParams);
+ LOG.info("mysql metadata url extraParams: {}", logStr);
}
public List<String> getAllDatabases() throws SQLException {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]