fix(firewall): validate whitelist ports and sources in form (#13835)

This commit is contained in:
ssongliu
2026-09-16 21:31:06 +08:00
committed by GitHub
parent 78402e1b7d
commit e864610015
13 changed files with 55 additions and 23 deletions

View File

@@ -4318,7 +4318,6 @@ const message = {
'Set one port allowed by the firewall. Manage the listening port in the service settings.',
whitelistSourcesHelper:
'Enter IP addresses or CIDR ranges, separated by commas or new lines. Leave blank to allow all IPv4 and IPv6 sources.',
systemAccessSourceError: 'Enter at least one valid {0} address or CIDR range.',
destinationPortPlaceholder: 'e.g. 80, 80,443, or 8080-8089',
deleteRuleConfirm: 'Will delete {0} rules. Continue?',
deleteUsedRuleConfirm:

View File

@@ -4371,7 +4371,6 @@ const message = {
'Configure un único puerto permitido por el cortafuegos. El puerto de escucha se gestiona en la configuración del servicio.',
whitelistSourcesHelper:
'Introduzca direcciones IP o rangos CIDR separados por comas o saltos de línea. Déjelo en blanco para permitir todos los orígenes IPv4 e IPv6.',
systemAccessSourceError: 'Introduzca al menos una dirección o red CIDR {0} válida.',
destinationPortPlaceholder: 'p. ej. 80, 80,443 o 8080-8089',
deleteRuleConfirm: 'Se eliminarán {0} reglas. ¿Continuar?',
deleteUsedRuleConfirm:

View File

@@ -4275,7 +4275,6 @@ const message = {
'یک درگاه مجاز برای دیواره آتش تنظیم کنید. درگاه شنود را در تنظیمات سرویس تغییر دهید.',
whitelistSourcesHelper:
'آدرس‌های IP یا محدوده‌های CIDR را با ویرگول یا خط جدید جدا کنید. برای مجاز کردن همه مبدأهای IPv4 و IPv6، این قسمت را خالی بگذارید.',
systemAccessSourceError: 'حداقل یک نشانی {0} یا بازه CIDR معتبر وارد کنید.',
destinationPortPlaceholder: 'مثلاً: 80، 80,443 یا 8080-8089',
deleteRuleConfirm: '{0} قانون حذف خواهند شد. ادامه می‌دهید؟',
deleteUsedRuleConfirm:

View File

@@ -4301,7 +4301,6 @@ const message = {
'ファイアウォールで許可するポートを1つ設定します待ち受けポートは各サービスの設定で変更してください',
whitelistSourcesHelper:
'IP アドレスまたは CIDR をカンマか改行で区切って入力してください 空欄の場合すべての IPv4 および IPv6 の接続元を許可します',
systemAccessSourceError: '有効な {0} アドレスまたは CIDR を1つ以上入力してください',
destinationPortPlaceholder: ': 8080,4438080-8089',
deleteRuleConfirm: '{0} 個のルールを削除します続行しますか',
deleteUsedRuleConfirm:

View File

@@ -4222,7 +4222,6 @@ const message = {
'방화벽에서 허용할 포트 하나를 설정합니다. 수신 포트는 해당 서비스 설정에서 변경하세요.',
whitelistSourcesHelper:
'IP 주소 또는 CIDR 대역을 쉼표나 줄바꿈으로 구분하세요. 비워 두면 모든 IPv4 및 IPv6 출발지를 허용합니다.',
systemAccessSourceError: '유효한 {0} 주소 또는 CIDR 대역을 하나 이상 입력하세요.',
destinationPortPlaceholder: '예: 80, 80,443 또는 8080-8089',
deleteRuleConfirm: '{0}개의 규칙을 삭제합니다. 계속하시겠습니까?',
deleteUsedRuleConfirm:

View File

@@ -4185,7 +4185,6 @@ const message = {
whitelistServicePortsHelper: 'ຕັ້ງຄ່າໜຶ່ງພອດທີ່ໄຟວໍອະນຸຍາດ. ປ່ຽນພອດຮັບຟັງໃນການຕັ້ງຄ່າບໍລິການ.',
whitelistSourcesHelper:
'ປ້ອນທີ່ຢູ່ IP ຫຼື CIDR ໂດຍແຍກດ້ວຍຈຸດຈ້ຳ ຫຼື ຂຶ້ນແຖວໃໝ່. ຖ້າປະໄວ້ຫວ່າງ ຈະອະນຸຍາດທຸກແຫຼ່ງທີ່ມາ IPv4 ແລະ IPv6.',
systemAccessSourceError: 'ປ້ອນທີ່ຢູ່ {0} ຫຼື CIDR ທີ່ຖືກຕ້ອງຢ່າງໜ້ອຍໜຶ່ງລາຍການ.',
destinationPortPlaceholder: 'ຕົວຢ່າງ: 80, 80,443 ຫຼື 8080-8089',
deleteRuleConfirm: 'ຈະລຶບ {0} ກົດລະບຽບ. ຕ້ອງການຕໍ່ຫຼືບໍ່?',
deleteUsedRuleConfirm:

View File

@@ -4385,7 +4385,6 @@ const message = {
'Tetapkan satu port yang dibenarkan oleh tembok api. Urus port pendengar dalam tetapan perkhidmatan.',
whitelistSourcesHelper:
'Masukkan alamat IP atau julat CIDR, dipisahkan dengan koma atau baris baharu. Biarkan kosong untuk membenarkan semua sumber IPv4 dan IPv6.',
systemAccessSourceError: 'Masukkan sekurang-kurangnya satu alamat {0} atau julat CIDR yang sah.',
destinationPortPlaceholder: 'contoh: 80, 80,443 atau 8080-8089',
deleteRuleConfirm: 'Akan memadam {0} peraturan. Teruskan?',
deleteUsedRuleConfirm:

View File

@@ -4407,7 +4407,6 @@ const message = {
'Defina uma única porta permitida pelo firewall. Gerencie a porta de escuta nas configurações do serviço.',
whitelistSourcesHelper:
'Insira endereços IP ou intervalos CIDR separados por vírgulas ou quebras de linha. Deixe em branco para permitir todas as origens IPv4 e IPv6.',
systemAccessSourceError: 'Informe pelo menos um endereço {0} ou intervalo CIDR válido.',
destinationPortPlaceholder: 'por exemplo: 80, 80,443 ou 8080-8089',
deleteRuleConfirm: 'Excluirá {0} regras. Continuar?',
deleteUsedRuleConfirm:

View File

@@ -4373,7 +4373,6 @@ const message = {
'Укажите один порт, разрешённый брандмауэром. Порт прослушивания меняется в настройках службы.',
whitelistSourcesHelper:
'Введите IP-адреса или подсети CIDR через запятую или с новой строки. Оставьте поле пустым, чтобы разрешить все источники IPv4 и IPv6.',
systemAccessSourceError: 'Укажите хотя бы один допустимый адрес {0} или диапазон CIDR.',
destinationPortPlaceholder: 'например: 80, 80,443 или 8080-8089',
deleteRuleConfirm: 'Удалит {0} правил. Продолжить?',
deleteUsedRuleConfirm:

View File

@@ -4389,7 +4389,6 @@ const message = {
'Güvenlik duvarının izin verdiği tek bir port ayarlayın. Dinleme portunu hizmet ayarlarından yönetin.',
whitelistSourcesHelper:
'IP adreslerini veya CIDR aralıklarını virgül ya da yeni satırla ayırın. Tüm IPv4 ve IPv6 kaynaklarına izin vermek için boş bırakın.',
systemAccessSourceError: 'En az bir geçerli {0} adresi veya CIDR aralığı girin.',
destinationPortPlaceholder: 'örn. 80, 80,443 veya 8080-8089',
deleteRuleConfirm: '{0} kural silinecek. Devam etmek istiyor musunuz?',
deleteUsedRuleConfirm:

View File

@@ -4022,7 +4022,6 @@ const message = {
systemAccessChangeConfirm: '對 {0} 白名單規則執行此操作後,可能無法透過該服務遠端存取。是否繼續?',
whitelistServicePortsHelper: '設定一個防火牆放行連接埠,服務監聽連接埠請在對應服務設定中修改。',
whitelistSourcesHelper: '支援 IP 或 CIDR多個以逗號或換行分隔。留空預設允許所有 IPv4 和 IPv6 來源。',
systemAccessSourceError: '請至少填寫一個有效的 {0} 位址或 CIDR 網段。',
destinationPortPlaceholder: '例如80、80,443 或 8080-8089',
deleteRuleConfirm: '將刪除 {0} 條規則,是否繼續?',
deleteUsedRuleConfirm: '該規則涵蓋由 {0} 提供的監聽服務。刪除後可能導致相關服務無法存取,是否繼續?',

View File

@@ -4070,7 +4070,6 @@ const message = {
systemAccessChangeConfirm: '对 {0} 白名单规则执行此操作后,可能无法通过该服务远程访问。是否继续?',
whitelistServicePortsHelper: '设置一个防火墙放行端口,服务监听端口请在对应服务设置中修改。',
whitelistSourcesHelper: '支持 IP 或 CIDR多个以逗号或换行分隔。留空默认允许所有 IPv4 和 IPv6 来源。',
systemAccessSourceError: '请至少填写一个有效的 {0} 地址或 CIDR 网段。',
destinationPortPlaceholder: '例如80、80,443 或 8080-8089',
deleteRuleConfirm: '将删除 {0} 条规则,是否继续?',
deleteUsedRuleConfirm: '该规则覆盖了由 {0} 提供的监听服务。删除后可能导致相关服务无法访问,是否继续?',

View File

@@ -46,7 +46,14 @@
:title="$t(editingRule ? 'commons.button.edit' : 'commons.button.create')"
:show-close="!saving"
>
<el-form label-position="top" :disabled="saving" @submit.prevent="saveRule">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
:disabled="saving"
@submit.prevent="saveRule"
>
<el-form-item :label="$t('commons.table.type')">
<el-select v-model="form.type" :disabled="!!editingRule">
<el-option value="custom" :label="$t('website.other')" />
@@ -65,16 +72,16 @@
</el-select>
</el-form-item>
<template v-if="form.type === 'custom'">
<el-form-item :label="$t('firewall.portOrRange')" required>
<el-form-item :label="$t('firewall.portOrRange')" prop="port">
<el-input v-model.trim="form.port" placeholder="80 / 8000-8100" clearable />
<span class="input-help">{{ $t('firewall.portWhiteListHelper') }}</span>
</el-form-item>
</template>
<el-form-item v-else-if="editingRule" :label="$t('commons.table.port')" required>
<el-form-item v-else-if="editingRule" :label="$t('commons.table.port')" prop="port">
<el-input v-model.trim="form.port" :placeholder="form.type === 'ssh' ? '2222' : '18443'" clearable />
<span class="input-help">{{ $t('firewall.whitelistServicePortsHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('firewall.allowedSources')">
<el-form-item :label="$t('firewall.allowedSources')" prop="sourceInput">
<el-input
v-model.trim="form.sourceInput"
type="textarea"
@@ -102,8 +109,13 @@ import {
} from '@/api/modules/firewall';
import i18n from '@/lang';
import { MsgError } from '@/utils/message';
import { formatHostAddressList, isValidIPOrCIDR, splitTagValues } from '@/views/host/firewall/utils/validation';
import { ElMessageBox } from 'element-plus';
import {
formatHostAddressList,
isValidIPOrCIDR,
isValidPortRange,
splitTagValues,
} from '@/views/host/firewall/utils/validation';
import { ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
import { normalizeWhiteListRule, WhiteListProtocol, WhiteListRule, WhiteListType, whiteListRuleKey } from './model';
const props = defineProps<{
@@ -120,12 +132,45 @@ const busy = computed(() => props.loading || saving.value);
const disabled = computed(() => busy.value || !props.rules);
const data = computed(() => props.rules || []);
const editingRule = ref<WhiteListRule>();
const formRef = ref<FormInstance>();
const form = ref({
type: 'custom' as WhiteListType | 'custom',
protocol: 'tcp' as WhiteListProtocol,
port: '',
sourceInput: '',
});
const rules: FormRules = {
port: [
{
required: true,
validator: (_rule, value: string, callback) => {
if (!value) {
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
return;
}
if (!isValidPortRange(value) || (form.value.type !== 'custom' && !/^\d+$/.test(value))) {
const message = form.value.type === 'custom' ? 'firewall.portFormatError' : 'commons.rule.port';
callback(new Error(i18n.global.t(message)));
return;
}
callback();
},
trigger: ['blur', 'change'],
},
],
sourceInput: [
{
validator: (_rule, value: string, callback) => {
if (splitTagValues([value]).some((source) => !isValidIPOrCIDR(source))) {
callback(new Error(i18n.global.t('commons.rule.ip')));
return;
}
callback();
},
trigger: ['blur', 'change'],
},
],
};
const serviceTypes: WhiteListType[] = ['ssh', 'panel'];
const serviceLabel = (type: WhiteListType) => (type === 'panel' ? '1Panel' : 'SSH');
const servicePortLabel = (type: WhiteListType) =>
@@ -145,19 +190,18 @@ const openEditor = (rule?: WhiteListRule) => {
port: rule?.port || '',
sourceInput: formatHostAddressList(rule ? rule.sources || [] : ['0.0.0.0/0', '::/0']),
};
formRef.value?.clearValidate();
dialogVisible.value = true;
};
const saveRule = async () => {
if (disabled.value) return;
if (disabled.value || !formRef.value) return;
const valid = await formRef.value.validate().catch(() => false);
if (!valid) return;
const sources = splitTagValues([form.value.sourceInput]);
if (!sources.length) {
sources.push('0.0.0.0/0', '::/0');
}
if (sources.some((source) => !isValidIPOrCIDR(source))) {
MsgError(i18n.global.t('firewall.systemAccessSourceError', ['IPv4 / IPv6']));
return;
}
let rule: WhiteListRule;
try {
rule = normalizeWhiteListRule({