feat: 优化 Fail2ban 禁用方式错误返回 (#3227)

This commit is contained in:
ssongliu 2023-12-08 15:40:07 +08:00 committed by GitHub
parent bd2003c1b6
commit 504d5f8596
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 193 additions and 21 deletions

View File

@ -8,6 +8,8 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/1Panel-dev/1Panel/backend/utils/toolbox"
)
@ -97,6 +99,25 @@ func (u *Fail2BanService) Operate(operation string) error {
}
func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
if req.Key == "banaction" {
switch req.Value {
case "firewallcmd-ipset":
isActive, _ := systemctl.IsActive("firewalld")
if !isActive {
return buserr.WithName("ErrBanAction", "firewalld")
}
case "ufw":
isActive, _ := systemctl.IsActive("ufw")
if !isActive {
return buserr.WithName("ErrBanAction", "ufw")
}
}
}
if req.Key == "logpath" {
if _, err := os.Stat(req.Value); err != nil {
return err
}
}
conf, err := os.ReadFile(defaultFail2BanPath)
if err != nil {
return fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)

View File

@ -160,3 +160,4 @@ ErrBashExecute: "Script execution error, please check the specific information i
#toolbox
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"

View File

@ -161,3 +161,4 @@ ErrBashExecute: "腳本執行錯誤,請在任務輸出文本域中查看具體
#toolbox
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"

View File

@ -159,4 +159,5 @@ ErrFirewall: "当前未检测到系统 firewalld 或 ufw 服务,请检查后
ErrBashExecute: "脚本执行错误,请在任务输出文本域中查看具体信息。"
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"

View File

@ -1,7 +1,6 @@
package toolbox
import (
"encoding/json"
"fmt"
"os"
"strings"
@ -98,10 +97,11 @@ func (f *Fail2ban) ListBanned() ([]string, error) {
if err != nil {
return lists, err
}
stdout = strings.ReplaceAll(stdout, "\n", "")
stdout = strings.ReplaceAll(stdout, "'", "\"")
if err := json.Unmarshal([]byte(stdout), &lists); err != nil {
return lists, fmt.Errorf("handle json unmarshal (%s) failed, err: %v", stdout, err)
itemList := strings.Split(stdout, "\n")
for _, item := range itemList {
if len(item) != 0 {
lists = append(lists, item)
}
}
return lists, nil
}
@ -146,6 +146,7 @@ port = 22
maxretry = 5
findtime = 300
bantime = 600
banaction = $banaction
action = %(action_mwl)s
logpath = $logpath`

View File

@ -950,6 +950,8 @@ const message = {
dnsTestFailed: 'DNS configuration information is not available. Please modify and try again!',
},
fail2ban: {
sshPort: 'Listen to SSH Port',
sshPortHelper: 'Current Fail2ban listens to the SSH connection port of the host',
noFail2ban: 'Fail2ban service not detected, please refer to the official documentation for installation',
unActive: 'The Fail2ban service is not enabled at present, please enable it first!',
operation: 'Perform [{0}] operation on Fail2ban service, continue?',
@ -959,6 +961,8 @@ const message = {
maxRetry: 'Maximum Retry Attempts',
banTime: 'Ban Time',
banTimeHelper: 'Default ban time is 10 minutes, -1 indicates permanent ban',
banTimeRule: 'Please enter a valid ban time or -1',
banAllTime: 'Permanent Ban',
findTime: 'Discovery Period',
banAction: 'Ban Action',
banActionOption: 'Ban specified IP addresses using {0}',

View File

@ -901,12 +901,16 @@ const message = {
dnsTestFailed: 'DNS 配置信息不可用請修改後重試',
},
fail2ban: {
sshPort: '監聽 SSH 端口',
sshPortHelper: '當前 Fail2ban 監聽主機 SSH 連接端口',
noFail2ban: '未檢測到 Fail2ban 服務請參考官方文檔進行安裝',
unActive: '當前未開啟 Fail2ban 服務請先開啟',
operation: ' Fail2ban 服務進行 [{0}] 操作是否繼續',
fail2banChange: 'Fail2ban 配置修改',
ignoreHelper: '白名單中的 IP 列表將被忽略屏蔽是否繼續',
bannedHelper: '黑名單中的 IP 列表將被伺服器屏蔽是否繼續',
banTimeRule: '請輸入正確的禁用時間或 -1',
banAllTime: '永久禁用',
maxRetry: '最大重試次數',
banTime: '禁用時間',
banTimeHelper: '默認禁用時間為 10 分鐘禁用時間為 -1 則表示永久禁用',

View File

@ -902,6 +902,8 @@ const message = {
dnsTestFailed: 'DNS 配置信息不可用请修改后重试',
},
fail2ban: {
sshPort: '监听 SSH 端口',
sshPortHelper: '当前 Fail2ban 监听主机 SSH 连接端口',
noFail2ban: '未检测到 Fail2ban 服务请参考官方文档进行安装',
unActive: '当前未开启 Fail2ban 服务请先开启',
operation: ' Fail2ban 服务进行 [{0}] 操作是否继续',
@ -911,6 +913,8 @@ const message = {
maxRetry: '最大重试次数',
banTime: '禁用时间',
banTimeHelper: '默认禁用时间为 10 分钟禁用时间为 -1 则表示永久禁用',
banTimeRule: '请输入正确的禁用时间或者 -1',
banAllTime: '永久禁用',
findTime: '发现周期',
banAction: '禁用方式',
banActionOption: '通过 {0} 来禁用指定的 IP 地址',

View File

@ -246,7 +246,7 @@ const onOperate = async (operation: string) => {
});
})
.catch(() => {
autoStart.value = operation === 'enable' ? 'disable' : 'enable';
search();
});
};

View File

@ -4,14 +4,17 @@
<template #header>
<DrawerHeader :header="$t('toolbox.fail2ban.banTime')" :back="handleClose" />
</template>
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form
ref="formRef"
label-position="top"
:model="form"
:rules="rules"
@submit.prevent
v-loading="loading"
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item
:label="$t('toolbox.fail2ban.banTime')"
prop="banTime"
:rules="Rules.integerNumber"
>
<el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime">
<el-input type="number" v-model.number="form.banTime">
<template #append>
<el-select v-model.number="form.banTimeUnit" style="width: 100px">
@ -43,7 +46,6 @@ import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { Rules } from '@/global/form-rules';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { updateFail2ban } from '@/api/modules/toolbox';
import { splitTime, transTimeUnit } from '@/utils/util';
@ -61,6 +63,20 @@ const form = reactive({
banTimeUnit: 's',
});
const rules = reactive({
banTime: [{ validator: checkBanTime, trigger: 'blur' }],
});
function checkBanTime(rule: any, value: any, callback: any) {
if (value === -1) {
callback();
}
const reg = /^[1-9]\d*$/;
if (!reg.test(value)) {
return callback(new Error(i18n.global.t('toolbox.fail2ban.banTimeRule')));
}
callback();
}
const formRef = ref<FormInstance>();
const acceptParams = (params: DialogProps): void => {
@ -74,11 +90,12 @@ const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let itemMsg =
form.banTime === -1
? i18n.global.t('toolbox.fail2ban.banAllTime')
: form.banTime + transTimeUnit(form.banTimeUnit);
ElMessageBox.confirm(
i18n.global.t('ssh.sshChangeHelper', [
i18n.global.t('toolbox.fail2ban.banTime'),
form.banTime + transTimeUnit(form.banTimeUnit),
]),
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('toolbox.fail2ban.banTime'), itemMsg]),
i18n.global.t('toolbox.fail2ban.fail2banChange'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
@ -86,7 +103,8 @@ const onSave = async (formEl: FormInstance | undefined) => {
type: 'info',
},
).then(async () => {
await updateFail2ban({ key: 'bantime', value: form.banTime + form.banTimeUnit })
let itemValue = form.banTime === -1 ? '-1' : form.banTime + form.banTimeUnit;
await updateFail2ban({ key: 'bantime', value: itemValue })
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;

View File

@ -65,6 +65,16 @@
<el-col :span="1"><br /></el-col>
<el-col :xs="24" :sm="20" :md="20" :lg="10" :xl="10">
<el-form :model="form" label-position="left" ref="formRef" label-width="120px">
<el-form-item :label="$t('toolbox.fail2ban.sshPort')" prop="port">
<el-input disabled v-model="form.port">
<template #append>
<el-button @click="onChangePort" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
<span class="input-help">{{ $t('toolbox.fail2ban.sshPortHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('toolbox.fail2ban.maxRetry')" prop="maxRetry">
<el-input disabled v-model="form.maxRetry">
<template #append>
@ -164,6 +174,7 @@
<FindTime ref="findTimeRef" @search="search" />
<BanAction ref="banActionRef" @search="search" />
<LogPath ref="logPathRef" @search="search" />
<Port ref="portRef" @search="search" />
<IPs ref="listRef" />
</div>
@ -179,6 +190,7 @@ import BanTime from '@/views/toolbox/fail2ban/ban-time/index.vue';
import FindTime from '@/views/toolbox/fail2ban/find-time/index.vue';
import BanAction from '@/views/toolbox/fail2ban/ban-action/index.vue';
import LogPath from '@/views/toolbox/fail2ban/log-path/index.vue';
import Port from '@/views/toolbox/fail2ban/port/index.vue';
import IPs from '@/views/toolbox/fail2ban/ips/index.vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@ -191,6 +203,7 @@ const formRef = ref();
const extensions = [javascript(), oneDark];
const confShowType = ref('base');
const portRef = ref();
const maxRetryRef = ref();
const banTimeRef = ref();
const findTimeRef = ref();
@ -242,6 +255,9 @@ const onSaveFile = async () => {
});
});
};
const onChangePort = () => {
portRef.value.acceptParams({ port: form.port });
};
const onChangeMaxRetry = () => {
maxRetryRef.value.acceptParams({ maxRetry: form.maxRetry });
};
@ -279,7 +295,7 @@ const onOperate = async (operation: string) => {
});
})
.catch(() => {
autoStart.value = operation === 'enable' ? 'disable' : 'enable';
search();
});
};
@ -307,7 +323,8 @@ const search = async () => {
form.port = res.data.port;
form.maxRetry = res.data.maxRetry;
form.banTime = res.data.banTime;
form.banTimeItem = transTimeUnit(form.banTime);
form.banTimeItem =
form.banTime === '-1' ? i18n.global.t('toolbox.fail2ban.banAllTime') : transTimeUnit(form.banTime);
form.findTime = res.data.findTime;
form.findTimeItem = transTimeUnit(form.findTime);
form.banAction = res.data.banAction;

View File

@ -0,0 +1,100 @@
<template>
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('toolbox.fail2ban.sshPort')" :back="handleClose" />
</template>
<el-form
ref="formRef"
label-position="top"
:rules="rules"
:model="form"
@submit.prevent
v-loading="loading"
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('toolbox.fail2ban.sshPort')" prop="port">
<el-input type="number" clearable v-model.number="form.port" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { ElMessageBox, FormInstance } from 'element-plus';
import { Rules, checkNumberRange } from '@/global/form-rules';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { updateFail2ban } from '@/api/modules/toolbox';
const emit = defineEmits<{ (e: 'search'): void }>();
interface DialogProps {
port: string;
}
const drawerVisible = ref();
const loading = ref();
const form = reactive({
port: 22,
});
const rules = reactive({
port: [Rules.integerNumber, checkNumberRange(1, 65535)],
});
const formRef = ref<FormInstance>();
const acceptParams = (params: DialogProps): void => {
form.port = Number(params.port);
drawerVisible.value = true;
};
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
ElMessageBox.confirm(
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('toolbox.fail2ban.sshPort'), form.port]),
i18n.global.t('toolbox.fail2ban.fail2banChange'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(async () => {
await updateFail2ban({ key: 'port', value: form.port + '' })
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;
drawerVisible.value = false;
emit('search');
})
.catch(() => {
loading.value = false;
});
});
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>