feat: support firewall port whitelist management (#12912)

This commit is contained in:
ssongliu
2026-06-01 18:26:54 +08:00
committed by GitHub
parent ba9a8592ee
commit 7b7f840c2e
21 changed files with 576 additions and 81 deletions
+3 -1
View File
@@ -25,6 +25,8 @@ type SettingInfo struct {
FileRecycleBin string `json:"fileRecycleBin"`
LocalSSHConnShow string `json:"localSSHConnShow"`
FirewallPortWhiteList string `json:"firewallPortWhiteList"`
}
type SettingUpdate struct {
@@ -33,7 +35,7 @@ type SettingUpdate struct {
}
type AgentSettingUpdate struct {
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin"`
Key string `json:"key" validate:"required,oneof=SystemIP DockerSockPath FileRecycleBin FirewallPortWhiteList"`
Value string `json:"value"`
}
+6 -22
View File
@@ -579,30 +579,14 @@ func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
}
func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error {
if !global.IsMaster {
if err := client.Port(fireClient.FireInfo{Port: global.CONF.Base.Port, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
portWhiteList, err := loadFirewallPortWhiteList()
if err != nil {
return err
}
for _, item := range portWhiteList {
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "add"); err != nil {
return err
}
} else {
var portSetting model.Setting
_ = global.CoreDB.Where("key = ?", "ServerPort").First(&portSetting).Error
if len(portSetting.Value) != 0 {
if err := client.Port(fireClient.FireInfo{Port: portSetting.Value, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
}
}
if err := client.Port(fireClient.FireInfo{Port: loadSSHPort(), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "80", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "udp", Strategy: "accept"}, "add"); err != nil {
return err
}
return client.Reload()
+152
View File
@@ -0,0 +1,152 @@
package service
import (
"fmt"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
fireClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
)
type firewallPortWhitelist struct {
Port string
Protocol string
}
func loadFirewallPortWhiteList() ([]firewallPortWhitelist, error) {
value, err := settingRepo.GetValueByKey(constant.FirewallPortWhiteList)
if err != nil {
value = constant.FirewallPortWhiteListValue
if err := settingRepo.UpdateOrCreate(constant.FirewallPortWhiteList, value); err != nil {
return nil, err
}
}
return parseFullFirewallPortWhiteList(value)
}
func parseFullFirewallPortWhiteList(value string) ([]firewallPortWhitelist, error) {
portWhiteList, err := parseFirewallPortWhiteList(value)
if err != nil {
return nil, err
}
panelPort := LoadPanelPort()
if panelPort == "" {
return nil, fmt.Errorf("find 1panel service port failed")
}
portWhiteList = append(portWhiteList, firewallPortWhitelist{Port: panelPort, Protocol: "tcp"})
portWhiteList = append(portWhiteList, firewallPortWhitelist{Port: loadSSHPort(), Protocol: "tcp"})
return normalizeFirewallPortWhiteList(portWhiteList), nil
}
func parseFirewallPortWhiteList(value string) ([]firewallPortWhitelist, error) {
items := strings.FieldsFunc(value, func(r rune) bool {
return r == ',' || r == '\n' || r == ';' || r == ' '
})
ports := make([]firewallPortWhitelist, 0, len(items))
exists := make(map[string]struct{})
for _, item := range items {
item = strings.TrimSpace(item)
if item == "" {
continue
}
port, protocol, ok := strings.Cut(item, "/")
if !ok {
protocol = "tcp"
}
port = strings.TrimSpace(port)
protocol = strings.ToLower(strings.TrimSpace(protocol))
if protocol != "tcp" && protocol != "udp" {
return nil, fmt.Errorf("invalid firewall port whitelist protocol: %s", item)
}
portNum, err := strconv.Atoi(port)
if err != nil || portNum < 1 || portNum > 65535 {
return nil, fmt.Errorf("invalid firewall port whitelist: %s", item)
}
key := fmt.Sprintf("%d/%s", portNum, protocol)
if _, ok := exists[key]; ok {
continue
}
exists[key] = struct{}{}
ports = append(ports, firewallPortWhitelist{Port: strconv.Itoa(portNum), Protocol: protocol})
}
return ports, nil
}
func normalizeFirewallPortWhiteList(portWhiteList []firewallPortWhitelist) []firewallPortWhitelist {
ports := make([]firewallPortWhitelist, 0, len(portWhiteList))
exists := make(map[string]struct{})
for _, item := range portWhiteList {
if item.Port == "" {
continue
}
key := fmt.Sprintf("%s/%s", item.Port, item.Protocol)
if _, ok := exists[key]; ok {
continue
}
exists[key] = struct{}{}
ports = append(ports, item)
}
return ports
}
func syncFirewallPortWhiteListAfterUpdate(oldValue string) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
portWhiteList, err := loadFirewallPortWhiteList()
if err != nil {
return err
}
if client.Name() != "iptables" {
oldPortWhiteList, err := parseFullFirewallPortWhiteList(oldValue)
if err != nil {
return err
}
return syncFirewallClientPortWhiteList(client, oldPortWhiteList, portWhiteList)
}
isInit, _ := iptables.LoadInitStatus("iptables", "base")
if !isInit {
return nil
}
return applyFirewallPortWhiteListRules(portWhiteList, true)
}
func syncFirewallClientPortWhiteList(client firewall.FirewallClient, oldPortWhiteList, portWhiteList []firewallPortWhitelist) error {
oldPorts := firewallPortWhiteListMap(oldPortWhiteList)
newPorts := firewallPortWhiteListMap(portWhiteList)
for _, item := range oldPortWhiteList {
key := firewallPortWhiteListKey(item)
if _, ok := newPorts[key]; ok {
continue
}
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "remove"); err != nil {
return err
}
}
for _, item := range portWhiteList {
key := firewallPortWhiteListKey(item)
if _, ok := oldPorts[key]; ok {
continue
}
if err := client.Port(fireClient.FireInfo{Port: item.Port, Protocol: item.Protocol, Strategy: "accept"}, "add"); err != nil {
return err
}
}
return client.Reload()
}
func firewallPortWhiteListMap(portWhiteList []firewallPortWhitelist) map[string]struct{} {
ports := make(map[string]struct{})
for _, item := range portWhiteList {
ports[firewallPortWhiteListKey(item)] = struct{}{}
}
return ports
}
func firewallPortWhiteListKey(item firewallPortWhitelist) string {
return item.Port + "/" + item.Protocol
}
+71 -10
View File
@@ -228,6 +228,12 @@ func (s *IptablesService) Operate(req dto.IptablesOp) error {
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
return err
}
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.BasicBeforeFileName); err != nil {
return err
}
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.BasicAfterFileName); err != nil {
return err
}
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
return nil
case "bind-base-without-init":
@@ -365,17 +371,11 @@ func initPreRules() error {
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "-m", "comment", "--comment", "ESTABLISHED Whitelist"); err != nil {
return err
}
panelPort := LoadPanelPort()
if len(panelPort) == 0 {
return errors.New("find 1panel service port failed")
portWhiteList, err := loadFirewallPortWhiteList()
if err != nil {
return err
}
ports := []string{"80", "443", panelPort, loadSSHPort()}
for _, item := range ports {
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-p", "tcp", "-m", "tcp", "--dport", item, "-j", "ACCEPT"); err != nil {
return err
}
}
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p", "udp", "-m", "udp", "--dport", "443", "-j", "ACCEPT"); err != nil {
if err := applyFirewallPortWhiteListRules(portWhiteList, false); err != nil {
return err
}
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p", "tcp", "-j", "DROP"); err != nil {
@@ -387,6 +387,67 @@ func initPreRules() error {
return nil
}
func applyFirewallPortWhiteListRules(portWhiteList []firewallPortWhitelist, withSave bool) error {
if err := syncFirewallPortWhiteListRules(portWhiteList); err != nil {
return err
}
for _, item := range portWhiteList {
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-p", item.Protocol, "-m", item.Protocol, "--dport", item.Port, "-j", "ACCEPT"); err != nil {
return err
}
}
if !withSave {
return nil
}
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.BasicBeforeFileName); err != nil {
return err
}
return iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.BasicAfterFileName)
}
func syncFirewallPortWhiteListRules(portWhiteList []firewallPortWhitelist) error {
tcpWhitelist := make(map[string]struct{})
udpWhitelist := make(map[string]struct{})
for _, item := range portWhiteList {
if item.Protocol == "udp" {
udpWhitelist[item.Port] = struct{}{}
continue
}
tcpWhitelist[item.Port] = struct{}{}
}
if err := cleanExtraFirewallPortRules(iptables.Chain1PanelBasicBefore, "tcp", tcpWhitelist); err != nil {
return err
}
if err := cleanExtraFirewallPortRules(iptables.Chain1PanelBasicBefore, "udp", udpWhitelist); err != nil {
return err
}
return cleanExtraFirewallPortRules(iptables.Chain1PanelBasicAfter, "udp", map[string]struct{}{})
}
func cleanExtraFirewallPortRules(chain, protocol string, whitelist map[string]struct{}) error {
rules, err := iptables.ReadFilterRulesByChain(chain)
if err != nil {
return err
}
kept := make(map[string]struct{})
for _, rule := range rules {
if rule.Strategy != "accept" || rule.Protocol != protocol || rule.DstPort == "" || rule.SrcIP != "" || rule.DstIP != "" || rule.SrcPort != "" {
continue
}
if _, ok := whitelist[rule.DstPort]; ok {
if _, seen := kept[rule.DstPort]; !seen {
kept[rule.DstPort] = struct{}{}
continue
}
}
if err := iptables.DeleteRule(iptables.FilterTab, chain, "-p", protocol, "-m", protocol, "--dport", rule.DstPort, "-j", "ACCEPT"); err != nil {
return err
}
}
return nil
}
func LoadPanelPort() string {
if !global.IsMaster {
return global.CONF.Base.Port
+16 -1
View File
@@ -124,7 +124,22 @@ func (u *SettingService) GetWebsiteDir() string {
}
func (u *SettingService) Update(key, value string) error {
return settingRepo.UpdateOrCreate(key, value)
oldValue := constant.FirewallPortWhiteListValue
if key == constant.FirewallPortWhiteList {
if _, err := parseFirewallPortWhiteList(value); err != nil {
return err
}
if val, err := settingRepo.GetValueByKey(key); err == nil {
oldValue = val
}
}
if err := settingRepo.UpdateOrCreate(key, value); err != nil {
return err
}
if key == constant.FirewallPortWhiteList {
return syncFirewallPortWhiteListAfterUpdate(oldValue)
}
return nil
}
func (u *SettingService) UpdateTerminalAI(req dto.TerminalAIInfo) error {
+3
View File
@@ -7,6 +7,9 @@ const (
SystemRestart = "systemRestart"
FirewallPortWhiteList = "FirewallPortWhiteList"
FirewallPortWhiteListValue = "80/tcp,443/tcp,443/udp"
TypeWebsite = "website"
TypePhp = "php"
TypeSSL = "ssl"
+1
View File
@@ -84,6 +84,7 @@ func InitAgentDB() {
migrations.AddFileShareTable,
migrations.AddFileHistoryTable,
migrations.MigrateLegoV5,
migrations.InitFirewallPortWhiteList,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+17
View File
@@ -846,6 +846,23 @@ var InitIptablesStatus = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "IptablesOutputStatus", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: constant.FirewallPortWhiteList, Value: constant.FirewallPortWhiteListValue}).Error; err != nil {
return err
}
return nil
},
}
var InitFirewallPortWhiteList = &gormigrate.Migration{
ID: "20260601-init-firewall-port-whitelist",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", constant.FirewallPortWhiteList).First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: constant.FirewallPortWhiteList, Value: constant.FirewallPortWhiteListValue}).Error
}
return err
}
return nil
},
}
+1
View File
@@ -26,6 +26,7 @@ export namespace Setting {
fileRecycleBin: string;
localSSHConnShow: string;
firewallPortWhiteList: string;
}
export interface SettingInfo {
systemVersion: string;
+5
View File
@@ -3769,6 +3769,11 @@ const message = {
bindHelper: 'Bind - Firewall rules will only take effect when the status is bound. Confirm?',
unbindHelper:
'Unbind - When unbound, all added firewall rules will become invalid. Proceed with caution. Confirm?',
whiteList: 'Allowlist',
portWhiteList: 'Port allowlist',
portWhiteListAlter:
'Ports in the allowlist are opened automatically when the firewall is initialized or started.',
portWhiteListHelper: 'Protocol can be specified, e.g. 80/tcp or 443/udp. Defaults to tcp when omitted.',
defaultStrategy: 'Default policy for current chain {0} is {1}',
defaultStrategy2:
'Default policy for current chain {0} is {1}, current status is unbound. Added firewall rules will take effect after binding!',
+30 -12
View File
@@ -3819,6 +3819,12 @@ const message = {
'Vincular: las reglas de firewall solo surtirán efecto cuando el estado esté vinculado. ¿Confirmar?',
unbindHelper:
'Desvincular: al desvincular, todas las reglas de firewall agregadas se volverán inválidas. Proceda con precaución. ¿Confirmar?',
whiteList: 'Lista blanca',
portWhiteList: 'Lista blanca de puertos',
portWhiteListAlter:
'Los puertos de la lista blanca se abrirán automáticamente al inicializar o iniciar el firewall.',
portWhiteListHelper:
'Puede especificar el protocolo, por ejemplo 80/tcp o 443/udp. Si se omite, se usa tcp por defecto.',
defaultStrategy: 'La política predeterminada para la cadena actual {0} es {1}',
defaultStrategy2:
'La política predeterminada para la cadena actual {0} es {1}, el estado actual es no vinculado. ¡Las reglas de firewall agregadas surtirán efecto después de la vinculación!',
@@ -4096,7 +4102,8 @@ const message = {
scoreWebsiteHTTP: '{0} no usa HTTPS',
scoreWebsiteStopped: 'Estado anormal de {0}',
scoreWebsiteMonitorUnavailable: 'El monitoreo web detectó un sitio no disponible',
scoreWebsiteMonitorAvailability: 'La disponibilidad del monitoreo web ({0}%) está por debajo del umbral',
scoreWebsiteMonitorAvailability:
'La disponibilidad del monitoreo web ({0}%) está por debajo del umbral',
scoreWafDisabled: 'WAF deshabilitado, los sitios web no están protegidos',
scoreWafHighRiskHit: 'WAF detectó {0} reglas de riesgo en el periodo del informe',
scoreCronjobFailed: '{0} registros de fallos de tareas programadas en los últimos 7 días',
@@ -4202,8 +4209,10 @@ const message = {
'Se detectaron {0} configuraciones de riesgo. Refuerce la seguridad de acceso para reducir el riesgo de intrusión.',
loginHealthNormalDesc: 'Las configuraciones de acceso y SSH están en estado normal',
loginHealthNormalReason: 'La seguridad de acceso es normal',
securityEntranceConfiguredDesc: 'Entrada de seguridad configurada para reducir la exposición de la entrada predeterminada',
securityEntranceRiskDesc: 'Entrada de seguridad deshabilitada. Se recomienda ocultar la entrada predeterminada',
securityEntranceConfiguredDesc:
'Entrada de seguridad configurada para reducir la exposición de la entrada predeterminada',
securityEntranceRiskDesc:
'Entrada de seguridad deshabilitada. Se recomienda ocultar la entrada predeterminada',
allowIPsRestrictedDesc: 'IPs autorizadas restringidas para el acceso al panel',
allowIPsRiskDesc: 'IPs de acceso sin restricciones. Configure IPs autorizadas para entornos públicos',
bindDomainRestrictedDesc: 'Dominio de acceso vinculado',
@@ -4223,11 +4232,15 @@ const message = {
mfaRiskDesc: 'Autenticación MFA deshabilitada. Se recomienda habilitar la autenticación multifactor',
passwordExpiration: 'Fecha de vencimiento de contraseña',
passwordExpirationNormalDesc: 'La fecha de vencimiento de contraseña está en un rango seguro',
passwordExpirationUnsetDesc: 'Fecha de vencimiento de contraseña no configurada. Se recomienda configurarla pronto',
passwordExpirationRiskDesc: 'La contraseña vence en {0} días. Se recomienda gestionarla con anticipación',
passwordExpirationExpiredDesc: 'La contraseña expiró hace {0} días. Por favor, gestionar lo antes posible',
passwordExpirationUnsetDesc:
'Fecha de vencimiento de contraseña no configurada. Se recomienda configurarla pronto',
passwordExpirationRiskDesc:
'La contraseña vence en {0} días. Se recomienda gestionarla con anticipación',
passwordExpirationExpiredDesc:
'La contraseña expiró hace {0} días. Por favor, gestionar lo antes posible',
sessionTimeoutNormalDesc: 'El tiempo de espera de sesión está en un rango seguro',
sessionTimeoutRiskDesc: 'Tiempo de espera de sesión largo. Se recomienda limitar la duración de sesiones inactivas',
sessionTimeoutRiskDesc:
'Tiempo de espera de sesión largo. Se recomienda limitar la duración de sesiones inactivas',
sshServiceNormalDesc: 'El servicio SSH está ejecutándose normalmente',
sshServiceRiskDesc: 'El servicio SSH no está ejecutándose o está en estado anormal',
sshPortNormalDesc: 'El puerto SSH se ha cambiado del puerto predeterminado',
@@ -4235,9 +4248,11 @@ const message = {
rootLoginNormalDesc: 'El usuario root no puede iniciar sesión directamente',
rootLoginRiskDesc: 'El usuario root puede iniciar sesión directamente. Se recomienda deshabilitarlo',
passwordAuthNormalDesc: 'El inicio de sesión SSH por contraseña está deshabilitado',
passwordAuthRiskDesc: 'El inicio de sesión por contraseña está permitido, aumentando el riesgo de fuerza bruta',
passwordAuthRiskDesc:
'El inicio de sesión por contraseña está permitido, aumentando el riesgo de fuerza bruta',
keyAuthNormalDesc: 'La autenticación SSH por clave está habilitada',
keyAuthRiskDesc: 'La autenticación por clave no está habilitada. Se recomienda usar inicio de sesión por clave',
keyAuthRiskDesc:
'La autenticación por clave no está habilitada. Se recomienda usar inicio de sesión por clave',
websiteOverview: 'Resumen de sitios web',
primaryDomain: 'Dominio principal',
expireTime: 'Fecha de vencimiento',
@@ -4277,7 +4292,8 @@ const message = {
alertHealthAttention: 'Requiere atención',
alertHealthRisk: 'Riesgo alto',
alertHealthSummary: 'Estado de salud de alertas',
alertHealthNormalReason: 'Tareas de alerta, canales de notificación y registros de ejecución están saludables',
alertHealthNormalReason:
'Tareas de alerta, canales de notificación y registros de ejecución están saludables',
alertNoChannelReason: 'No hay canal de notificación de alerta habilitado',
alertFailedLogReason: '{0} registros de fallos de alerta encontrados',
alertPendingReason: '{0} registros de alerta pendientes de sincronización',
@@ -4354,7 +4370,8 @@ const message = {
viewHostMonitor: 'Ver monitoreo del host',
systemHealthAttentionDesc:
'{0} elementos de riesgo detectados en las últimas 24 horas. Priorice los picos de recursos y el uso de disco.',
systemHealthNormalDesc: 'Las métricas de ejecución del host estuvieron normales en las últimas 24 horas',
systemHealthNormalDesc:
'Las métricas de ejecución del host estuvieron normales en las últimas 24 horas',
monitorEnabled: 'Monitoreo habilitado',
cpuCoreUsage: '{0} / {1} núcleos',
load1Minute: 'Carga 1 min',
@@ -4521,7 +4538,8 @@ const message = {
savePath: 'Directorio de guardado',
savePathRequired: 'Configure el directorio de guardado del informe',
autoExport: 'Habilitar exportación automática',
autoExportDesc: 'Generar automáticamente un informe de operaciones cada día a las 09:00 cuando esté habilitado',
autoExportDesc:
'Generar automáticamente un informe de operaciones cada día a las 09:00 cuando esté habilitado',
autoExportEnabled: 'Exportación automática habilitada',
autoExportDisabled: 'Exportación automática deshabilitada',
readOnlyPermissionTip:
+13 -6
View File
@@ -3805,6 +3805,10 @@ const message = {
bindHelper: 'バインド - ファイアウォールルールは状態がバインドされている場合のみ有効になります確認しますか',
unbindHelper:
'アンバインド - アンバインドすると追加されたすべてのファイアウォールルールが無効になります注意して操作してください確認しますか',
whiteList: 'ホワイトリスト',
portWhiteList: 'ポートホワイトリスト',
portWhiteListAlter: 'ファイアウォールの初期化または起動時にホワイトリスト内のポートは自動的に許可されます',
portWhiteListHelper: '80/tcp 443/udp のようにプロトコルを指定できます省略時は tcp が使用されます',
defaultStrategy: '現在のチェーン {0} のデフォルトポリシーは {1} です',
defaultStrategy2:
'現在のチェーン {0} のデフォルトポリシーは {1} です現在の状態は未バインドです追加されたファイアウォールルールはバインド後に有効になります',
@@ -4184,12 +4188,15 @@ const message = {
'{0} 件のリスク設定を検出しましたログインセキュリティを強化して侵入リスクを低減してください',
loginHealthNormalDesc: '現在のログインとSSHセキュリティ設定は正常です',
loginHealthNormalReason: 'ログインセキュリティは正常です',
securityEntranceConfiguredDesc: 'セキュリティエントランスが設定済みでデフォルト入口の露出リスクを低減しています',
securityEntranceRiskDesc: 'セキュリティエントランスが無効ですデフォルトのログイン入口を隠すことをお勧めします',
securityEntranceConfiguredDesc:
'セキュリティエントランスが設定済みでデフォルト入口の露出リスクを低減しています',
securityEntranceRiskDesc:
'セキュリティエントランスが無効ですデフォルトのログイン入口を隠すことをお勧めします',
allowIPsRestrictedDesc: 'パネルアクセス許可IPが制限されています',
allowIPsRiskDesc: 'アクセスIPが制限されていません公開環境では許可IPの設定をお勧めします',
bindDomainRestrictedDesc: 'アクセスドメインがバインドされています',
bindDomainRiskDesc: 'アクセスドメインがバインドされていません固定ドメインへのアクセス制限をお勧めします',
bindDomainRiskDesc:
'アクセスドメインがバインドされていません固定ドメインへのアクセス制限をお勧めします',
accessRestrictionValue: '許可IP: {0} / バインドドメイン: {1}',
accessRestrictionNormalDesc:
'許可IPまたはバインドドメインが設定済みでパネルアクセス元が制限されています',
@@ -4198,8 +4205,7 @@ const message = {
panelHTTPSNormalDesc: 'パネルHTTPSが有効です',
panelHTTPSRiskDesc: 'パネルHTTPSが無効ですログイン通信が平文で露出するリスクがあります',
passwordComplexityNormalDesc: 'パスワード複雑性検証が有効です',
passwordComplexityRiskDesc:
'パスワード複雑性検証が無効で弱パスワードのリスクが高いです',
passwordComplexityRiskDesc: 'パスワード複雑性検証が無効で弱パスワードのリスクが高いです',
mfa: 'MFA 認証',
mfaNormalDesc: 'MFA認証が有効でログインセキュリティが強化されています',
mfaRiskDesc: 'MFA認証が無効です多要素認証の有効化をお勧めします',
@@ -4209,7 +4215,8 @@ const message = {
passwordExpirationRiskDesc: 'パスワードが{0}日以内に期限切れになります事前に対応してください',
passwordExpirationExpiredDesc: 'パスワードが{0}日前に期限切れになりました至急対応してください',
sessionTimeoutNormalDesc: 'セッションタイムアウトは安全な範囲内です',
sessionTimeoutRiskDesc: 'セッションタイムアウトが長めですアイドルセッションの有効期間を制限することをお勧めします',
sessionTimeoutRiskDesc:
'セッションタイムアウトが長めですアイドルセッションの有効期間を制限することをお勧めします',
sshServiceNormalDesc: 'SSHサービスは正常に稼働しています',
sshServiceRiskDesc: 'SSHサービスが未稼働または異常です',
sshPortNormalDesc: 'SSHポートがデフォルトポートから変更済みです',
+6 -4
View File
@@ -3718,6 +3718,10 @@ const message = {
bindHelper: '바인딩 - 방화벽 규칙은 상태가 바인딩된 경우에만 효과가 있습니다. 확인하시겠습니까?',
unbindHelper:
'바인딩 해제 - 바인딩 해제 추가된 모든 방화벽 규칙이 무효화됩니다. 주의하여 진행하세요. 확인하시겠습니까?',
whiteList: '화이트리스트',
portWhiteList: '포트 화이트리스트',
portWhiteListAlter: '방화벽을 초기화하거나 시작할 화이트리스트의 포트가 자동으로 허용됩니다.',
portWhiteListHelper: '80/tcp 또는 443/udp처럼 프로토콜을 지정할 있습니다. 생략하면 기본값은 tcp입니다.',
defaultStrategy: '현재 체인 {0} 기본 정책은 {1}입니다',
defaultStrategy2:
'현재 체인 {0} 기본 정책은 {1}입니다. 현재 상태는 바인딩되지 않았습니다. 추가된 방화벽 규칙은 바인딩 후에 효과가 발생합니다!',
@@ -3952,8 +3956,7 @@ const message = {
reportDate: '보고서 날짜',
serverSecurityOverview: '서버 보안 운영 개요',
securityScore: '보안 점수',
overviewSummary:
'현재 보안 등급: {0}. 감점 {1}, {2} 위험 항목 발견, {3} 검사 대상 포함.',
overviewSummary: '현재 보안 등급: {0}. 감점 {1}, {2} 위험 항목 발견, {3} 검사 대상 포함.',
riskDistribution: '위험 분포',
totalDeducted: ' 감점',
noRiskDeducted: '미감점',
@@ -4092,8 +4095,7 @@ const message = {
recent24Hours: '최근 24시간',
currentService: '현재 서비스',
riskItemsExist: '{0} 위험 항목 존재',
loginHealthRiskDesc:
'{0}개의 위험 설정이 감지되었습니다. 로그인 보안을 강화하여 침입 위험을 낮추세요.',
loginHealthRiskDesc: '{0}개의 위험 설정이 감지되었습니다. 로그인 보안을 강화하여 침입 위험을 낮추세요.',
loginHealthNormalDesc: '현재 로그인 SSH 보안 설정이 정상 상태입니다',
loginHealthNormalReason: '로그인 보안 설정 정상',
securityEntranceConfiguredDesc: '보안 입구가 구성되어 기본 입구 노출 위험이 감소됩니다',
+10 -2
View File
@@ -3857,6 +3857,12 @@ const message = {
bindHelper: 'Ikat - Peraturan firewall hanya akan berkuat kuasa apabila status terikat. Sahkan?',
unbindHelper:
'Nyahikat - Apabila tidak terikat, semua peraturan firewall yang ditambah akan menjadi tidak sah. Teruskan dengan berhati-hati. Sahkan?',
whiteList: 'Senarai putih',
portWhiteList: 'Senarai putih port',
portWhiteListAlter:
'Port dalam senarai putih akan dibuka secara automatik apabila firewall dimulakan atau dihidupkan.',
portWhiteListHelper:
'Protokol boleh ditentukan, contohnya 80/tcp atau 443/udp. Jika diabaikan, tcp digunakan secara lalai.',
defaultStrategy: 'Dasar lalai untuk rantaian semasa {0} adalah {1}',
defaultStrategy2:
'Dasar lalai untuk rantaian semasa {0} adalah {1}, status semasa adalah tidak terikat. Peraturan firewall yang ditambah akan berkuat kuasa selepas pengikatan!',
@@ -4239,8 +4245,10 @@ const message = {
'{0} konfigurasi berisiko dikesan. Kukuhkan keselamatan log masuk untuk mengurangkan risiko pencerobohan.',
loginHealthNormalDesc: 'Konfigurasi keselamatan log masuk dan SSH semasa adalah normal',
loginHealthNormalReason: 'Keselamatan log masuk adalah normal',
securityEntranceConfiguredDesc: 'Pintu masuk keselamatan dikonfigurasi untuk mengurangkan pendedahan laluan lalai',
securityEntranceRiskDesc: 'Pintu masuk keselamatan tidak didayakan. Sembunyikan pintu masuk log masuk lalai',
securityEntranceConfiguredDesc:
'Pintu masuk keselamatan dikonfigurasi untuk mengurangkan pendedahan laluan lalai',
securityEntranceRiskDesc:
'Pintu masuk keselamatan tidak didayakan. Sembunyikan pintu masuk log masuk lalai',
allowIPsRestrictedDesc: 'IP dibenarkan dihadkan untuk akses panel',
allowIPsRiskDesc: 'IP akses tidak dihadkan. Konfigurasikan IP dibenarkan untuk persekitaran awam',
bindDomainRestrictedDesc: 'Domain akses telah diikat',
+10 -2
View File
@@ -3993,6 +3993,12 @@ const message = {
'Vincular - As regras de firewall entrarão em vigor quando o status estiver vinculado. Confirmar?',
unbindHelper:
'Desvincular - Quando desvinculado, todas as regras de firewall adicionadas se tornarão inválidas. Prossiga com cautela. Confirmar?',
whiteList: 'Lista branca',
portWhiteList: 'Lista branca de portas',
portWhiteListAlter:
'As portas na lista branca serão abertas automaticamente ao inicializar ou iniciar o firewall.',
portWhiteListHelper:
'O protocolo pode ser especificado, por exemplo 80/tcp ou 443/udp. Se omitido, o padrão é tcp.',
defaultStrategy: 'A política padrão para a cadeia atual {0} é {1}',
defaultStrategy2:
'A política padrão para a cadeia atual {0} é {1}, o status atual é não vinculado. As regras de firewall adicionadas entrarão em vigor após a vinculação!',
@@ -4378,7 +4384,8 @@ const message = {
'{0} configurações de risco detectadas. Fortaleça a segurança de login para reduzir o risco de invasão.',
loginHealthNormalDesc: 'As configurações atuais de login e segurança SSH estão normais',
loginHealthNormalReason: 'Segurança de login está normal',
securityEntranceConfiguredDesc: 'Entrada de segurança configurada para reduzir a exposição da entrada padrão',
securityEntranceConfiguredDesc:
'Entrada de segurança configurada para reduzir a exposição da entrada padrão',
securityEntranceRiskDesc: 'Entrada de segurança desativada. Oculte a entrada de login padrão',
allowIPsRestrictedDesc: 'IPs autorizados estão restritos para acesso ao painel',
allowIPsRiskDesc: 'IPs de acesso sem restrição. Configure IPs autorizados para ambientes públicos',
@@ -4445,7 +4452,8 @@ const message = {
alertHealthAttention: 'Atenção necessária',
alertHealthRisk: 'Alto risco',
alertHealthSummary: 'Saúde do alerta',
alertHealthNormalReason: 'Tarefas de alerta, canais de notificação e registros de execução estão saudáveis',
alertHealthNormalReason:
'Tarefas de alerta, canais de notificação e registros de execução estão saudáveis',
alertNoChannelReason: 'Nenhum canal de notificação está ativado',
alertFailedLogReason: '{0} registros de alerta com falha encontrados',
alertPendingReason: '{0} registros de alerta pendentes de sincronização',
+11 -3
View File
@@ -3848,6 +3848,11 @@ const message = {
bindHelper: 'Привязать - Правила брандмауэра вступят в силу только когда статус привязан. Подтвердить?',
unbindHelper:
'Отвязать - При отвязке все добавленные правила брандмауэра станут недействительными. Действуйте осторожно. Подтвердить?',
whiteList: 'Белый список',
portWhiteList: 'Белый список портов',
portWhiteListAlter:
'Порты из белого списка будут автоматически открыты при инициализации или запуске брандмауэра.',
portWhiteListHelper: 'Можно указать протокол, например 80/tcp или 443/udp. Если не указано, используется tcp.',
defaultStrategy: 'Политика по умолчанию для текущей цепочки {0} - {1}',
defaultStrategy2:
'Политика по умолчанию для текущей цепочки {0} - {1}, текущий статус - не привязан. Добавленные правила брандмауэра вступят в силу после привязки!',
@@ -4230,7 +4235,8 @@ const message = {
'Обнаружено {0} рисковых настроек. Рекомендуется усилить безопасность входа для снижения риска вторжения.',
loginHealthNormalDesc: 'Текущие настройки безопасности входа и SSH в норме',
loginHealthNormalReason: 'Настройки безопасности входа в норме',
securityEntranceConfiguredDesc: 'Безопасный вход настроен, снижается риск раскрытия стандартной точки входа',
securityEntranceConfiguredDesc:
'Безопасный вход настроен, снижается риск раскрытия стандартной точки входа',
securityEntranceRiskDesc: 'Безопасный вход не включён. Рекомендуется скрыть стандартную точку входа',
allowIPsRestrictedDesc: 'Доступ к панели ограничен по IP-адресам',
allowIPsRiskDesc: 'Доступ по IP не ограничен. Для публичных сред рекомендуется настроить доверенные IP',
@@ -4252,10 +4258,12 @@ const message = {
passwordExpiration: 'Срок действия пароля',
passwordExpirationNormalDesc: 'Срок действия пароля находится в безопасном диапазоне',
passwordExpirationUnsetDesc: 'Срок действия пароля не задан, рекомендуется скорее настроить',
passwordExpirationRiskDesc: 'Срок действия пароля истекает через {0} дней, рекомендуется заблаговременно продлить',
passwordExpirationRiskDesc:
'Срок действия пароля истекает через {0} дней, рекомендуется заблаговременно продлить',
passwordExpirationExpiredDesc: 'Срок действия пароля истёк {0} дней назад, продлите как можно скорее',
sessionTimeoutNormalDesc: 'Тайм-аут сессии находится в безопасном диапазоне',
sessionTimeoutRiskDesc: 'Тайм-аут сессии слишком велик. Рекомендуется ограничить срок неактивных сессий',
sessionTimeoutRiskDesc:
'Тайм-аут сессии слишком велик. Рекомендуется ограничить срок неактивных сессий',
sshServiceNormalDesc: 'Служба SSH работает нормально',
sshServiceRiskDesc: 'Служба SSH не работает или находится в аномальном состоянии',
sshPortNormalDesc: 'SSH-порт изменён с порта по умолчанию',
+24 -18
View File
@@ -3849,6 +3849,12 @@ const message = {
bindHelper: 'Bağla - Güvenlik duvarı kuralları yalnızca durum bağlı olduğunda etkili olur. Onaylıyor musunuz?',
unbindHelper:
'Bağlantıyı Kaldır - Bağlantı kaldırıldığında, eklenen tüm güvenlik duvarı kuralları geçersiz olacaktır. Dikkatli ilerleyin. Onaylıyor musunuz?',
whiteList: 'Beyaz liste',
portWhiteList: 'Port beyaz listesi',
portWhiteListAlter:
'Beyaz listedeki portlar, güvenlik duvarı başlatılırken veya çalıştırılırken otomatik olarak açılır.',
portWhiteListHelper:
'Protokol belirtilebilir, örn. 80/tcp veya 443/udp. Belirtilmezse varsayılan tcp kullanılır.',
defaultStrategy: 'Mevcut zincir {0} için varsayılan politika {1}',
defaultStrategy2:
'Mevcut zincir {0} için varsayılan politika {1}, mevcut durum bağlı değil. Eklenen güvenlik duvarı kuralları bağlandıktan sonra etkili olacak!',
@@ -4113,7 +4119,7 @@ const message = {
scoreSSHLoginFailedHigh: 'SSH girişi {0} kez başarısız oldu',
scoreSSHLoginFailedMedium: 'SSH girişi {0} kez başarısız oldu',
scoreMFADisabled: 'MFA devre dışı',
scoreAllowIPsOpen: 'İzin verilen IP\'ler ve bağlı alan adı yapılandırılmamış veya erişim kısıtlanmamış',
scoreAllowIPsOpen: "İzin verilen IP'ler ve bağlı alan adı yapılandırılmamış veya erişim kısıtlanmamış",
scorePasswordExpired: 'Panel şifresi süresi dolmuş',
scorePasswordExpiring: 'Panel şifresi {0} gün içinde sona eriyor',
scorePanelHTTPSDisabled: 'Panel HTTPS devre dışı',
@@ -4196,7 +4202,7 @@ const message = {
notConfigured: 'Yapılandırılmamış',
normal: 'Normal',
needAttention: 'Dikkat Gerekli',
allowIPs: 'İzin Verilen IP\'ler',
allowIPs: "İzin Verilen IP'ler",
restricted: 'Kısıtlanmış',
unrestricted: 'Kısıtlanmamış',
bindDomain: 'Bağlı Alan Adı',
@@ -4231,22 +4237,22 @@ const message = {
'{0} riskli yapılandırma algılandı. Saldırı riskini azaltmak için giriş güvenliğini güçlendirin.',
loginHealthNormalDesc: 'Mevcut giriş ve SSH güvenlik yapılandırmaları normal durumda',
loginHealthNormalReason: 'Giriş güvenliği normal',
securityEntranceConfiguredDesc: 'Güvenlik girişi yapılandırılmış, varsayılan giriş maruziyeti azaltılmış',
securityEntranceConfiguredDesc:
'Güvenlik girişi yapılandırılmış, varsayılan giriş maruziyeti azaltılmış',
securityEntranceRiskDesc: 'Güvenlik girişi devre dışı. Varsayılan giriş girişini gizleyin',
allowIPsRestrictedDesc: 'Panel erişimi için izin verilen IP\'ler kısıtlanmış',
allowIPsRiskDesc: 'Erişim IP\'leri kısıtlanmamış. Genel ortamlar için izin verilen IP\'leri yapılandırın',
allowIPsRestrictedDesc: "Panel erişimi için izin verilen IP'ler kısıtlanmış",
allowIPsRiskDesc: "Erişim IP'leri kısıtlanmamış. Genel ortamlar için izin verilen IP'leri yapılandırın",
bindDomainRestrictedDesc: 'Erişim alan adı bağlanmış',
bindDomainRiskDesc: 'Erişim alan adı bağlanmamış. Erişimi sabit bir alan adıyla kısıtlayın',
accessRestrictionValue: 'İzin verilen IP\'ler: {0} / Bağlı alan adı: {1}',
accessRestrictionValue: "İzin verilen IP'ler: {0} / Bağlı alan adı: {1}",
accessRestrictionNormalDesc:
'Panel erişim kaynaklarını kısıtlamak için izin verilen IP\'ler veya bağlı alan adı yapılandırılmış',
"Panel erişim kaynaklarını kısıtlamak için izin verilen IP'ler veya bağlı alan adı yapılandırılmış",
accessRestrictionRiskDesc:
'İzin verilen IP\'ler ve bağlı alan adı yapılandırılmamış veya erişim kısıtlanmamış. En az birini yapılandırın',
"İzin verilen IP'ler ve bağlı alan adı yapılandırılmamış veya erişim kısıtlanmamış. En az birini yapılandırın",
panelHTTPSNormalDesc: 'Panel HTTPS etkin',
panelHTTPSRiskDesc: 'Panel HTTPS devre dışı. Giriş trafiği açığa çıkabilir',
passwordComplexityNormalDesc: 'Şifre karmaşıklık doğrulaması etkin',
passwordComplexityRiskDesc:
'Şifre karmaşıklık doğrulaması devre dışı, zayıf şifre riski yüksek',
passwordComplexityRiskDesc: 'Şifre karmaşıklık doğrulaması devre dışı, zayıf şifre riski yüksek',
sessionTimeoutNormalDesc: 'Oturum zaman aşımı güvenli aralıkta',
sessionTimeoutRiskDesc: 'Oturum zaman aşımı uzun. Boş oturum geçerlilik süresini kısıtlayın',
sshServiceNormalDesc: 'SSH servisi normal çalışıyor',
@@ -4399,7 +4405,7 @@ const message = {
'{0} risk öğesi algılandı. Web sitesi güvenliğini ve kullanılabilirliğini korumak için yakında ele alın.',
websiteHealthNormalDesc: 'Mevcut web sitesi koruma metrikleri normal',
handleSslRisk: 'Sertifika Risklerini Ele Al',
viewWaf: 'WAF\'ı Gör',
viewWaf: "WAF'ı Gör",
priority: 'Öncelik',
riskDescription: 'Risk Açıklaması',
deductScore: 'Düşülen Puan',
@@ -4514,14 +4520,14 @@ const message = {
checkBackupTasks: 'Yedek Görevlerini Kontrol Et',
generationRule: 'Oluşturma Kuralları',
scheduleDaily: 'Günlük',
scheduleDailyDesc: 'Her gün 09:00\'da son 24 saatin raporunu oluştur',
scheduleDailyDesc: "Her gün 09:00'da son 24 saatin raporunu oluştur",
scheduleWeekly: 'Haftalık',
scheduleWeeklyDesc: 'Her Pazartesi 09:00\'da son 7 günün raporunu oluştur',
scheduleWeeklyDesc: "Her Pazartesi 09:00'da son 7 günün raporunu oluştur",
scheduleMonthly: 'Aylık',
scheduleMonthlyDesc: 'Her ayın 1\'inde 09:00\'da bir önceki ayın raporunu oluştur',
scheduleCurrentDaily: 'Her gün 09:00\'da son 24 saatin raporu oluşturulur · Sonraki {0}',
scheduleCurrentWeekly: 'Her Pazartesi 09:00\'da son 7 günün raporu oluşturulur · Sonraki {0}',
scheduleCurrentMonthly: 'Her ayın 1\'inde 09:00\'da bir önceki ayın raporu oluşturulur · Sonraki {0}',
scheduleMonthlyDesc: "Her ayın 1'inde 09:00'da bir önceki ayın raporunu oluştur",
scheduleCurrentDaily: "Her gün 09:00'da son 24 saatin raporu oluşturulur · Sonraki {0}",
scheduleCurrentWeekly: "Her Pazartesi 09:00'da son 7 günün raporu oluşturulur · Sonraki {0}",
scheduleCurrentMonthly: "Her ayın 1'inde 09:00'da bir önceki ayın raporu oluşturulur · Sonraki {0}",
notificationMethod: 'Bildirim Yöntemleri',
channel: 'Kanal',
receiver: 'Alıcı',
@@ -4542,7 +4548,7 @@ const message = {
savePath: 'Kayıt Dizini',
savePathRequired: 'Rapor kayıt dizinini ayarlayın',
autoExport: 'Otomatik Dışa Aktarmayı Etkinleştir',
autoExportDesc: 'Etkinleştirildiğinde her gün 09:00\'da otomatik olarak operasyon raporu oluşturulur',
autoExportDesc: "Etkinleştirildiğinde her gün 09:00'da otomatik olarak operasyon raporu oluşturulur",
autoExportEnabled: 'Otomatik dışa aktarma etkin',
autoExportDisabled: 'Otomatik dışa aktarma devre dışı',
readOnlyPermissionTip:
+4
View File
@@ -3506,6 +3506,10 @@ const message = {
initHelper: '偵測到 {0} 未初始化請點選頂部狀態列的初始化按鈕進行設定',
bindHelper: '綁定 僅當狀態為綁定時防火牆規則才能生效是否確認',
unbindHelper: '解除綁定 解除綁定時已新增的所有防火牆規則將失效請謹慎操作是否確認',
whiteList: '白名單',
portWhiteList: '埠白名單',
portWhiteListAlter: '防火牆初始化或啟動時將自動放行白名單埠',
portWhiteListHelper: '支援指定協定 80/tcp443/udp未指定時預設使用 tcp',
defaultStrategy: '目前鏈 {0} 的預設策略為 {1}',
defaultStrategy2: '目前鏈 {0} 的預設策略為 {1}當前狀態為未綁定已新增的防火牆規則需要綁定後生效',
filterRule: 'Filter 規則',
+3
View File
@@ -3498,6 +3498,9 @@ const message = {
initHelper: '检测到 {0} 未初始化请点击顶部状态栏的初始化按钮进行配置',
bindHelper: '绑定 仅当状态为绑定时防火墙规则才能生效是否确认',
unbindHelper: '解绑 解除绑定时已添加的所有防火墙规则将失效请谨慎操作是否确认',
portWhiteList: '端口白名单',
portWhiteListAlter: '防火墙初始化或启动时将自动放行白名单端口',
portWhiteListHelper: '支持指定协议 80/tcp443/udp未指定时默认使用 TCP',
defaultStrategy: '当前链 {0} 的默认策略为 {1}',
defaultStrategy2: '当前链 {0} 的默认策略为 {1}当前状态为未绑定已添加的防火墙规则需要绑定后生效',
filterRule: 'Filter 规则',
@@ -46,6 +46,9 @@
<el-button v-permission type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onOpenWhiteList" plain>
{{ $t('firewall.portWhiteList') }}
</el-button>
<el-button v-permission @click="onDelete(null)" plain :disabled="selects.length === 0">
{{ $t('commons.button.delete') }}
</el-button>
@@ -158,6 +161,7 @@
<OperateDialog @search="search" ref="dialogRef" />
<ImportDialog @search="search" ref="dialogImportRef" />
<ProcessDetail ref="processDetailRef" />
<WhiteList ref="whiteListRef" />
</div>
</template>
@@ -165,6 +169,7 @@
import FireRouter from '@/views/host/firewall/index.vue';
import OperateDialog from '@/views/host/firewall/port/operate/index.vue';
import ImportDialog from '@/views/host/firewall/port/import/index.vue';
import WhiteList from '@/views/host/firewall/port/white-list/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import ProcessDetail from '@/views/host/process/process/detail/index.vue';
import { onMounted, reactive, ref } from 'vue';
@@ -194,6 +199,7 @@ const fireStatusRef = ref();
const opRef = ref();
const dialogImportRef = ref();
const processDetailRef = ref();
const whiteListRef = ref();
const listeningProcesses = ref<Process.ListeningProcess[]>([]);
@@ -398,6 +404,10 @@ const onImport = () => {
dialogImportRef.value.acceptParams();
};
const onOpenWhiteList = () => {
whiteListRef.value.acceptParams();
};
const onExport = () => {
ElMessageBox.confirm(
i18n.global.t('firewall.exportHelper', [selects.value.length]),
@@ -0,0 +1,180 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('firewall.portWhiteList')" @close="handleClose" size="small">
<template #content>
<el-alert type="info" :closable="false" :title="$t('firewall.portWhiteListAlter')" />
<el-button class="mt-5" type="primary" @click="openCreate">
{{ $t('commons.button.add') }}
</el-button>
<ComplexTable :data="data" v-loading="loading">
<el-table-column :label="$t('commons.table.port')" prop="port">
<template #default="{ row }">
<span v-if="!row.edit">{{ row.port }}</span>
<el-input v-else v-model.trim="row.port" placeholder="80/tcp" clearable />
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.operate')" width="160">
<template #default="{ row, $index }">
<el-button v-if="row.edit" link type="primary" @click="saveRow(row)">
{{ $t('commons.button.save') }}
</el-button>
<el-button v-if="!row.edit" link type="primary" @click="editRow(row)">
{{ $t('commons.button.edit') }}
</el-button>
<el-button v-if="!row.edit" link type="primary" @click="removeRow($index)">
{{ $t('commons.button.delete') }}
</el-button>
<el-button v-if="row.edit" link type="primary" @click="cancelEdit(row, $index)">
{{ $t('commons.button.cancel') }}
</el-button>
</template>
</el-table-column>
</ComplexTable>
<span class="input-help">{{ $t('firewall.portWhiteListHelper') }}</span>
</template>
<template #footer>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :loading="loading" type="primary" @click="onSubmit">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getAgentSettingInfo, updateAgentSetting } from '@/api/modules/setting';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { checkPort } from '@/utils/validate';
interface WhiteListItem {
port: string;
oldPort: string;
edit: boolean;
isNew: boolean;
}
const emit = defineEmits<{ (e: 'search'): void }>();
const drawerVisible = ref(false);
const loading = ref(false);
const data = ref<WhiteListItem[]>([]);
const defaultWhiteList = '80/tcp,443/tcp,443/udp';
const acceptParams = async (): Promise<void> => {
drawerVisible.value = true;
loading.value = true;
await getAgentSettingInfo()
.then((res) => {
data.value = parseWhiteList(res.data.firewallPortWhiteList ?? defaultWhiteList);
})
.finally(() => {
loading.value = false;
});
};
const parseWhiteList = (value: string): WhiteListItem[] => {
return value
.split(/[\s,;]+/)
.filter((item) => item !== '')
.map((item) => ({
port: item,
oldPort: item,
edit: false,
isNew: false,
}));
};
const openCreate = () => {
for (const item of data.value) {
if (item.isNew && item.port === '') {
return;
}
}
data.value.unshift({
port: '',
oldPort: '',
edit: true,
isNew: true,
});
};
const editRow = (row: WhiteListItem) => {
row.oldPort = row.port;
row.edit = true;
};
const saveRow = (row: WhiteListItem) => {
if (!validatePort(row.port)) {
return;
}
row.port = normalizePort(row.port);
row.oldPort = row.port;
row.edit = false;
row.isNew = false;
};
const cancelEdit = (row: WhiteListItem, index: number) => {
if (row.isNew) {
data.value.splice(index, 1);
return;
}
row.port = row.oldPort;
row.edit = false;
};
const removeRow = (index: number) => {
data.value.splice(index, 1);
};
const normalizePort = (value: string): string => {
const segments = value.split('/');
const port = segments[0];
const protocol = segments[1] ? segments[1].toLowerCase() : '';
return protocol ? `${port}/${protocol}` : port;
};
const validatePort = (value: string): boolean => {
if (value === '') {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
const segments = value.split('/');
const [port, protocol = 'tcp'] = segments;
if (checkPort(port) || !['tcp', 'udp'].includes(protocol.toLowerCase()) || segments.length > 2) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
return true;
};
const onSubmit = async () => {
const ports = data.value.map((item) => item.port).filter((item) => item !== '');
for (const port of ports) {
if (!validatePort(port)) {
return;
}
}
loading.value = true;
await updateAgentSetting({
key: 'FirewallPortWhiteList',
value: ports.map((item) => normalizePort(item)).join('\n'),
})
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.finally(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>