mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-17 12:58:51 +08:00
feat: 系统授权 ip 支持 ip 段 (#2352)
This commit is contained in:
parent
bbf2c50b25
commit
a520bdbe56
5 changed files with 102 additions and 44 deletions
|
|
@ -2,11 +2,13 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,7 +27,10 @@ func WhiteAllow() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
clientIP := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
for _, ip := range strings.Split(status.Value, ",") {
|
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()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -33,3 +38,26 @@ func WhiteAllow() gin.HandlerFunc {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, errors.New("IP address not allowed"))
|
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?',
|
'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',
|
allowIPsHelper1: 'If the authorized IP address is empty, the authorized IP address is canceled',
|
||||||
allowIPEgs:
|
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',
|
mfa: 'MFA',
|
||||||
secret: 'Secret',
|
secret: 'Secret',
|
||||||
mfaInterval: 'Refresh interval (s)',
|
mfaInterval: 'Refresh interval (s)',
|
||||||
|
|
|
||||||
|
|
@ -1079,7 +1079,7 @@ const message = {
|
||||||
allowIPsHelper: '設置授權 IP 後,僅有設置中的 IP 可以訪問 1Panel 服務',
|
allowIPsHelper: '設置授權 IP 後,僅有設置中的 IP 可以訪問 1Panel 服務',
|
||||||
allowIPsWarning: '設置授權 IP 後,僅有設置中的 IP 可以訪問 1Panel 服務,是否繼續?',
|
allowIPsWarning: '設置授權 IP 後,僅有設置中的 IP 可以訪問 1Panel 服務,是否繼續?',
|
||||||
allowIPsHelper1: '授權 IP 為空時,則取消授權 IP',
|
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: '兩步驗證',
|
mfa: '兩步驗證',
|
||||||
secret: '密鑰',
|
secret: '密鑰',
|
||||||
mfaAlert: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
|
mfaAlert: '兩步驗證密碼是基於當前時間生成,請確保服務器時間已同步',
|
||||||
|
|
|
||||||
|
|
@ -1079,7 +1079,7 @@ const message = {
|
||||||
allowIPsHelper: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务',
|
allowIPsHelper: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务',
|
||||||
allowIPsWarning: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务,是否继续?',
|
allowIPsWarning: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务,是否继续?',
|
||||||
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
|
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: '两步验证',
|
mfa: '两步验证',
|
||||||
secret: '密钥',
|
secret: '密钥',
|
||||||
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,22 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<DrawerHeader :header="$t('setting.allowIPs')" :back="handleClose" />
|
<DrawerHeader :header="$t('setting.allowIPs')" :back="handleClose" />
|
||||||
</template>
|
</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-row type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item :label="$t('setting.allowIPs')">
|
<el-form-item :label="$t('setting.allowIPs')" prop="allowIPs">
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:placeholder="$t('setting.allowIPEgs')"
|
:placeholder="$t('setting.allowIPEgs')"
|
||||||
:autosize="{ minRows: 8, maxRows: 10 }"
|
:autosize="{ minRows: 8, maxRows: 10 }"
|
||||||
v-model="allowIPs"
|
v-model="form.allowIPs"
|
||||||
/>
|
/>
|
||||||
<span class="input-help">{{ $t('setting.allowIPsHelper1') }}</span>
|
<span class="input-help">{{ $t('setting.allowIPsHelper1') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -22,7 +29,7 @@
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
<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') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -31,59 +38,82 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { updateSetting } from '@/api/modules/setting';
|
import { updateSetting } from '@/api/modules/setting';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
import { checkIpV4V6 } from '@/utils/util';
|
import { checkCidr, checkIpV4V6 } from '@/utils/util';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
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 {
|
interface DialogProps {
|
||||||
allowIPs: string;
|
allowIPs: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const drawerVisiable = ref();
|
const drawerVisiable = ref();
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
|
|
||||||
const acceptParams = (params: DialogProps): void => {
|
const acceptParams = (params: DialogProps): void => {
|
||||||
allowIPs.value = params.allowIPs;
|
form.allowIPs = params.allowIPs;
|
||||||
drawerVisiable.value = true;
|
drawerVisiable.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSave = async () => {
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
if (allowIPs.value) {
|
if (!formEl) return;
|
||||||
let ips = allowIPs.value.split('\n');
|
formEl.validate(async (valid) => {
|
||||||
for (const ip of ips) {
|
if (!valid) return;
|
||||||
if (ip) {
|
let title = form.allowIPs ? i18n.global.t('setting.allowIPs') : i18n.global.t('setting.unAllowIPs');
|
||||||
if (checkIpV4V6(ip) || ip === '0.0.0.0') {
|
let allow = form.allowIPs
|
||||||
MsgError(i18n.global.t('firewall.addressFormatError'));
|
? i18n.global.t('setting.allowIPsWarning')
|
||||||
return false;
|
: 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',
|
||||||
let title = allowIPs.value ? i18n.global.t('setting.allowIPs') : i18n.global.t('setting.unAllowIPs');
|
}).then(async () => {
|
||||||
let allow = allowIPs.value ? i18n.global.t('setting.allowIPsWarning') : i18n.global.t('setting.unAllowIPsWarning');
|
loading.value = true;
|
||||||
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', ',') })
|
await updateSetting({ key: 'AllowIPs', value: form.allowIPs.replaceAll('\n', ',') })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
emit('search');
|
emit('search');
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue