feat: 系统授权 ip 支持 ip 段 (#2352)

This commit is contained in:
ssongliu 2023-09-20 12:02:20 +08:00 committed by GitHub
parent bbf2c50b25
commit a520bdbe56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 44 deletions

View file

@ -2,11 +2,13 @@ package middleware
import (
"errors"
"net"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
@ -25,7 +27,10 @@ func WhiteAllow() gin.HandlerFunc {
}
clientIP := c.ClientIP()
for _, ip := range strings.Split(status.Value, ",") {
if len(ip) != 0 && ip == clientIP {
if len(ip) == 0 {
continue
}
if ip == clientIP || (strings.Contains(ip, "/") && checkIpInCidr(ip, clientIP)) {
c.Next()
return
}
@ -33,3 +38,26 @@ func WhiteAllow() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, errors.New("IP address not allowed"))
}
}
func checkIpInCidr(cidr, checkIP string) bool {
ip, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
global.LOG.Errorf("parse CIDR %s failed, err: %v", cidr, err)
return false
}
for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) {
if ip.String() == checkIP {
return true
}
}
return false
}
func incIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

View file

@ -1091,7 +1091,7 @@ const message = {
'After setting the authorized IP address, only the IP address in the setting can access the 1Panel service. Do you want to continue?',
allowIPsHelper1: 'If the authorized IP address is empty, the authorized IP address is canceled',
allowIPEgs:
'If multiple ip authorizations exist, newlines need to be displayed. For example, \n172.16.10.111 \n172.16.10.112',
'If multiple ip authorizations exist, newlines need to be displayed. For example, \n172.16.10.111 \n172.16.10.0/24',
mfa: 'MFA',
secret: 'Secret',
mfaInterval: 'Refresh interval (s)',

View file

@ -1079,7 +1079,7 @@ const message = {
allowIPsHelper: '設置授權 IP 僅有設置中的 IP 可以訪問 1Panel 服務',
allowIPsWarning: '設置授權 IP 僅有設置中的 IP 可以訪問 1Panel 服務是否繼續',
allowIPsHelper1: '授權 IP 為空時則取消授權 IP',
allowIPEgs: '當存在多個授權 IP 需要換行顯示 \n172.16.10.111 \n172.16.10.112',
allowIPEgs: '當存在多個授權 IP 需要換行顯示 \n172.16.10.111 \n172.16.10.0/24',
mfa: '兩步驗證',
secret: '密鑰',
mfaAlert: '兩步驗證密碼是基於當前時間生成請確保服務器時間已同步',

View file

@ -1079,7 +1079,7 @@ const message = {
allowIPsHelper: '设置授权 IP 仅有设置中的 IP 可以访问 1Panel 服务',
allowIPsWarning: '设置授权 IP 仅有设置中的 IP 可以访问 1Panel 服务是否继续',
allowIPsHelper1: '授权 IP 为空时则取消授权 IP',
allowIPEgs: '当存在多个授权 IP 需要换行显示 \n172.16.10.111 \n172.16.10.112',
allowIPEgs: '当存在多个授权 IP 需要换行显示 \n172.16.10.111 \n172.16.10.0/24',
mfa: '两步验证',
secret: '密钥',
mfaAlert: '两步验证密码是基于当前时间生成请确保服务器时间已同步',

View file

@ -4,15 +4,22 @@
<template #header>
<DrawerHeader :header="$t('setting.allowIPs')" :back="handleClose" />
</template>
<el-form label-position="top" @submit.prevent v-loading="loading">
<el-form
ref="formRef"
label-position="top"
@submit.prevent
:model="form"
:rules="rules"
v-loading="loading"
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('setting.allowIPs')">
<el-form-item :label="$t('setting.allowIPs')" prop="allowIPs">
<el-input
type="textarea"
:placeholder="$t('setting.allowIPEgs')"
:autosize="{ minRows: 8, maxRows: 10 }"
v-model="allowIPs"
v-model="form.allowIPs"
/>
<span class="input-help">{{ $t('setting.allowIPsHelper1') }}</span>
</el-form-item>
@ -22,7 +29,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave()">
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@ -31,59 +38,82 @@
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { MsgSuccess } from '@/utils/message';
import { updateSetting } from '@/api/modules/setting';
import { ElMessageBox } from 'element-plus';
import { checkIpV4V6 } from '@/utils/util';
import { ElMessageBox, FormInstance } from 'element-plus';
import { checkCidr, checkIpV4V6 } from '@/utils/util';
import DrawerHeader from '@/components/drawer-header/index.vue';
const emit = defineEmits<{ (e: 'search'): void }>();
const allowIPs = ref();
const form = reactive({
allowIPs: '',
});
const rules = reactive({
allowIPs: [{ validator: checkAddress, trigger: 'blur' }],
});
function checkAddress(rule: any, value: any, callback: any) {
if (form.allowIPs !== '') {
let addrs = form.allowIPs.split('\n');
for (const item of addrs) {
if (item === '0.0.0.0') {
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
}
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();
}
}
const formRef = ref<FormInstance>();
interface DialogProps {
allowIPs: string;
}
const drawerVisiable = ref();
const loading = ref();
const acceptParams = (params: DialogProps): void => {
allowIPs.value = params.allowIPs;
form.allowIPs = params.allowIPs;
drawerVisiable.value = true;
};
const onSave = async () => {
if (allowIPs.value) {
let ips = allowIPs.value.split('\n');
for (const ip of ips) {
if (ip) {
if (checkIpV4V6(ip) || ip === '0.0.0.0') {
MsgError(i18n.global.t('firewall.addressFormatError'));
return false;
}
}
}
}
let title = allowIPs.value ? i18n.global.t('setting.allowIPs') : i18n.global.t('setting.unAllowIPs');
let allow = allowIPs.value ? i18n.global.t('setting.allowIPsWarning') : i18n.global.t('setting.unAllowIPsWarning');
ElMessageBox.confirm(allow, title, {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let title = form.allowIPs ? i18n.global.t('setting.allowIPs') : i18n.global.t('setting.unAllowIPs');
let allow = form.allowIPs
? i18n.global.t('setting.allowIPsWarning')
: i18n.global.t('setting.unAllowIPsWarning');
ElMessageBox.confirm(allow, title, {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
await updateSetting({ key: 'AllowIPs', value: allowIPs.value.replaceAll('\n', ',') })
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
await updateSetting({ key: 'AllowIPs', value: form.allowIPs.replaceAll('\n', ',') })
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
});
});
};