mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-09-20 07:35:58 +08:00
feat: 系统授权 ip 支持 ip 段 (#2352)
This commit is contained in:
parent
bbf2c50b25
commit
a520bdbe56
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
|
||||
|
|
|
@ -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: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue