Skip to content

Commit a652276

Browse files
authored
[feat:4107] Mysql connection security check extract utils (#4108)
* mysql connection security check extract utils
1 parent 584ea70 commit a652276

File tree

10 files changed

+462
-166
lines changed

10 files changed

+462
-166
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ git push origin dev-fix dev-fix
145145

146146
- 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)
147147
Whether it is a bug fix or a new feature development, please submit a PR to the dev-* branch
148-
- 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)
148+
- 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)
149149
- If the PR contains new features, the document update should be included in this PR
150150
- If this PR is not ready to merge, please add [WIP] prefix to the head of the name (WIP = work-in-progress)
151151
- All submissions to dev-* branches need to go through at least one review before they can be merged

CONTRIBUTING_CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ git push origin dev-fix dev-fix
139139

140140
- 如果您还不知道怎样向开源项目发起 PR,请参考[About pull requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
141141
- 无论是 Bug 修复,还是新功能开发,请将 PR 提交到 dev-* 分支
142-
- PR 和提交名称遵循 `<type>(<scope>): <subject>` 原则,详情可以参考[Commit message 和 Change log 编写指南](https://linkis.apache.org/zh-CN/community/development-specification/commit-message)
142+
- PR 和提交名称遵循 `<type>(<scope>): <subject>` 原则,详情可以参考[Commit message 和 Change log 编写指南](https://linkis.apache.org/zh-CN/docs/1.3.1/development/development-specification/commit-message)
143143
- 如果 PR 中包含新功能,理应将文档更新包含在本次 PR 中
144144
- 如果本次 PR 尚未准备好合并,请在名称头部加上 [WIP] 前缀(WIP = work-in-progress)
145145
- 所有提交到 dev-* 分支的提交至少需要经过一次 Review 才可以被合并

docs/info-1.3.1.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## 参数变化
22

3-
| 模块名(服务名) | 类型 | 参数名 | 默认值 | 描述 |
4-
|---------------------------------------------------|-----|----------------------------------------------------------------------|-------| ------------------------------------------------------- |
5-
| ps-linkismanager | 修改 | pipeline.output.isoverwtite <br/>-><br/> pipeline.output.isoverwrite | true |取值范围:true或false|
6-
| linkis-engineconn-plugins <br/> linkis-datasource | 新增 | linkis.mysql.strong.security.enable | false |取值范围:true或false|
3+
| 模块名(服务名) | 类型 | 参数名 | 默认值 | 描述 |
4+
|---------------------------------------------------|-----|----------------------------------------------------------------------|-------|-----------------|
5+
| ps-linkismanager | 修改 | pipeline.output.isoverwtite <br/>-><br/> pipeline.output.isoverwrite | true | 取值范围:true或false |
6+
| linkis-engineconn-plugins <br/> linkis-datasource | 新增 | linkis.mysql.strong.security.enable | false | 取值范围:true或false |
7+
| linkis-common | 新增 | linkis.mysql.force.params | allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false | mysql连接强制携带参数 |
8+
| linkis-common | 新增 | linkis.mysql.sensitive.params | allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,# | mysql连接安全校验参数 |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.linkis.common.exception;
19+
20+
public class LinkisSecurityException extends LinkisRuntimeException {
21+
22+
@Override
23+
public ExceptionLevel getLevel() {
24+
return null;
25+
}
26+
27+
public LinkisSecurityException(int errCode, String desc) {
28+
super(errCode, desc);
29+
}
30+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.linkis.common.utils;
19+
20+
import org.apache.linkis.common.conf.CommonVars;
21+
import org.apache.linkis.common.conf.CommonVars$;
22+
import org.apache.linkis.common.exception.LinkisSecurityException;
23+
24+
import org.apache.commons.lang3.StringUtils;
25+
26+
import java.util.HashMap;
27+
import java.util.Iterator;
28+
import java.util.LinkedHashMap;
29+
import java.util.Map;
30+
import java.util.stream.Collectors;
31+
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
public abstract class SecurityUtils {
36+
37+
private static final Logger logger = LoggerFactory.getLogger(SecurityUtils.class);
38+
39+
private static final String COMMA = ",";
40+
41+
private static final String EQUAL_SIGN = "=";
42+
43+
private static final String AND_SYMBOL = "&";
44+
45+
private static final String QUESTION_MARK = "?";
46+
47+
/** allowLoadLocalInfile,allowLoadLocalInfiled,# */
48+
public static final CommonVars<String> MYSQL_SENSITIVE_PARAMS =
49+
CommonVars$.MODULE$.apply(
50+
"linkis.mysql.sensitive.params",
51+
"allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,#");
52+
53+
/**
54+
* "allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false"
55+
*/
56+
public static final CommonVars<String> MYSQL_FORCE_PARAMS =
57+
CommonVars$.MODULE$.apply(
58+
"linkis.mysql.force.params",
59+
"allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false");
60+
61+
public static final CommonVars<String> MYSQL_STRONG_SECURITY_ENABLE =
62+
CommonVars$.MODULE$.apply("linkis.mysql.strong.security.enable", "false");
63+
64+
/**
65+
* mysql url append force params
66+
*
67+
* @param url
68+
* @return
69+
*/
70+
public static String appendMysqlForceParams(String url) {
71+
if (StringUtils.isBlank(url)) {
72+
return "";
73+
}
74+
75+
String extraParamString = MYSQL_FORCE_PARAMS.getValue();
76+
77+
if (url.endsWith(QUESTION_MARK)) {
78+
url = url + extraParamString;
79+
} else if (url.lastIndexOf(QUESTION_MARK) < 0) {
80+
url = url + QUESTION_MARK + extraParamString;
81+
} else {
82+
url = url + AND_SYMBOL + extraParamString;
83+
}
84+
return url;
85+
}
86+
87+
public static void appendMysqlForceParams(Map<String, Object> extraParams) {
88+
extraParams.putAll(parseMysqlUrlParamsToMap(MYSQL_FORCE_PARAMS.getValue()));
89+
}
90+
91+
public static String checkJdbcSecurity(String url) {
92+
logger.info("checkJdbcSecurity origin url: {}", url);
93+
if (StringUtils.isBlank(url)) {
94+
throw new LinkisSecurityException(35000, "Invalid mysql connection cul, url is empty");
95+
}
96+
if (url.endsWith(QUESTION_MARK) || !url.contains(QUESTION_MARK)) {
97+
logger.info("checkJdbcSecurity target url: {}", url);
98+
return url;
99+
}
100+
String[] items = url.split("\\?");
101+
if (items.length != 2) {
102+
logger.warn("Invalid url: {}", url);
103+
throw new LinkisSecurityException(35000, "Invalid mysql connection cul: " + url);
104+
}
105+
Map<String, Object> params = parseMysqlUrlParamsToMap(items[1]);
106+
Map<String, Object> securityMap = checkJdbcSecurity(params);
107+
String paramUrl = parseParamsMapToMysqlParamUrl(securityMap);
108+
url = items[0] + QUESTION_MARK + paramUrl;
109+
logger.info("checkJdbcSecurity target url: {}", url);
110+
return url;
111+
}
112+
113+
/**
114+
* check jdbc params
115+
*
116+
* @param paramsMap
117+
*/
118+
public static Map<String, Object> checkJdbcSecurity(Map<String, Object> paramsMap) {
119+
if (paramsMap == null) {
120+
return new HashMap<>();
121+
}
122+
123+
// mysql url strong security
124+
if (Boolean.valueOf(MYSQL_STRONG_SECURITY_ENABLE.getValue())) {
125+
paramsMap.clear();
126+
return paramsMap;
127+
}
128+
129+
Iterator<Map.Entry<String, Object>> iterator = paramsMap.entrySet().iterator();
130+
while (iterator.hasNext()) {
131+
Map.Entry<String, Object> entry = iterator.next();
132+
String key = entry.getKey();
133+
Object value = entry.getValue();
134+
if (StringUtils.isBlank(key) || value == null || StringUtils.isBlank(value.toString())) {
135+
logger.warn("Invalid parameter key or value is blank.");
136+
iterator.remove();
137+
continue;
138+
}
139+
if (isNotSecurity(key, value.toString())) {
140+
logger.warn("Sensitive param : key={} and value={}", key, value);
141+
throw new LinkisSecurityException(
142+
35000,
143+
"Invalid mysql connection parameters: " + parseParamsMapToMysqlParamUrl(paramsMap));
144+
}
145+
}
146+
return paramsMap;
147+
}
148+
149+
public static String parseParamsMapToMysqlParamUrl(Map<String, Object> forceParams) {
150+
if (forceParams == null) {
151+
return "";
152+
}
153+
return forceParams.entrySet().stream()
154+
.map(e -> String.join(EQUAL_SIGN, e.getKey(), String.valueOf(e.getValue())))
155+
.collect(Collectors.joining(AND_SYMBOL));
156+
}
157+
158+
private static Map<String, Object> parseMysqlUrlParamsToMap(String paramsUrl) {
159+
String[] params = paramsUrl.split(AND_SYMBOL);
160+
Map<String, Object> map = new LinkedHashMap<>(params.length);
161+
for (String param : params) {
162+
String[] item = param.split(EQUAL_SIGN);
163+
if (item.length != 2) {
164+
logger.warn("mysql force param {} error.", param);
165+
continue;
166+
}
167+
map.put(item[0], item[1]);
168+
}
169+
return map;
170+
}
171+
172+
private static boolean isNotSecurity(String key, String value) {
173+
boolean res = true;
174+
String sensitiveParamsStr = MYSQL_SENSITIVE_PARAMS.getValue();
175+
if (StringUtils.isBlank(sensitiveParamsStr)) {
176+
return false;
177+
}
178+
String[] forceParams = sensitiveParamsStr.split(COMMA);
179+
for (String forceParam : forceParams) {
180+
if (isNotSecurity(key, value, forceParam)) {
181+
res = false;
182+
break;
183+
}
184+
}
185+
return !res;
186+
}
187+
188+
private static boolean isNotSecurity(String key, String value, String param) {
189+
return key.toLowerCase().contains(param.toLowerCase())
190+
|| value.toLowerCase().contains(param.toLowerCase());
191+
}
192+
}

0 commit comments

Comments
 (0)