1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +08:00

feat: 防火墙支持多 ip 及多 ip 段 (#2122)

Refs #2046
This commit is contained in:
ssongliu 2023-08-30 22:28:12 +08:00 committed by GitHub
parent 9cecd66f3d
commit f9d5a445c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 63 deletions

View File

@ -219,12 +219,15 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
return err
}
protos := strings.Split(req.Protocol, "/")
itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",")
if client.Name() == "ufw" {
if len(req.Address) == 0 {
req.Address = "Anywhere"
}
if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") {
for _, proto := range protos {
for _, addr := range itemAddress {
if len(addr) == 0 {
addr = "Anywhere"
}
req.Address = addr
req.Port = strings.ReplaceAll(req.Port, "-", ":")
req.Protocol = proto
if err := u.operatePort(client, req); err != nil {
@ -232,32 +235,44 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
}
_ = u.addPortRecord(req)
}
}
return nil
}
if req.Protocol == "tcp/udp" {
req.Protocol = ""
}
for _, addr := range itemAddress {
if len(addr) == 0 {
addr = "Anywhere"
}
req.Address = addr
if err := u.operatePort(client, req); err != nil {
return err
}
_ = u.addPortRecord(req)
}
return nil
}
itemPorts := req.Port
for _, proto := range protos {
if strings.Contains(req.Port, "-") {
for _, addr := range itemAddress {
req.Protocol = proto
req.Address = addr
if err := u.operatePort(client, req); err != nil {
return err
}
_ = u.addPortRecord(req)
}
} else {
ports := strings.Split(itemPorts, ",")
for _, port := range ports {
if len(port) == 0 {
continue
}
for _, addr := range itemAddress {
req.Address = addr
req.Port = port
req.Protocol = proto
if err := u.operatePort(client, req); err != nil {
@ -267,6 +282,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
}
}
}
}
return client.Reload()
}
@ -290,6 +306,7 @@ func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload boo
if err := client.RichRules(fireInfo, req.Operation); err != nil {
return err
}
req.Address = addr
_ = u.addAddressRecord(req)
}
if reload {

View File

@ -1621,8 +1621,8 @@ const message = {
allow: 'Allow',
deny: 'Deny',
addressFormatError: 'Please enter a valid ip address!',
addressHelper1: 'Multiple IP please separated with ",", e.g. 172.16.10.11, 172.16.10.99',
addressHelper2: 'IP segment, e.g. 172.16.10.0/24',
addressHelper1: 'Supports input of single IPs or IP ranges: 172.16.10.11 or 172.16.0.0/24',
addressHelper2: 'For multiple IPs or IP ranges, separate with commas: 172.16.10.11, 172.16.0.0/24',
allIP: 'All IP',
portRule: 'Port rule',
ipRule: 'IP rule',

View File

@ -1538,8 +1538,8 @@ const message = {
allow: '放行',
deny: '屏蔽',
addressFormatError: '請輸入合法的 ip 地址',
addressHelper1: '多個 IP 請用 "," 隔開172.16.10.11,172.16.10.99',
addressHelper2: 'IP 172.16.0.0/24',
addressHelper1: '支持輸入 IP IP 172.16.10.11 172.16.0.0/24',
addressHelper2: '多個 IP IP 請用 "," 隔開172.16.10.11,172.16.0.0/24',
allIP: '所有 IP',
portRule: '端口規則',
ipRule: 'IP 規則',

View File

@ -1538,8 +1538,8 @@ const message = {
allow: '放行',
deny: '屏蔽',
addressFormatError: '请输入合法的 ip 地址',
addressHelper1: '多个 IP 请用 "," 隔开172.16.10.11,172.16.10.99',
addressHelper2: 'IP 172.16.0.0/24',
addressHelper1: '支持输入 IP IP 172.16.10.11 172.16.0.0/24',
addressHelper2: '多个 IP IP 请用 "," 隔开172.16.10.11,172.16.0.0/24',
allIP: '所有 IP',
portRule: '端口规则',
ipRule: 'IP 规则',

View File

@ -235,6 +235,19 @@ export function checkIpV4V6(value: string): boolean {
}
}
export function checkCidr(value: string): boolean {
if (value === '') {
return true;
}
const reg =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
if (!reg.test(value) && value !== '') {
return true;
} else {
return false;
}
}
export function checkPort(value: string): boolean {
if (Number(value) <= 0) {
return true;

View File

@ -277,7 +277,7 @@ const onDelete = async (row: Host.RuleIP | null) => {
}
}
loading.value = true;
await batchOperateRule({ type: 'port', rules: rules })
await batchOperateRule({ type: 'address', rules: rules })
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));

View File

@ -44,14 +44,13 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message';
import { MsgSuccess } from '@/utils/message';
import { Host } from '@/api/interface/host';
import { operateIPRule, updateAddrRule } from '@/api/modules/host';
import { checkIpV4V6, deepCopy } from '@/utils/util';
import { checkCidr, checkIpV4V6, deepCopy } from '@/utils/util';
const loading = ref();
const oldRule = ref<Host.RuleIP>();
@ -81,8 +80,26 @@ const handleClose = () => {
};
const rules = reactive({
address: [Rules.requiredInput],
address: [{ validator: checkAddress, trigger: 'blur' }],
});
function checkAddress(rule: any, value: any, callback: any) {
if (!dialogData.value.rowData.address) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
let addrs = dialogData.value.rowData.address.split(',');
for (const item of addrs) {
if (item.indexOf('/') !== -1) {
if (checkCidr(item)) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
} else {
if (checkIpV4V6(item)) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
}
}
callback();
}
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
@ -93,20 +110,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!valid) return;
dialogData.value.rowData.operation = 'add';
if (!dialogData.value.rowData) return;
let ips = [];
if (dialogData.value.rowData.address.indexOf(',') !== -1) {
ips = dialogData.value.rowData.address.split(',');
} else if (dialogData.value.rowData.address.indexOf('/') !== -1) {
ips.push(dialogData.value.rowData.address.split('/')[0]);
} else {
ips.push(dialogData.value.rowData.address);
}
for (const ip of ips) {
if (checkIpV4V6(ip)) {
MsgError(i18n.global.t('firewall.addressFormatError'));
return;
}
}
loading.value = true;
if (dialogData.value.title === 'create') {
await operateIPRule(dialogData.value.rowData)

View File

@ -37,10 +37,9 @@
v-if="dialogData.rowData!.source === 'address'"
prop="address"
>
<el-input
:placeholder="$t('firewall.addressHelper')"
v-model="dialogData.rowData!.address"
/>
<el-input v-model.trim="dialogData.rowData!.address" />
<span class="input-help">{{ $t('firewall.addressHelper1') }}</span>
<span class="input-help">{{ $t('firewall.addressHelper2') }}</span>
</el-form-item>
<el-form-item :label="$t('firewall.strategy')" prop="strategy">
@ -76,7 +75,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message';
import { Host } from '@/api/interface/host';
import { operatePortRule, updatePortRule } from '@/api/modules/host';
import { checkIpV4V6, checkPort, deepCopy } from '@/utils/util';
import { checkCidr, checkIpV4V6, checkPort, deepCopy } from '@/utils/util';
const loading = ref();
const oldRule = ref<Host.RulePort>();
@ -113,9 +112,28 @@ const handleClose = () => {
const rules = reactive({
protocol: [Rules.requiredSelect],
port: [Rules.requiredInput],
address: [Rules.requiredInput],
address: [{ validator: checkAddress, trigger: 'blur' }],
});
function checkAddress(rule: any, value: any, callback: any) {
if (!dialogData.value.rowData.address) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
let addrs = dialogData.value.rowData.address.split(',');
for (const item of addrs) {
if (item.indexOf('/') !== -1) {
if (checkCidr(item)) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
} else {
if (checkIpV4V6(item)) {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
}
}
callback();
}
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
@ -127,18 +145,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!dialogData.value.rowData) return;
if (dialogData.value.rowData.source === 'anyWhere') {
dialogData.value.rowData.address = '';
} else {
if (dialogData.value.rowData.address.indexOf('/') !== -1) {
if (checkIpV4V6(dialogData.value.rowData.address.split('/')[0])) {
MsgError(i18n.global.t('firewall.addressFormatError'));
return;
}
} else {
if (checkIpV4V6(dialogData.value.rowData.address)) {
MsgError(i18n.global.t('firewall.addressFormatError'));
return;
}
}
}
let ports = [];
if (dialogData.value.rowData.port.indexOf('-') !== -1 && !dialogData.value.rowData.port.startsWith('-')) {