端口映射安全规则界面修改后即时生效

This commit is contained in:
=
2023-12-07 11:03:54 +08:00
parent 9f2198e32e
commit 7a173757f0
8 changed files with 64 additions and 27 deletions
@@ -86,7 +86,7 @@
<el-form-item>
<div style="line-height: 28px; color: cornflowerblue">
<div>规则描述:</div>
<div>单个ip192.168.1.1,0:0:0:0:0:0:10.0.0.1, ipv6只支持单个ip判断</div>
<div>单个ip192.168.1.1, AA22:BB11:1122:CDEF:1234:AA99:7654:7410, ipv6只支持单个ip判断</div>
<div>范围类型192.168.1.0-192.168.1.255</div>
<div>掩码类型192.168.1.0/24</div>
<div>泛型0.0.0.0/ALL</div>
@@ -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时开启才有效)
@@ -62,6 +62,12 @@ public class PortMappingCreateReq {
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 安全组Id
*/
private Integer securityGroupId;
/**
* 描述
*/
@@ -86,7 +86,7 @@ public class SecurityRuleDO {
* @param ip 指定的IP
* @return 放行状态
*/
public SecurityRulePassTypeEnum allow(String ip) {
public SecurityRulePassTypeEnum judge(String ip) {
// 被判断的IP地址为空,不做判断
if (StrUtil.isEmpty(ip)) {
@@ -108,9 +108,11 @@ public class SecurityRuleDO {
String[] rules = this.rule.split(",");
for (String rule : rules) {
rule = rule.trim();
// 单个ip,ipv6在此步已处理,后面不需要额外判断ipv6的情况
if (rule.matches("(\\d+\\.){3}\\d+") || isIpv6) {
if (rule.equals(ip)) {
if (rule.equalsIgnoreCase(ip)) {
return passType == SecurityRulePassTypeEnum.ALLOW ? SecurityRulePassTypeEnum.ALLOW : SecurityRulePassTypeEnum.DENY;
}
}
@@ -29,11 +29,9 @@ import java.net.InetSocketAddress;
@Slf4j
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Inject
private SecurityGroupService securityGroupService;
private final SecurityGroupService securityGroupService = Solon.context().getBean(SecurityGroupService.class);
@Inject
private PortMappingService portMappingService;
private final PortMappingService portMappingService = Solon.context().getBean(PortMappingService.class);
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
@@ -74,7 +72,7 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
// 判断IP是否在该端口绑定的安全组允许的规则内
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPor(sa.getPort()))) {
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPort(sa.getPort()))) {
// 不在安全组规则放行范围内
ctx.channel().close();
return;
@@ -31,11 +31,9 @@ import java.nio.charset.StandardCharsets;
@Slf4j
public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Inject
private SecurityGroupService securityGroupService;
private final SecurityGroupService securityGroupService = Solon.context().getBean(SecurityGroupService.class);
@Inject
private PortMappingService portMappingService;
private final PortMappingService portMappingService = Solon.context().getBean(PortMappingService.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception {
@@ -130,7 +128,7 @@ public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler<Datagr
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 判断IP是否在该端口绑定的安全组允许的规则内
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPor(sa.getPort()))) {
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPort(sa.getPort()))) {
// 不在安全组规则放行范围内
ctx.channel().close();
return;
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import com.google.common.collect.Sets;
import org.apache.ibatis.solon.annotation.Db;
@@ -77,7 +78,7 @@ public class PortMappingService implements LifecycleBean {
private DBInitialize dbInitialize;
/** 端口到安全组Id的映射 */
private final Map<Integer, Integer> portToSecurityGroupMap = new ConcurrentHashMap<>();
private final Map<Integer, Integer> mappingPortToSecurityGroupMap = new ConcurrentHashMap<>();
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
if (StringUtils.isNotEmpty(req.getDescription())) {
@@ -166,6 +167,9 @@ 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();
}
@@ -203,6 +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());
}
updateMappingPortToSecurityGroupMap(portMappingDO.getServerPort(), req.getSecurityGroupId());
}
public PortMappingDetailRes detail(Integer id) {
@@ -277,6 +283,8 @@ 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) {
@@ -287,7 +295,7 @@ public class PortMappingService implements LifecycleBean {
mappingDO.setSecurityGroupId(groupId);
mappingDO.setUpdateTime(new Date());
portMappingMapper.updateById(mappingDO);
portToSecurityGroupMap.put(mappingDO.getServerPort(), groupId);
updateMappingPortToSecurityGroupMap(mappingDO.getServerPort(), groupId);
}
public void portUnbindSecurityGroup(Integer portMappingId) {
@@ -298,7 +306,7 @@ public class PortMappingService implements LifecycleBean {
mappingDO.setSecurityGroupId(0);
mappingDO.setUpdateTime(new Date());
portMappingMapper.updateById(mappingDO);
portToSecurityGroupMap.remove(mappingDO.getServerPort());
updateMappingPortToSecurityGroupMap(mappingDO.getServerPort(), null);
}
/**
@@ -311,8 +319,8 @@ public class PortMappingService implements LifecycleBean {
return portMappingMapper.findEnableListByLicenseId(licenseId);
}
public Integer getSecurityGroupIdByMappingPor(Integer port) {
return portToSecurityGroupMap.get(port);
public Integer getSecurityGroupIdByMappingPort(Integer port) {
return mappingPortToSecurityGroupMap.get(port);
}
@@ -327,11 +335,22 @@ public class PortMappingService implements LifecycleBean {
}
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
List<PortMappingDO> 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<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
List<PortMappingDO> portMappingDOList = allMappingDOList.stream()
.filter(item -> NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal()) && item.getSubdomain() != null)
.collect(Collectors.toList());
// List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
@@ -340,12 +359,18 @@ public class PortMappingService implements LifecycleBean {
return;
}
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
if (item.getSecurityGroupId() != null) {
portToSecurityGroupMap.put(item.getServerPort(), item.getSecurityGroupId());
}
});
}
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 {
@@ -149,24 +149,30 @@ public class SecurityGroupService {
* @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)) {
return ipAllowControlCache.get(judgeAllowMapKey);
allow = ipAllowControlCache.get(judgeAllowMapKey);
log.debug("【安全组】{}-安全组{}:该IP在缓存中,缓存策略为{}", ip, groupId, allow ? "允许" : "拒绝");
return allow;
}
List<SecurityRuleDO> ruleDOList = securityRuleMapper.selectList(Wrappers.lambdaQuery(SecurityRuleDO.class)
@@ -174,15 +180,16 @@ public class SecurityGroupService {
.eq(SecurityRuleDO::getEnable, EnableStatusEnum.ENABLE)
.orderByAsc(SecurityRuleDO::getPriority)
);
Boolean allow = null;
for (SecurityRuleDO ruleDO : ruleDOList) {
SecurityRulePassTypeEnum passType = ruleDO.allow(ip);
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;
}
}
@@ -190,6 +197,7 @@ public class SecurityGroupService {
// 当前IP没有匹配到任何一条规则,则使用安全组默认规则
if (allow == null) {
allow = groupDO.getDefaultPassType() == SecurityRulePassTypeEnum.ALLOW;
log.debug("【安全组】{}-安全组{}:使用安全组默认放行类型:{}", ip, groupId, allow ? "允许" : "拒绝");
}
ipAllowControlCache.put(judgeAllowMapKey, allow);