diff --git a/neutrino-proxy-admin/config/dev.env.js b/neutrino-proxy-admin/config/dev.env.js index 03fd65b0..6bb64a3f 100644 --- a/neutrino-proxy-admin/config/dev.env.js +++ b/neutrino-proxy-admin/config/dev.env.js @@ -1,7 +1,7 @@ module.exports = { NODE_ENV: '"development"', ENV_CONFIG: '"dev"', - BASE_API: '"https://neutrino-proxy.asgc.fun/neutrino-proxy-server"', + BASE_API: '"http://localhost:8888/neutrino-proxy-server"', USER_NAME: '"visitor"', USER_PWD: '"123456"' } diff --git a/neutrino-proxy-admin/config/prod.env.js b/neutrino-proxy-admin/config/prod.env.js index 0c43ea73..c388e184 100644 --- a/neutrino-proxy-admin/config/prod.env.js +++ b/neutrino-proxy-admin/config/prod.env.js @@ -1,5 +1,5 @@ module.exports = { NODE_ENV: '"production"', ENV_CONFIG: '"prod"', - BASE_API: '"https://api-prod"' + BASE_API: '""' } diff --git a/neutrino-proxy-admin/package.json b/neutrino-proxy-admin/package.json index 943c7d6b..d2bdb819 100644 --- a/neutrino-proxy-admin/package.json +++ b/neutrino-proxy-admin/package.json @@ -23,7 +23,7 @@ "codemirror": "5.32.0", "dropzone": "5.2.0", "echarts": "3.8.5", - "element-ui": "2.0.8", + "element-ui": "^2.15.14", "file-saver": "1.3.3", "font-awesome": "4.7.0", "js-cookie": "2.2.0", @@ -70,7 +70,7 @@ "friendly-errors-webpack-plugin": "1.6.1", "html-webpack-plugin": "2.30.1", "node-notifier": "5.1.2", - "node-sass": "^4.7.2", + "node-sass": "^9.0.0", "optimize-css-assets-webpack-plugin": "3.2.0", "ora": "1.3.0", "portfinder": "1.0.13", diff --git a/neutrino-proxy-admin/src/api/portMapping.js b/neutrino-proxy-admin/src/api/portMapping.js index cf7989e9..315e4f50 100644 --- a/neutrino-proxy-admin/src/api/portMapping.js +++ b/neutrino-proxy-admin/src/api/portMapping.js @@ -44,3 +44,22 @@ export function updateEnableStatus(id, enable) { } }) } + +export function portMappingBindSecurityGroup(id, securityGroupId) { + return request({ + url: '/port-mapping/bind/security-group', + method: 'post', + data: { + id: id, + securityGroupId: securityGroupId + } + }) +} + +export function portMappingUnbindSecurityGroup(id) { + return request({ + url: `/port-mapping/unbind/security-group?id=${id}`, + method: 'post' + }) +} + diff --git a/neutrino-proxy-admin/src/api/securityGroup.js b/neutrino-proxy-admin/src/api/securityGroup.js new file mode 100644 index 00000000..c9456516 --- /dev/null +++ b/neutrino-proxy-admin/src/api/securityGroup.js @@ -0,0 +1,97 @@ +import request from '@/utils/request' + +const baseUri = '/security'; + +export function fetchGroupList() { + return request({ + url: `${baseUri}/group/s`, + method: 'get' + }) +} + +export function fetchGroupOne(groupId) { + return request({ + url: `${baseUri}/group/getOne?groupId=${groupId}`, + method: 'get' + }) +} + +export function createGroup(data) { + return request({ + url: `${baseUri}/group/create`, + method: 'post', + data + }) +} + +export function updateGroup(data) { + return request({ + url: `${baseUri}/group/update`, + method: 'post', + data + }) +} + +export function deleteGroup(groupId) { + return request({ + url: `${baseUri}/group/delete?groupId=${groupId}`, + method: 'post' + }) +} + +export function enableGroup(groupId) { + return request({ + url: `${baseUri}/group/enable?groupId=${groupId}`, + method: 'post' + }) +} + +export function disableGroup(groupId) { + return request({ + url: `${baseUri}/group/disable?groupId=${groupId}`, + method: 'post' + }) +} + +export function fetchRuleList(groupId) { + return request({ + url: `${baseUri}/rule/s?groupId=${groupId}`, + method: 'get' + }) +} + +export function createRule(data) { + return request({ + url: `${baseUri}/rule/create`, + method: 'post', + data + }) +} + +export function updateRule(data) { + return request({ + url: `${baseUri}/rule/update`, + method: 'post', + data + }) +} +export function deleteRule(ruleId) { + return request({ + url: `${baseUri}/rule/delete?ruleId=${ruleId}`, + method: 'post' + }) +} + +export function enableRule(ruleId) { + return request({ + url: `${baseUri}/rule/enable?ruleId=${ruleId}`, + method: 'post' + }) +} + +export function disableRule(ruleId) { + return request({ + url: `${baseUri}/rule/disable?ruleId=${ruleId}`, + method: 'post' + }) +} \ No newline at end of file diff --git a/neutrino-proxy-admin/src/components/Link/linkPopover.vue b/neutrino-proxy-admin/src/components/Link/linkPopover.vue new file mode 100644 index 00000000..b711f3df --- /dev/null +++ b/neutrino-proxy-admin/src/components/Link/linkPopover.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/neutrino-proxy-admin/src/lang/zh.js b/neutrino-proxy-admin/src/lang/zh.js index 4cc16d9b..a9c3b139 100644 --- a/neutrino-proxy-admin/src/lang/zh.js +++ b/neutrino-proxy-admin/src/lang/zh.js @@ -48,6 +48,8 @@ export default { user: '用户管理', system: '系统管理', portPool: '端口池管理', + securityGroup: '安全组管理', + securityRule: '安全规则管理', portGroup: '端口分组管理', protocal: '协议管理', proxy: '代理配置', @@ -128,6 +130,7 @@ export default { confirm: '确 定', userId: '用户ID', userName: '用户名', + name: '名称', group: '分组', groupName: '分组名称', groupPossessor: '分组所属', @@ -169,7 +172,19 @@ export default { totalFlow: '总流量', protocalName: '协议', supportStatus: '支持状态', - domainName: '域名' + domainName: '域名', + securityGroup: '安全组', + defaultPassType: '默认放行类型', + ruleName: '规则名称', + rule: '规则内容', + passType: '放行类型', + priority: '优先级', + ruleConfig: '规则配置', + portMappingBindSecurityGroup: '绑定安全组', + securityGroupBindPortMapping: '端口映射绑定', + bind: '绑定', + unbind: '解绑', + bindOtherSecurityGroup: '已绑定其他安全组' }, button: { lookOver: '查看' diff --git a/neutrino-proxy-admin/src/router/index.js b/neutrino-proxy-admin/src/router/index.js index 3c246651..47a870f2 100644 --- a/neutrino-proxy-admin/src/router/index.js +++ b/neutrino-proxy-admin/src/router/index.js @@ -77,6 +77,8 @@ export const asyncRouterMap = [ { path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }}, { path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }}, { path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }}, + { path: 'securityGroup', component: _import('system/securityGroup'), name: 'securityGroup', meta: { title: 'securityGroup' }}, + { path: 'securityRule', component: _import('system/securityRule'), name: 'securityRule', meta: { title: 'securityRule' }, hidden: true}, { path: 'protocal', component: _import('system/protocal'), name: 'protocal', meta: { title: 'protocal' }}, { path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }} ] diff --git a/neutrino-proxy-admin/src/views/proxy/portMapping.vue b/neutrino-proxy-admin/src/views/proxy/portMapping.vue index 9caf8f0e..9b77a665 100644 --- a/neutrino-proxy-admin/src/views/proxy/portMapping.vue +++ b/neutrino-proxy-admin/src/views/proxy/portMapping.vue @@ -176,6 +176,12 @@ + + + + + + @@ -204,6 +210,7 @@ + \ No newline at end of file diff --git a/neutrino-proxy-admin/src/views/system/securityRule.vue b/neutrino-proxy-admin/src/views/system/securityRule.vue new file mode 100644 index 00000000..d103e6e6 --- /dev/null +++ b/neutrino-proxy-admin/src/views/system/securityRule.vue @@ -0,0 +1,349 @@ + + + diff --git a/neutrino-proxy-client/src/main/resources/app.yml b/neutrino-proxy-client/src/main/resources/app.yml index c513862e..7ed0a997 100644 --- a/neutrino-proxy-client/src/main/resources/app.yml +++ b/neutrino-proxy-client/src/main/resources/app.yml @@ -44,7 +44,7 @@ neutrino: # 是否启用SSL(注意:该配置必须和server-port对应上) ssl-enable: ${SSL_ENABLE:true} # 客户端连接唯一凭证 - license-key: ${LICENSE_KEY:} + license-key: ${LICENSE_KEY:b0a907332b474b25897c4dcb31fc7eb6} # 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成) client-id: ${CLIENT_ID:} # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) diff --git a/neutrino-proxy-core/pom.xml b/neutrino-proxy-core/pom.xml index fc566dbf..f29d6890 100644 --- a/neutrino-proxy-core/pom.xml +++ b/neutrino-proxy-core/pom.xml @@ -30,6 +30,11 @@ hutool-core ${hutool.version} + + cn.hutool + hutool-cache + ${hutool.version} + diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/IpUtil.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/IpUtil.java new file mode 100644 index 00000000..74ec5859 --- /dev/null +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/IpUtil.java @@ -0,0 +1,18 @@ +package org.dromara.neutrinoproxy.core.util; + +import io.netty.channel.ChannelHandlerContext; + +import java.net.InetSocketAddress; + +public class IpUtil extends org.noear.solon.core.util.IpUtil { + + public static String getRemoteIp(ChannelHandlerContext ctx) { + String remoteAddress = ""; + InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + if (socketAddress != null) { + remoteAddress = socketAddress.getAddress().getHostAddress(); + } + return remoteAddress; + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/SecurityRulePassTypeEnum.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/SecurityRulePassTypeEnum.java new file mode 100644 index 00000000..07cd72c9 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/SecurityRulePassTypeEnum.java @@ -0,0 +1,16 @@ +package org.dromara.neutrinoproxy.server.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum SecurityRulePassTypeEnum { + DENY(0, "deny"), + ALLOW(1, "allow"), + NONE(-1, "none") + ; + + private final Integer code; + private final String desc; +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java index e9f536e0..85cb748a 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java @@ -13,6 +13,8 @@ import org.dromara.neutrinoproxy.server.util.ParamCheckUtil; import org.apache.commons.lang3.StringUtils; import org.noear.solon.annotation.*; +import java.util.List; + /** * 端口映射 * @author: aoshiguchen @@ -64,7 +66,7 @@ public class PortMappingController { @Post @Mapping("/update") - public PortMappingUpdateRes update(PortMappingUpdateReq req) { + public void update(PortMappingUpdateReq req) { ParamCheckUtil.checkNotNull(req, "req"); ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId"); ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort"); @@ -89,7 +91,7 @@ public class PortMappingController { req.setProxyTimeoutMs(0L); } - return portMappingService.update(req); + portMappingService.update(req); } @Get @@ -119,4 +121,26 @@ public class PortMappingController { portMappingService.delete(req.getId()); } + + /** + * 绑定安全组 + * @param req portMappingId和securityGroupId + */ + @Post + @Mapping("/bind/security-group") + public void bindSecurityGroup(PortMappingBindSecurityGroupReq req) { + portMappingService.portBindSecurityGroup(req.getId(), req.getSecurityGroupId()); + } + + /** + * 安全组解绑 + * @param id 端口映射Id + */ + @Post + @Mapping("/unbind/security-group") + public void unbindSecurityGroup(Integer id) { + portMappingService.portUnbindSecurityGroup(id); + } + + } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/SecurityController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/SecurityController.java new file mode 100644 index 00000000..18dc335e --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/SecurityController.java @@ -0,0 +1,117 @@ +package org.dromara.neutrinoproxy.server.controller; + +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityGroupCreateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityGroupUpdateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityRuleCreateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityRuleUpdateReq; +import org.dromara.neutrinoproxy.server.controller.res.system.SecurityGroupRes; +import org.dromara.neutrinoproxy.server.controller.res.system.SecurityRuleRes; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityGroupDO; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityRuleDO; +import org.dromara.neutrinoproxy.server.service.PortMappingService; +import org.dromara.neutrinoproxy.server.service.SecurityGroupService; +import org.noear.solon.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@Controller +@Mapping("/security") +public class SecurityController { + + @Inject + private SecurityGroupService groupService; + + @Inject + private PortMappingService portMappingService; + + /** + * 获取当前用户权限下的安全组 + */ + @Get + @Mapping("/group/s") + public List getGroups() { + List groupDOList = groupService.queryGroupList(); + return groupDOList.stream().map(SecurityGroupDO::toRes).collect(Collectors.toList()); + } + + @Get + @Mapping("/group/getOne") + public SecurityGroupRes getGroupOne(Integer groupId) { + return groupService.queryGroupOne(groupId).toRes(); + } + + @Post + @Mapping("/group/create") + public void createGroup(SecurityGroupCreateReq req) { + groupService.createGroup(req); + } + + @Post + @Mapping("/group/update") + public void updateGroup(SecurityGroupUpdateReq req) { + groupService.updateGroup(req); + } + + /** + * 将级联删除对应规则,并更新缓存 + * @param groupId 安全组Id + */ + @Post + @Mapping("/group/delete") + public void deleteGroup(Integer groupId) { + groupService.deleteGroup(groupId); + } + + @Post + @Mapping("/group/enable") + public void enableGroup(Integer groupId) { + groupService.setGroupStatus(groupId, EnableStatusEnum.ENABLE); + } + + @Post + @Mapping("/group/disable") + public void disableGroup(Integer groupId) { + groupService.setGroupStatus(groupId, EnableStatusEnum.DISABLE); + } + + @Get + @Mapping("/rule/s") + public List getRulesByGroupId(Integer groupId) { + List ruleDOList = groupService.queryRuleListByGroupId(groupId); + return ruleDOList.stream().map(SecurityRuleDO::toRes).collect(Collectors.toList()); + } + + @Post + @Mapping("/rule/create") + public void createRule(SecurityRuleCreateReq req) { + groupService.createRule(req); + } + + @Post + @Mapping("/rule/update") + public void updateRule(SecurityRuleUpdateReq req) { + groupService.updateRule(req); + } + + @Post + @Mapping("/rule/delete") + public void deleteRule(Integer ruleId) { + groupService.deleteRule(ruleId); + } + + + @Post + @Mapping("/rule/enable") + public void enableRule(Integer ruleId) { + groupService.setRuleStatus(ruleId, EnableStatusEnum.ENABLE); + } + + @Post + @Mapping("/rule/disable") + public void disableRule(Integer ruleId) { + groupService.setRuleStatus(ruleId, EnableStatusEnum.DISABLE); + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingBindSecurityGroupReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingBindSecurityGroupReq.java new file mode 100644 index 00000000..0b4df7ca --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingBindSecurityGroupReq.java @@ -0,0 +1,12 @@ +package org.dromara.neutrinoproxy.server.controller.req.proxy; + +import lombok.Data; + +@Data +public class PortMappingBindSecurityGroupReq { + + private Integer id; + + private Integer securityGroupId; + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java index 5270e8c6..1ee94e0c 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java @@ -62,6 +62,12 @@ public class PortMappingCreateReq { * 代理超时时间 */ private Long proxyTimeoutMs; + + /** + * 安全组Id + */ + private Integer securityGroupId; + /** * 描述 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java index 5b1a94b9..6ba18c1c 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java @@ -66,6 +66,12 @@ public class PortMappingUpdateReq { * 代理超时时间 */ private Long proxyTimeoutMs; + + /** + * 安全组Id + */ + private Integer securityGroupId; + /** * 描述 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupCreateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupCreateReq.java new file mode 100644 index 00000000..e96590f8 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupCreateReq.java @@ -0,0 +1,23 @@ +package org.dromara.neutrinoproxy.server.controller.req.system; + +import lombok.Data; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +@Data +public class SecurityGroupCreateReq { + /** + * 组名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 通过类型 + */ + private SecurityRulePassTypeEnum defaultPassType; + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupUpdateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupUpdateReq.java new file mode 100644 index 00000000..6dc8ffe1 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityGroupUpdateReq.java @@ -0,0 +1,25 @@ +package org.dromara.neutrinoproxy.server.controller.req.system; + +import lombok.Data; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +@Data +public class SecurityGroupUpdateReq { + + private Integer id; + + /** + * 组名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 通过类型 + */ + private SecurityRulePassTypeEnum defaultPassType; +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleCreateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleCreateReq.java new file mode 100644 index 00000000..95285858 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleCreateReq.java @@ -0,0 +1,57 @@ +package org.dromara.neutrinoproxy.server.controller.req.system; + +import cn.hutool.core.net.Ipv4Util; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +import java.util.Date; + +@Data +@ToString +@Accessors(chain = true) +public class SecurityRuleCreateReq { + + /** + * 所属安全组 + */ + private Integer groupId; + + /** + * 规则名 + */ + private String name; + + /** + * 规则描述 + */ + private String description; + + /** + * 规则,ipv6只支持单个ip判断 + * 单个ip:192.168.1.1,0:0:0:0:0:0:10.0.0.1 + * 范围类型:192.168.1.0-192.168.1.255 + * 掩码类型:192.168.1.0/24 + * 泛型:0.0.0.0/ALL + * 每个类型中间以英文逗号分隔 + */ + private String rule; + + /** + * 放行类型,reject 或 allow + * {@link SecurityRulePassTypeEnum} + */ + private SecurityRulePassTypeEnum passType; + + /** + * 优先级,数字越小,优先级越高 + */ + private Integer priority; + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleUpdateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleUpdateReq.java new file mode 100644 index 00000000..47c7e2fb --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/system/SecurityRuleUpdateReq.java @@ -0,0 +1,51 @@ +package org.dromara.neutrinoproxy.server.controller.req.system; + +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +@Data +@ToString +@Accessors(chain = true) +public class SecurityRuleUpdateReq { + + private Integer id; + + /** + * 所属安全组 + */ + private Integer groupId; + + /** + * 规则名 + */ + private String name; + + /** + * 规则描述 + */ + private String description; + + /** + * 规则,ipv6只支持单个ip判断 + * 单个ip:192.168.1.1,0:0:0:0:0:0:10.0.0.1 + * 范围类型:192.168.1.0-192.168.1.255 + * 掩码类型:192.168.1.0/24 + * 泛型:0.0.0.0/ALL + * 每个类型中间以英文逗号分隔 + */ + private String rule; + + /** + * 放行类型,reject 或 allow + * {@link SecurityRulePassTypeEnum} + */ + private SecurityRulePassTypeEnum passType; + + /** + * 优先级,数字越小,优先级越高 + */ + private Integer priority; + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java index 8a022cee..19203115 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java @@ -101,6 +101,12 @@ public class PortMappingListRes { * 描述 */ private String description; + + /** + * 安全组Id + */ + private Integer securityGroupId; + /** * 创建时间 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityGroupRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityGroupRes.java new file mode 100644 index 00000000..23bbde2b --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityGroupRes.java @@ -0,0 +1,49 @@ +package org.dromara.neutrinoproxy.server.controller.res.system; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +import java.util.Date; + +@Data +@Accessors(chain = true) +public class SecurityGroupRes { + + private Integer id; + + /** + * 组名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 启用状态 + * {@link EnableStatusEnum} + */ + private String enable; + + /** + * 默认放行类型 + * {@link SecurityRulePassTypeEnum} + */ + private String defaultPassType; + + /** + * 创建时间 + */ + private String createTime; + /** + * 更新时间 + */ + private String updateTime; + + + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityRuleRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityRuleRes.java new file mode 100644 index 00000000..10ce5043 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/SecurityRuleRes.java @@ -0,0 +1,68 @@ +package org.dromara.neutrinoproxy.server.controller.res.system; + +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; + +import java.util.Date; + +@Data +@ToString +@Accessors(chain = true) +public class SecurityRuleRes { + + private Integer id; + + /** + * 所属安全组 + */ + private Integer groupId; + + /** + * 规则名 + */ + private String name; + + /** + * 规则描述 + */ + private String description; + + /** + * 规则,ipv6只支持单个ip判断 + * 单个ip:192.168.1.1,0:0:0:0:0:0:10.0.0.1 + * 范围类型:192.168.1.0-192.168.1.255 + * 掩码类型:192.168.1.0/24 + * 泛型:0.0.0.0/ALL + * 每个类型中间以英文逗号分隔 + */ + private String rule; + + /** + * 放行类型,reject 或 allow + * {@link SecurityRulePassTypeEnum} + */ + private String passType; + + /** + * 优先级,数字越小,优先级越高 + */ + private Integer priority; + + /** + * 启用状态 + */ + private String enable; + + /** + * 创建时间 + */ + private String createTime; + /** + * 更新时间 + */ + private String updateTime; + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityGroupMapper.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityGroupMapper.java new file mode 100644 index 00000000..10dbe111 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityGroupMapper.java @@ -0,0 +1,7 @@ +package org.dromara.neutrinoproxy.server.dal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityGroupDO; + +public interface SecurityGroupMapper extends BaseMapper { +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityRuleMapper.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityRuleMapper.java new file mode 100644 index 00000000..9ded6d66 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/SecurityRuleMapper.java @@ -0,0 +1,7 @@ +package org.dromara.neutrinoproxy.server.dal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityRuleDO; + +public interface SecurityRuleMapper extends BaseMapper { +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java index 5e010d59..b6f2af36 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java @@ -21,6 +21,7 @@ */ package org.dromara.neutrinoproxy.server.dal.entity; +import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -92,6 +93,12 @@ public class PortMappingDO { * {@link EnableStatusEnum} */ private Integer enable; + + /** + * 安全组Id + */ + private Integer securityGroupId = 0; // 设置为null不生效,不知道为啥 + /** * 创建时间 */ @@ -101,23 +108,9 @@ public class PortMappingDO { */ private Date updateTime; - public PortMappingListRes toRes() { PortMappingListRes res = new PortMappingListRes(); - res.setId(id); - res.setLicenseId(licenseId); - res.setProtocal(protocal); - res.setSubdomain(subdomain); - res.setServerPort(serverPort); - res.setClientIp(clientIp); - res.setClientPort(clientPort); - res.setDescription(description); - res.setIsOnline(isOnline); - res.setProxyResponses(proxyResponses); - res.setProxyTimeoutMs(proxyTimeoutMs); - res.setEnable(enable); - res.setCreateTime(createTime); - res.setUpdateTime(updateTime); + BeanUtil.copyProperties(this, res); return res; } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityGroupDO.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityGroupDO.java new file mode 100644 index 00000000..a7b1458c --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityGroupDO.java @@ -0,0 +1,74 @@ +package org.dromara.neutrinoproxy.server.dal.entity; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; +import org.dromara.neutrinoproxy.server.controller.res.system.SecurityGroupRes; + +import java.util.Date; + +@Data +@ToString +@Accessors(chain = true) +@TableName("security_group") +public class SecurityGroupDO { + + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 组名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 用户id + */ + private Integer userId; + + /** + * 启用状态 + * {@link EnableStatusEnum} + */ + private EnableStatusEnum enable; + + /** + * 默认放行类型 + * {@link SecurityRulePassTypeEnum} + */ + private SecurityRulePassTypeEnum defaultPassType; + + /** + * 创建时间 + */ + private Date createTime; + /** + * 更新时间 + */ + private Date updateTime; + + public SecurityGroupRes toRes() { + SecurityGroupRes res = new SecurityGroupRes(); + BeanUtil.copyProperties(this, res); + res.setEnable(enable.getDesc()) + .setDefaultPassType(defaultPassType.getDesc()) + .setCreateTime(DateUtil.format(this.getCreateTime(), DatePattern.NORM_DATETIME_FORMAT)) + .setUpdateTime(DateUtil.format(this.getUpdateTime(), DatePattern.NORM_DATETIME_FORMAT)); + return res; + } + + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityRuleDO.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityRuleDO.java new file mode 100644 index 00000000..9a3240d4 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/SecurityRuleDO.java @@ -0,0 +1,159 @@ +package org.dromara.neutrinoproxy.server.dal.entity; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.net.Ipv4Util; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; +import org.dromara.neutrinoproxy.server.controller.res.system.SecurityRuleRes; + +import java.util.Date; + +@Data +@ToString +@Accessors(chain = true) +@TableName("security_rule") +public class SecurityRuleDO { + + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * 所属安全组 + */ + private Integer groupId; + + /** + * 规则名 + */ + private String name; + + /** + * 规则描述 + */ + private String description; + + /** + * 规则,ipv6只支持单个ip判断 + * 单个ip:192.168.1.1,0:0:0:0:0:0:10.0.0.1 + * 范围类型:192.168.1.0-192.168.1.255 + * 掩码类型:192.168.1.0/24 + * 泛型:0.0.0.0/ALL + * 每个类型中间以英文逗号分隔 + */ + private String rule; + + /** + * 放行类型,reject 或 allow + * {@link SecurityRulePassTypeEnum} + */ + private SecurityRulePassTypeEnum passType; + + /** + * 优先级,数字越小,优先级越高 + */ + private Integer priority; + + /** + * 用户id + */ + private Integer userId; + + /** + * 启用状态 + * {@link EnableStatusEnum} + */ + private EnableStatusEnum enable; + /** + * 创建时间 + */ + private Date createTime; + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 判断当前规则是否允许指定ip放行 + * @param ip 指定的IP + * @return 放行状态 + */ + public SecurityRulePassTypeEnum judge(String ip) { + + // 被判断的IP地址为空,不做判断 + if (StrUtil.isEmpty(ip)) { + return SecurityRulePassTypeEnum.NONE; + } + + // 没有规则,默认允许访问 + if (StrUtil.isEmpty(rule)) { + return SecurityRulePassTypeEnum.ALLOW; + } + + // ipv6只适配单ip形式 + boolean isIpv6 = ip.contains(":"); + long ipLong = -1L; + if (!isIpv6) { + ipLong = Ipv4Util.ipv4ToLong(ip); + } + + String[] rules = this.rule.split(","); + for (String rule : rules) { + + rule = rule.trim(); + + // 单个ip,ipv6在此步已处理,后面不需要额外判断ipv6的情况 + if (rule.matches("(\\d+\\.){3}\\d+") || isIpv6) { + if (rule.equalsIgnoreCase(ip)) { + return passType == SecurityRulePassTypeEnum.ALLOW ? SecurityRulePassTypeEnum.ALLOW : SecurityRulePassTypeEnum.DENY; + } + } + + // 范围类型 + if (rule.matches("(\\d+\\.){3}\\d+-(\\d+\\.){3}\\d+")) { + String[] ipRange = rule.split("-"); + if (ipRange[0].compareTo(ip) <= 0 && ip.compareTo(ipRange[1]) <= 0) { + return passType == SecurityRulePassTypeEnum.ALLOW ? SecurityRulePassTypeEnum.ALLOW : SecurityRulePassTypeEnum.DENY; + } + } + + // 掩码类型 + if (rule.matches("(\\d+\\.){3}\\d+/\\d+")) { + String[] netIp = rule.split("/"); + Long beginIp = Ipv4Util.getBeginIpLong(netIp[0], Integer.parseInt(netIp[1])); + Long endIp = Ipv4Util.getEndIpLong(netIp[0], Integer.parseInt(netIp[1])); + if (beginIp <= ipLong && ipLong <= endIp) { + return passType == SecurityRulePassTypeEnum.ALLOW ? SecurityRulePassTypeEnum.ALLOW : SecurityRulePassTypeEnum.DENY; + } + } + + if (rule.equalsIgnoreCase("ALL") || rule.equals("0.0.0.0") || rule.equals("0..0.0.0/0")) { + return passType == SecurityRulePassTypeEnum.ALLOW ? SecurityRulePassTypeEnum.ALLOW : SecurityRulePassTypeEnum.DENY; + } + + } + + // 都没有匹配到 + return SecurityRulePassTypeEnum.NONE; + } + + public SecurityRuleRes toRes() { + SecurityRuleRes res = new SecurityRuleRes(); + BeanUtil.copyProperties(this, res); + res.setPassType(this.passType.getDesc()) + .setEnable(this.getEnable().getDesc()) + .setCreateTime(DateUtil.format(this.getCreateTime(), DatePattern.NORM_DATETIME_FORMAT)) + .setUpdateTime(DateUtil.format(this.getUpdateTime(), DatePattern.NORM_DATETIME_FORMAT)) + ; + return res; + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java index 8f723802..d9aa25a9 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java @@ -1,19 +1,23 @@ package org.dromara.neutrinoproxy.server.proxy.core; import cn.hutool.core.util.StrUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.neutrinoproxy.core.Constants; -import org.dromara.neutrinoproxy.core.ProxyMessage; -import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; -import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo; -import org.dromara.neutrinoproxy.server.service.FlowReportService; -import org.dromara.neutrinoproxy.server.util.ProxyUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOption; import io.netty.channel.SimpleChannelInboundHandler; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.util.IpUtil; +import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; +import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo; +import org.dromara.neutrinoproxy.server.service.FlowReportService; +import org.dromara.neutrinoproxy.server.service.PortMappingService; +import org.dromara.neutrinoproxy.server.service.SecurityGroupService; +import org.dromara.neutrinoproxy.server.util.ProxyUtil; import org.noear.solon.Solon; +import org.noear.solon.annotation.Inject; import java.net.InetSocketAddress; @@ -25,6 +29,10 @@ import java.net.InetSocketAddress; @Slf4j public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler { + private final SecurityGroupService securityGroupService = Solon.context().getBean(SecurityGroupService.class); + + private final PortMappingService portMappingService = Solon.context().getBean(PortMappingService.class); + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 当出现异常就关闭连接 @@ -62,8 +70,15 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler { + private final SecurityGroupService securityGroupService = Solon.context().getBean(SecurityGroupService.class); + + private final PortMappingService portMappingService = Solon.context().getBean(PortMappingService.class); + @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception { log.debug("chid>>>{}", ctx.channel().id().asLongText()); @@ -118,6 +126,13 @@ public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler mappingPortToSecurityGroupMap = new ConcurrentHashMap<>(); + public PageInfo page(PageQuery pageQuery, PortMappingListReq req) { if (StringUtils.isNotEmpty(req.getDescription())) { //描述字段为模糊查询,在应用层处理,否则sqlite不支持 @@ -161,10 +167,13 @@ public class PortMappingService implements LifecycleBean { if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); } + + updateMappingPortToSecurityGroupMap(portMappingDO.getServerPort(), req.getSecurityGroupId()); + return new PortMappingCreateRes(); } - public PortMappingUpdateRes update(PortMappingUpdateReq req) { + public void update(PortMappingUpdateReq req) { LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId()); ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST); if (!SystemContextHolder.isAdmin()) { @@ -181,16 +190,10 @@ public class PortMappingService implements LifecycleBean { ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST); PortMappingDO portMappingDO = new PortMappingDO(); - portMappingDO.setId(req.getId()); - portMappingDO.setProtocal(req.getProtocal()); - portMappingDO.setSubdomain(req.getSubdomain()); - portMappingDO.setLicenseId(req.getLicenseId()); - portMappingDO.setServerPort(req.getServerPort()); - portMappingDO.setClientIp(req.getClientIp()); - portMappingDO.setClientPort(req.getClientPort()); - portMappingDO.setProxyResponses(req.getProxyResponses()); - portMappingDO.setProxyTimeoutMs(req.getProxyTimeoutMs()); - portMappingDO.setDescription(req.getDescription()); + BeanUtil.copyProperties(req, portMappingDO); + if (req.getSecurityGroupId() == null) { + portMappingDO.setSecurityGroupId(0); + } portMappingDO.setUpdateTime(new Date()); portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus()); portMappingMapper.updateById(portMappingDO); @@ -204,7 +207,8 @@ public class PortMappingService implements LifecycleBean { if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); } - return new PortMappingUpdateRes(); + + updateMappingPortToSecurityGroupMap(portMappingDO.getServerPort(), req.getSecurityGroupId()); } public PortMappingDetailRes detail(Integer id) { @@ -279,6 +283,30 @@ public class PortMappingService implements LifecycleBean { if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain()); } + + updateMappingPortToSecurityGroupMap(portMappingDO.getServerPort(), null); + } + + public void portBindSecurityGroup(Integer portMappingId, Integer groupId) { + PortMappingDO mappingDO = portMappingMapper.findById(portMappingId); + if (mappingDO == null) { + throw new RuntimeException("指定的端口映射不存在"); + } + mappingDO.setSecurityGroupId(groupId); + mappingDO.setUpdateTime(new Date()); + portMappingMapper.updateById(mappingDO); + updateMappingPortToSecurityGroupMap(mappingDO.getServerPort(), groupId); + } + + public void portUnbindSecurityGroup(Integer portMappingId) { + PortMappingDO mappingDO = portMappingMapper.findById(portMappingId); + if (mappingDO == null) { + throw new RuntimeException("指定的端口映射不存在"); + } + mappingDO.setSecurityGroupId(0); + mappingDO.setUpdateTime(new Date()); + portMappingMapper.updateById(mappingDO); + updateMappingPortToSecurityGroupMap(mappingDO.getServerPort(), null); } /** @@ -291,6 +319,11 @@ public class PortMappingService implements LifecycleBean { return portMappingMapper.findEnableListByLicenseId(licenseId); } + public Integer getSecurityGroupIdByMappingPort(Integer port) { + return mappingPortToSecurityGroupMap.get(port); + } + + /** * 服务端项目停止、启动时,更新在线状态为离线 */ @@ -302,11 +335,22 @@ public class PortMappingService implements LifecycleBean { } portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date()); + List allMappingDOList = portMappingMapper.selectList(Wrappers.lambdaQuery(PortMappingDO.class)); + allMappingDOList.forEach(item -> { + Integer securityGroupId = item.getSecurityGroupId(); + if (securityGroupId != null && securityGroupId > 0) { + updateMappingPortToSecurityGroupMap(item.getServerPort(), item.getSecurityGroupId()); + } + }); + // 未配置域名,则不需要处理域名映射逻辑 if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName())) { return; } - List portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain)); + List portMappingDOList = allMappingDOList.stream() + .filter(item -> NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal()) && item.getSubdomain() != null) + .collect(Collectors.toList()); +// List portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain)); if (CollectionUtil.isEmpty(portMappingDOList)) { return; } @@ -315,9 +359,18 @@ public class PortMappingService implements LifecycleBean { return; } ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort()); + }); } + private void updateMappingPortToSecurityGroupMap(Integer serverPort, Integer securityGroupId) { + if (securityGroupId == null || securityGroupId == 0) { + mappingPortToSecurityGroupMap.remove(serverPort); + return; + } + mappingPortToSecurityGroupMap.put(serverPort, securityGroupId); + } + @Override public void start() throws Throwable { diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/SecurityGroupService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/SecurityGroupService.java new file mode 100644 index 00000000..6411379a --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/SecurityGroupService.java @@ -0,0 +1,207 @@ +package org.dromara.neutrinoproxy.server.service; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jdk.jshell.Snippet; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.solon.annotation.Db; +import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder; +import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; +import org.dromara.neutrinoproxy.server.constant.SecurityRulePassTypeEnum; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityGroupCreateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityGroupUpdateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityRuleCreateReq; +import org.dromara.neutrinoproxy.server.controller.req.system.SecurityRuleUpdateReq; +import org.dromara.neutrinoproxy.server.dal.SecurityGroupMapper; +import org.dromara.neutrinoproxy.server.dal.SecurityRuleMapper; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityGroupDO; +import org.dromara.neutrinoproxy.server.dal.entity.SecurityRuleDO; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Init; +import org.noear.solon.core.runtime.NativeDetector; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Slf4j +public class SecurityGroupService { + + @Db + private SecurityGroupMapper securityGroupMapper; + + @Db + private SecurityRuleMapper securityRuleMapper; + + private final Map securityGroupMap = new ConcurrentHashMap<>(); + + // 允许通过控制的缓存,缓存类型最近最久未使用缓存,容量100,超时时间5分钟 + private final Cache ipAllowControlCache = CacheUtil.newLRUCache(100, 1000 * 60 * 5); + + @Init(index = 100) + public synchronized void init() { + securityGroupMap.clear(); + List groupDOList = securityGroupMapper.selectList(Wrappers.lambdaQuery(SecurityGroupDO.class) + .eq(SecurityGroupDO::getEnable, EnableStatusEnum.ENABLE)); + groupDOList.forEach(securityGroupDO -> securityGroupMap.put(securityGroupDO.getId(), securityGroupDO)); + ipAllowControlCache.clear(); + } + + public void clearCache() { + ipAllowControlCache.clear(); + } + + public List queryGroupList() { + return securityGroupMapper.selectList(Wrappers.lambdaQuery(SecurityGroupDO.class) + .eq(SecurityGroupDO::getUserId, SystemContextHolder.getUserId())); + } + + public SecurityGroupDO queryGroupOne(Integer groupId) { + return securityGroupMapper.selectById(groupId); + } + + public void createGroup(SecurityGroupCreateReq req) { + SecurityGroupDO groupDO = new SecurityGroupDO(); + BeanUtil.copyProperties(req, groupDO); + groupDO.setEnable(EnableStatusEnum.ENABLE) + .setUserId(SystemContextHolder.getUserId()) + .setCreateTime(new Date()) + .setUpdateTime(new Date()); + securityGroupMapper.insert(groupDO); + init(); + } + + public void updateGroup(SecurityGroupUpdateReq req) { + SecurityGroupDO groupDO = securityGroupMapper.selectById(req.getId()); + BeanUtil.copyProperties(req, groupDO); + securityGroupMapper.updateById(groupDO); + init(); + } + + public void setGroupStatus(Integer groupId, EnableStatusEnum statusEnum) { + SecurityGroupDO groupDO = securityGroupMapper.selectById(groupId); + if (groupDO == null) { + throw new RuntimeException("指定的安全组不存在"); + } + groupDO.setEnable(statusEnum); + securityGroupMapper.updateById(groupDO); + init(); + } + + /** + * 删除安全组,并级联删除安全组下的规则,删除后,需缓存 + * @param groupId 安全组Id + */ + public void deleteGroup(Integer groupId) { + securityGroupMapper.deleteById(groupId); + securityRuleMapper.delete(Wrappers.lambdaQuery(SecurityRuleDO.class) + .eq(SecurityRuleDO::getGroupId, groupId)); + init(); + } + + public List queryRuleListByGroupId(Integer groupId) { + return securityRuleMapper.selectList(Wrappers.lambdaQuery(SecurityRuleDO.class) + .eq(SecurityRuleDO::getGroupId, groupId) + .orderByAsc(SecurityRuleDO::getPriority) + ); + } + + public void createRule(SecurityRuleCreateReq req) { + SecurityRuleDO ruleDO = new SecurityRuleDO(); + BeanUtil.copyProperties(req, ruleDO); + ruleDO.setUserId(SystemContextHolder.getUserId()) + .setCreateTime(new Date()) + .setEnable(EnableStatusEnum.ENABLE) + .setUpdateTime(new Date()); + securityRuleMapper.insert(ruleDO); + clearCache(); + } + + public void updateRule(SecurityRuleUpdateReq req) { + SecurityRuleDO ruleDO = securityRuleMapper.selectById(req.getId()); + BeanUtil.copyProperties(req, ruleDO); + securityRuleMapper.updateById(ruleDO); + clearCache(); + } + + public void deleteRule(Integer ruleId) { + securityRuleMapper.deleteById(ruleId); + clearCache(); + } + + public void setRuleStatus(Integer ruleId, EnableStatusEnum statusEnum) { + SecurityRuleDO ruleDO = securityRuleMapper.selectById(ruleId); + ruleDO.setEnable(statusEnum); + ruleDO.setUpdateTime(new Date()); + securityRuleMapper.updateById(ruleDO); + clearCache(); + } + + /** + * 判断ip在该安全组下是否允许,如果安全组没有创建,则放行,默认黑名单规则 + * @param ip 被判断的IP地址 + * @param groupId 安全组Id + * @return 是否放行 + */ + public boolean judgeAllow(String ip, Integer groupId) { + ip = ip.toLowerCase(); + // 不能判断当前连接的IP,保守处理,拒绝放行 + if (StrUtil.isEmpty(ip)) { + log.debug("【安全组】不能正确获取到IP地址,保守处理,拒绝放行"); + return false; + } + + // 黑名单规则,没有该安全组,则放行 + if (groupId == null) { + log.debug("【安全组】{}:该IP访问的端口映射没有绑定安全组(1), 放行", ip); + return true; + } + SecurityGroupDO groupDO = securityGroupMap.get(groupId); + if (groupDO == null) { + log.debug("【安全组】{}:该IP访问的端口映射没有绑定安全组(2), 放行", ip); + return true; + } + + Boolean allow = null; + String judgeAllowMapKey = ip + groupId; + if (ipAllowControlCache.containsKey(judgeAllowMapKey)) { + allow = ipAllowControlCache.get(judgeAllowMapKey); + log.debug("【安全组】{}-安全组{}:该IP在缓存中,缓存策略为{}", ip, groupId, allow ? "允许" : "拒绝"); + return allow; + } + + List ruleDOList = securityRuleMapper.selectList(Wrappers.lambdaQuery(SecurityRuleDO.class) + .eq(SecurityRuleDO::getGroupId, groupId) + .eq(SecurityRuleDO::getEnable, EnableStatusEnum.ENABLE) + .orderByAsc(SecurityRuleDO::getPriority) + ); + for (SecurityRuleDO ruleDO : ruleDOList) { + SecurityRulePassTypeEnum passType = ruleDO.judge(ip); + if (passType == SecurityRulePassTypeEnum.ALLOW) { + allow = true; + log.debug("【安全组】{}-安全组{}:匹配到安全规则{}行为:{}", ip, groupId, ruleDO.getId(), "允许"); + break; + } + if (passType == SecurityRulePassTypeEnum.DENY) { + allow = false; + log.info("【安全组】{}-安全组{}:匹配到安全规则{}行为:{}", ip, groupId, ruleDO.getId(), "拒绝"); + break; + } + } + + // 当前IP没有匹配到任何一条规则,则使用安全组默认规则 + if (allow == null) { + allow = groupDO.getDefaultPassType() == SecurityRulePassTypeEnum.ALLOW; + log.debug("【安全组】{}-安全组{}:使用安全组默认放行类型:{}", ip, groupId, allow ? "允许" : "拒绝"); + } + + ipAllowControlCache.put(judgeAllowMapKey, allow); + + return allow; + } +} diff --git a/neutrino-proxy-server/src/main/resources/app.yml b/neutrino-proxy-server/src/main/resources/app.yml index 665cfc83..cb798520 100644 --- a/neutrino-proxy-server/src/main/resources/app.yml +++ b/neutrino-proxy-server/src/main/resources/app.yml @@ -74,13 +74,15 @@ neutrino: data: db: # 数据库类型,目前支持h2、mysql、mariadb - type: ${DB_TYPE:h2} + # type: ${DB_TYPE:h2} + type: ${DB_TYPE:mysql} # 数据库连接URL - url: ${DB_URL:jdbc:h2:file:./data/db;MODE=MySQL;AUTO_SERVER=TRUE} + # url: ${DB_URL:jdbc:h2:file:./data/db;MODE=MySQL;AUTO_SERVER=TRUE} + url: ${DB_URL:jdbc:mysql://okfly.vip:37889/neutrino-proxy?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true} # 数据库用户名 - username: ${DB_USER:} + username: ${DB_USER:root} # 数据库密码 - password: ${DB_PASSWORD:} + password: ${DB_PASSWORD:Root1234@} #添加MIME印射(如果有需要?) #是否启用静态文件服务。(可不配,默认为启用) diff --git a/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql b/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql index c6c0037b..1234ddc0 100644 --- a/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql +++ b/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql @@ -50,6 +50,36 @@ CREATE TABLE IF NOT EXISTS `port_group` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; +#安全组 +CREATE TABLE IF NOT EXISTS `security_group` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(20) NOT NULL COMMENT '安全组名称', + `description` varchar(255) COMMENT '安全组描述', + `user_id` int NOT NULL COMMENT '用户ID', + `enable` varchar(20) NOT NULL COMMENT '启用状态', + `default_pass_type` varchar(20) NOT NULL COMMENT '默认放行类型', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#安全组规则 +CREATE TABLE IF NOT EXISTS `security_rule` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `group_id` int NOT NULL COMMENT '关联安全组', + `name` varchar(20) NOT NULL COMMENT '规则名称', + `description` varchar(255) NOT NULL COMMENT '规则描述', + `rule` text NOT NULL COMMENT '规则内容', + `pass_type` varchar(20) NOT NULL COMMENT '放行类型', + `priority` int(1) NOT NULL COMMENT '优先级', + `user_id` int NOT NULL COMMENT '用户ID', + `enable` varchar(20) NOT NULL COMMENT '启用状态', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_group_id_priority` (`group_id`, `priority`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + #############################代理配置相关表############################# #license表 CREATE TABLE IF NOT EXISTS `license` ( @@ -79,6 +109,7 @@ CREATE TABLE IF NOT EXISTS `port_mapping` ( `proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量', `proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数', `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `security_group_id` int DEFAULT NULL COMMENT '安全组Id', `create_time` datetime(3) NOT NULL COMMENT '创建时间', `update_time` datetime(3) NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), @@ -207,3 +238,4 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` ( KEY `I_flow_report_month_user_id` (`user_id`), KEY `I_flow_report_month_license_id` (`license_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +