@@ -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"'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
ENV_CONFIG: '"prod"',
|
||||
BASE_API: '"https://api-prod"'
|
||||
BASE_API: '""'
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<el-popover
|
||||
placement="top"
|
||||
:width="width"
|
||||
v-model="visible">
|
||||
<p class="popper-p-css"><i class="el-icon-warning" style="color: #e6a23c"/>{{title}}</p>
|
||||
<div style="text-align: center; margin: 0">
|
||||
<el-button size="mini" @click="handleCancelClick">{{cancelText}}</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleCommitClick">{{okText}}</el-button>
|
||||
</div>
|
||||
<el-link slot="reference" :underline="false" :type="type" :size="size" :icon="icon" :disabled="disabled" style="text-align: left; font-size: 12px">{{buttonText}}</el-link>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'deleteLink',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '删除'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'danger'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'mini'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '确定删除吗?'
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCancelClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCancelClick')
|
||||
},
|
||||
handleCommitClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCommitClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.popper-p-css{
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 5px !important;
|
||||
.el-icon-warning{
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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: '查看'
|
||||
|
||||
@@ -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' }}
|
||||
]
|
||||
|
||||
@@ -176,6 +176,12 @@
|
||||
<template slot="append">毫秒</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.securityGroup')" prop="securityGroup">
|
||||
<el-select style="width: 280px;" class="filter-item" v-model="temp.securityGroupId" clearable >
|
||||
<el-option v-for="item in securityGroupList" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('描述')" prop="description">
|
||||
<el-input v-model="temp.description"></el-input>
|
||||
</el-form-item>
|
||||
@@ -204,6 +210,7 @@
|
||||
|
||||
<script>
|
||||
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
|
||||
import { fetchGroupList } from '@/api/securityGroup'
|
||||
import { availablePortList, portAvailable } from '@/api/portPool'
|
||||
import { licenseList, licenseAuthList } from '@/api/license'
|
||||
import { protocalList } from '@/api/protocal'
|
||||
@@ -325,6 +332,7 @@ export default {
|
||||
licenseId:null,
|
||||
},
|
||||
more: true,
|
||||
securityGroupList: []
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -359,6 +367,7 @@ export default {
|
||||
this.getLicenseList()
|
||||
this.getLicenseAuthList()
|
||||
this.getProtocalList()
|
||||
this.fetchSecurityGroupList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
@@ -380,6 +389,13 @@ export default {
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
fetchSecurityGroupList () {
|
||||
fetchGroupList().then(res => {
|
||||
if(res.data.code == 0) {
|
||||
this.securityGroupList = res.data.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getDomainNameBindInfo() {
|
||||
domainNameBindInfo().then(response => {
|
||||
this.domainName = response.data.data
|
||||
@@ -484,6 +500,9 @@ export default {
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
if (row.securityGroupId === 0) {
|
||||
this.temp.securityGroupId = null
|
||||
}
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
||||
style="width: 100%">
|
||||
<!-- <el-table-column align="center" width="40" type="selection" /> -->
|
||||
<el-table-column align="center" :label="$t('table.id')" width="60">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.name')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.name}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.desc')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.description}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.defaultPassType')">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.defaultPassType == 'allow'">允许</el-tag>
|
||||
<el-tag type="info" v-if="scope.row.defaultPassType == 'deny'">拒绝</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.createTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.updateTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.updateTime}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.enableStatus')">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.enable == '启用'">{{scope.row.enable}}</el-tag>
|
||||
<el-tag type="danger" v-if="scope.row.enable == '禁用'">{{scope.row.enable}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" class-name="small-padding fixed-width" style="display:flex;justify-content:center">
|
||||
<template slot-scope="scope">
|
||||
<div >
|
||||
<el-link :underline="false" type="primary" size="mini" @click="handleUpdate(scope.row)" style="font-size: 12px">{{$t('table.edit')}}</el-link>
|
||||
<el-link :underline="false" v-if="scope.row.enable =='启用'" size="mini" type="warning" @click="handleDisableStatus(scope.row)" style="font-size: 12px">{{$t('table.disable')}}</el-link>
|
||||
<el-link :underline="false" v-if="scope.row.enable =='禁用'" size="mini" type="success" @click="handleEnableStatus(scope.row)" style="font-size: 12px">{{$t('table.enable')}}</el-link>
|
||||
<el-link :underline="false" type="primary" size="mini" @click="handleGoRulePage(scope.row)" style="font-size: 12px">{{$t('table.ruleConfig')}}</el-link>
|
||||
</div>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" style="font-size: 12px">
|
||||
更多操作<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item>
|
||||
<LinkPopover @handleCommitClick="handleDelete(scope.row)" style="width: 100%"/>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-link :underline="false" type="primary" size="mini" @click="handlePortMapping(scope.row)" style="font-size: 12px">{{$t('table.securityGroupBindPortMapping')}}</el-link>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="right" label-width="120px" style='width: 500px; margin-left:10px;'>
|
||||
<el-form-item :label="$t('table.name')" prop="name">
|
||||
<el-input :placeholder="$t('table.name')" v-model="temp.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.desc')" prop="desc">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" :placeholder="$t('table.desc')" v-model="temp.description"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.defaultPassType')" prop="defaultPassType">
|
||||
<el-select style="width: 380px" class="filter-item" v-model="temp.defaultPassType">
|
||||
<el-option v-for="item in passTypeList" :key="item.key" :label="item.key" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
|
||||
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
|
||||
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="Reading statistics" :visible.sync="dialogPvVisible">
|
||||
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column prop="key" label="Channel"> </el-table-column>
|
||||
<el-table-column prop="pv" label="Pv"> </el-table-column>
|
||||
</el-table>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogPvVisible = false">{{$t('table.confirm')}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="$t('table.securityGroupBindPortMapping')+'---'+forBindProtMappingSecurityGroup.name+'安全组'" :visible.sync="dialogBindPortMappingVisible" width="90%">
|
||||
<el-table :key='tableKey' :data="portMappingList" v-loading="listLoading" element-loading-text="给我一点时间" border fit align="center" width="100%"
|
||||
highlight-current-row style="width: 100%">
|
||||
<el-table-column align="center" :label="$t('table.id')" width="50">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.id }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.licenseName')" width="130">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.licenseName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.protocalName')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.protocal }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.domainName')" width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.domain }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.serverPort')" width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.serverPort }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.proxyClient')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.clientIp }}:{{ scope.row.clientPort }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.desc')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.description }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.enable | statusFilter">{{ scope.row.enable | statusName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="120" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" v-if="!scope.row.securityGroupId" @click="handleBind(scope.row)">{{$t('table.bind')}}</el-button>
|
||||
<el-button type="danger" size="mini" v-if="scope.row.securityGroupId && scope.row.securityGroupId == forBindProtMappingSecurityGroup.id" @click="handleUnbind(scope.row)">{{$t('table.unbind')}}</el-button>
|
||||
<span v-if="scope.row.securityGroupId && scope.row.securityGroupId != forBindProtMappingSecurityGroup.id" style="font-size: 12px;">{{$t('table.bindOtherSecurityGroup')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handlePortMappingSizeChange" @current-change="handlePortMappingCurrentChange"
|
||||
:current-pageInfo.sync="portMappingListQuery.current" :pageInfo-sizes="[10, 20, 30, 50]" :pageInfo-size="portMappingListQuery.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="portMappingTotal">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {fetchGroupList, createGroup, updateGroup, deleteGroup, enableGroup, disableGroup} from '@/api/securityGroup'
|
||||
import { fetchList as fetchPortMappingList, portMappingBindSecurityGroup, portMappingUnbindSecurityGroup} from '@/api/portMapping'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import LinkPopover from '../../components/Link/linkPopover'
|
||||
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
LinkPopover
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: [],
|
||||
listLoading: true,
|
||||
temp: {
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
defaultPassType: undefined
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
passTypeList: [{key: '允许', value: 1}, {key: '拒绝', value: 0}],
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
name: [{ required: true, message: '安全组名称必填', trigger: 'blur' }],
|
||||
defaultPassType: [{ required: true, message: '默认放行类型必选', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false,
|
||||
checkBoxData:[], //表单勾选的行
|
||||
dialogBindPortMappingVisible: false,
|
||||
forBindProtMappingSecurityGroup: {},
|
||||
portMappingList: [],
|
||||
portMappingTotal: null,
|
||||
portMappingListLoading: false,
|
||||
portMappingListQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
userId: undefined,
|
||||
license: undefined,
|
||||
port: undefined,
|
||||
isOnline: undefined,
|
||||
enable: undefined,
|
||||
description: undefined
|
||||
},
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '启用',
|
||||
2: '禁用'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// eslint-disable-next-line no-sequences
|
||||
this.getList()
|
||||
this.getPortMappingList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchGroupList().then(response => {
|
||||
this.list = response.data.data
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getPortMappingList() {
|
||||
this.portMappingListLoading = true
|
||||
fetchPortMappingList(this.portMappingListQuery).then(response => {
|
||||
this.portMappingList = response.data.data.records
|
||||
this.portMappingTotal = response.data.data.total
|
||||
this.portMappingListQuery.current = response.data.data.current
|
||||
this.portMappingListLoading = false
|
||||
})
|
||||
},
|
||||
handleEnableStatus(row) {
|
||||
enableGroup(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDisableStatus(row) {
|
||||
disableGroup(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
defaultPassType: undefined
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
createGroup(this.temp).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.temp.defaultPassType = row.defaultPassType == 'allow' ? 1 : 0
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const tempData = Object.assign({}, this.temp)
|
||||
updateGroup(tempData).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
deleteGroup(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleGoRulePage (row) {
|
||||
this.$router.push(`/system/securityRule?groupId=${row.id}`)
|
||||
},
|
||||
handlePortMapping(row) {
|
||||
this.dialogBindPortMappingVisible = true
|
||||
this.forBindProtMappingSecurityGroup = row
|
||||
},
|
||||
handlePortMappingSizeChange(val) {
|
||||
this.portMappingListQuery.size = val
|
||||
this.getPortMappingList()
|
||||
},
|
||||
handlePortMappingCurrentChange(val) {
|
||||
this.portMappingListQuery.current = val
|
||||
this.getPortMappingList()
|
||||
},
|
||||
handleBind(portMapping) {
|
||||
portMappingBindSecurityGroup(portMapping.id, this.forBindProtMappingSecurityGroup.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '绑定成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getPortMappingList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleUnbind(portMapping) {
|
||||
portMappingUnbindSecurityGroup(portMapping.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '解绑成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getPortMappingList()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.filter-container {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,349 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
|
||||
<div>
|
||||
<div style="text-align: center;line-height:48px;font-size:24px">{{group.name}}安全组</div>
|
||||
<div style="text-align: center;font-size:14px; color: #606266">{{group.description}}</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-container" align="right">
|
||||
<el-button class="filter-item" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
||||
style="width: 100%">
|
||||
<!-- <el-table-column align="center" width="40" type="selection" /> -->
|
||||
<el-table-column align="center" :label="$t('table.id')" width="60">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.ruleName')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.name}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.desc')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.description}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.rule')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.rule}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.passType')">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.passType == 'allow'" effect="dark">允许</el-tag>
|
||||
<el-tag type="info" v-if="scope.row.passType == 'deny'" effect="dark">拒绝</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.priority')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.priority}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.createTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.updateTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.updateTime}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="success" v-if="scope.row.enable == '启用'">{{scope.row.enable}}</el-tag>
|
||||
<el-tag type="warning" v-if="scope.row.enable == '禁用'">{{scope.row.enable}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="primary" :underline="false" size="mini" @click="handleUpdate(scope.row)" style="font-size:12px">{{$t('table.edit')}}</el-link>
|
||||
<el-link :underline="false" v-if="scope.row.enable =='启用'" size="mini" type="warning" @click="handleDisableStatus(scope.row)" style="font-size:12px">{{$t('table.disable')}}</el-link>
|
||||
<el-link :underline="false" v-if="scope.row.enable =='禁用'" size="mini" type="success" @click="handleEnableStatus(scope.row)" style="font-size:12px">{{$t('table.enable')}}</el-link>
|
||||
<LinkPopover @handleCommitClick="handleDelete(scope.row)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible" top="4vh">
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="right" label-width="100px" style='margin-left:50px;margin-right: 150px'>
|
||||
<el-form-item :label="$t('table.name')" prop="name">
|
||||
<el-input :placeholder="$t('table.name')" v-model="temp.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.desc')" prop="desc">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" :placeholder="$t('table.desc')" v-model="temp.description"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.rule')" prop="rule">
|
||||
<el-input type="textarea" :autosize="{ minRows: 4, maxRows: 10}" :placeholder="$t('table.rule')" v-model="temp.rule"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="line-height: 28px; color: cornflowerblue">
|
||||
<div>规则描述:</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>
|
||||
<div>每个类型中间以英文逗号分隔,形如 192.168.1.1,192.168.3.0/24 是正确的 </div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item :label="$t('table.passType')" prop="passType">
|
||||
<el-select class="filter-item" v-model="temp.passType">
|
||||
<el-option v-for="item in passTypeList" :key="item.key" :label="item.key" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.priority')" prop="priority">
|
||||
<el-input-number v-model="temp.priority" :min="1" :max="1000" :placeholder="$t('table.priority')"></el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
|
||||
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
|
||||
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="Reading statistics" :visible.sync="dialogPvVisible">
|
||||
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column prop="key" label="Channel"> </el-table-column>
|
||||
<el-table-column prop="pv" label="Pv"> </el-table-column>
|
||||
</el-table>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogPvVisible = false">{{$t('table.confirm')}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {fetchGroupOne, fetchRuleList, createRule, updateRule, deleteRule, enableRule, disableRule} from '@/api/securityGroup'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import LinkPopover from '../../components/Link/linkPopover'
|
||||
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
LinkPopover
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupId: 1,
|
||||
group: {},
|
||||
tableKey: 0,
|
||||
list: [],
|
||||
listLoading: true,
|
||||
temp: {
|
||||
id: undefined,
|
||||
groupId: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
rule: '',
|
||||
passType: undefined,
|
||||
priority: 1
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
passTypeList: [{key: '允许', value: 1}, {key: '拒绝', value: 0}],
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
name: [{ required: true, message: '安全组名称必填', trigger: 'blur' }],
|
||||
rule: [{ required: true, message: '规则内容必填', trigger: 'blur' }],
|
||||
passType: [{ required: true, message: '放行类型必选', trigger: 'blur' }],
|
||||
priority: [{ required: true, message: '优先级必填', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false,
|
||||
checkBoxData:[], //表单勾选的行
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '启用',
|
||||
2: '禁用'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// eslint-disable-next-line no-sequences
|
||||
const queryParam = this.$route.query
|
||||
if (queryParam && typeof queryParam === 'object' && queryParam.groupId) {
|
||||
this.groupId = queryParam.groupId
|
||||
localStorage.setItem('groupId', this.groupId)
|
||||
this.getGroupOne()
|
||||
this.getList()
|
||||
return
|
||||
}
|
||||
|
||||
const groupId = localStorage.getItem('groupId')
|
||||
if (groupId) {
|
||||
this.groupId = parseInt(groupId)
|
||||
this.getGroupOne()
|
||||
this.getList()
|
||||
return
|
||||
}
|
||||
|
||||
this.$notify({
|
||||
title: '错误',
|
||||
message: '没有获取到安全组信息',
|
||||
type: 'error',
|
||||
duration: 3000
|
||||
})
|
||||
this.$router.push(`/system/securityGroup`)
|
||||
},
|
||||
methods: {
|
||||
getGroupOne () {
|
||||
fetchGroupOne(this.groupId).then(response => {
|
||||
this.group = response.data.data
|
||||
})
|
||||
},
|
||||
getList() {
|
||||
if (!this.groupId) {
|
||||
this.$notify({
|
||||
title: '错误',
|
||||
message: '没有获取到安全组信息',
|
||||
type: 'error',
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
this.listLoading = true
|
||||
fetchRuleList(this.groupId).then(response => {
|
||||
this.list = response.data.data
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleEnableStatus(row) {
|
||||
enableRule(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDisableStatus(row) {
|
||||
disableRule(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
defaultPassType: undefined
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.temp.groupId = this.groupId
|
||||
createRule(this.temp).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.temp.passType = row.passType == 'allow' ? 1 : 0
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const tempData = Object.assign({}, this.temp)
|
||||
tempData.groupId = this.groupId
|
||||
updateRule(tempData).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
deleteRule(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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时开启才有效)
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -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;
|
||||
}
|
||||
+26
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+117
@@ -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<SecurityGroupRes> getGroups() {
|
||||
List<SecurityGroupDO> 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<SecurityRuleRes> getRulesByGroupId(Integer groupId) {
|
||||
List<SecurityRuleDO> 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);
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -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;
|
||||
|
||||
}
|
||||
+6
@@ -62,6 +62,12 @@ public class PortMappingCreateReq {
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
|
||||
/**
|
||||
* 安全组Id
|
||||
*/
|
||||
private Integer securityGroupId;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
||||
+6
@@ -66,6 +66,12 @@ public class PortMappingUpdateReq {
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
|
||||
/**
|
||||
* 安全组Id
|
||||
*/
|
||||
private Integer securityGroupId;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
||||
+23
@@ -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;
|
||||
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
+57
@@ -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;
|
||||
|
||||
}
|
||||
+51
@@ -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;
|
||||
|
||||
}
|
||||
+6
@@ -101,6 +101,12 @@ public class PortMappingListRes {
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 安全组Id
|
||||
*/
|
||||
private Integer securityGroupId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
|
||||
+49
@@ -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;
|
||||
|
||||
|
||||
|
||||
}
|
||||
+68
@@ -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;
|
||||
|
||||
}
|
||||
+7
@@ -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<SecurityGroupDO> {
|
||||
}
|
||||
+7
@@ -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<SecurityRuleDO> {
|
||||
}
|
||||
+8
-15
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+74
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+159
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+23
-8
@@ -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<ByteBuf> {
|
||||
|
||||
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<ByteBu
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
// 判断IP是否在该端口绑定的安全组允许的规则内
|
||||
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPort(sa.getPort()))) {
|
||||
// 不在安全组规则放行范围内
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
|
||||
+15
@@ -10,12 +10,16 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.ProxyAttachment;
|
||||
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;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -27,6 +31,10 @@ import java.nio.charset.StandardCharsets;
|
||||
@Slf4j
|
||||
public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
|
||||
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<Datagr
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// 判断IP是否在该端口绑定的安全组允许的规则内
|
||||
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
|
||||
if (!securityGroupService.judgeAllow(IpUtil.getRemoteIp(ctx), portMappingService.getSecurityGroupIdByMappingPort(sa.getPort()))) {
|
||||
// 不在安全组规则放行范围内
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
|
||||
+66
-13
@@ -1,10 +1,12 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
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;
|
||||
@@ -47,6 +49,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -74,6 +77,9 @@ public class PortMappingService implements LifecycleBean {
|
||||
@Inject
|
||||
private DBInitialize dbInitialize;
|
||||
|
||||
/** 端口到安全组Id的映射 */
|
||||
private final Map<Integer, Integer> mappingPortToSecurityGroupMap = new ConcurrentHashMap<>();
|
||||
|
||||
public PageInfo<PortMappingListRes> 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<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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
+207
@@ -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<Integer, SecurityGroupDO> securityGroupMap = new ConcurrentHashMap<>();
|
||||
|
||||
// 允许通过控制的缓存,缓存类型最近最久未使用缓存,容量100,超时时间5分钟
|
||||
private final Cache<String, Boolean> ipAllowControlCache = CacheUtil.newLRUCache(100, 1000 * 60 * 5);
|
||||
|
||||
@Init(index = 100)
|
||||
public synchronized void init() {
|
||||
securityGroupMap.clear();
|
||||
List<SecurityGroupDO> 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<SecurityGroupDO> 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<SecurityRuleDO> 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<SecurityRuleDO> 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;
|
||||
}
|
||||
}
|
||||
@@ -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印射(如果有需要?)
|
||||
#是否启用静态文件服务。(可不配,默认为启用)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user