端口映射安全规则界面修改后即时生效
This commit is contained in:
@@ -86,7 +86,7 @@
|
||||
<el-form-item>
|
||||
<div style="line-height: 28px; color: cornflowerblue">
|
||||
<div>规则描述:</div>
|
||||
<div>单个ip:192.168.1.1,0:0:0:0:0:0:10.0.0.1, ipv6只支持单个ip判断</div>
|
||||
<div>单个ip:192.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时开启才有效)
|
||||
|
||||
+6
@@ -62,6 +62,12 @@ public class PortMappingCreateReq {
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
|
||||
/**
|
||||
* 安全组Id
|
||||
*/
|
||||
private Integer securityGroupId;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
||||
+4
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -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;
|
||||
|
||||
+3
-5
@@ -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;
|
||||
|
||||
+34
-9
@@ -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 {
|
||||
|
||||
|
||||
+12
-4
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user