Skip to content

Commit

Permalink
[feat:4107] Mysql connection security check extract utils (#4108)
Browse files Browse the repository at this point in the history
* mysql connection security check extract utils
  • Loading branch information
aiceflower authored Jan 12, 2023
1 parent 584ea70 commit a652276
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 166 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 才可以被合并
Expand Down
10 changes: 6 additions & 4 deletions docs/info-1.3.1.md
Original file line number Diff line number Diff line change
@@ -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连接安全校验参数 |
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading

0 comments on commit a652276

Please sign in to comment.