From 433f1a940fff0fdbaf0a14def61283f0685199dc Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 31 Aug 2026 16:18:13 +0800 Subject: [PATCH] fix: improve firewall abnormal state diagnostics (#13673) --- agent/app/api/v2/firewall.go | 38 ++++++++-- agent/app/dto/firewall.go | 11 +++ agent/app/service/firewall.go | 69 +++++++++++++++++-- agent/app/service/firewall_docker.go | 56 +++++++++++++++ agent/app/service/forward.go | 55 +++++++++++++++ agent/i18n/lang/en.yaml | 9 +++ agent/i18n/lang/es-ES.yaml | 9 +++ agent/i18n/lang/fa.yaml | 9 +++ agent/i18n/lang/ja.yaml | 9 +++ agent/i18n/lang/ko.yaml | 9 +++ agent/i18n/lang/lo.yaml | 9 +++ agent/i18n/lang/ms.yaml | 9 +++ agent/i18n/lang/pt-BR.yaml | 9 +++ agent/i18n/lang/ru.yaml | 9 +++ agent/i18n/lang/tr.yaml | 9 +++ agent/i18n/lang/zh-Hant.yaml | 9 +++ agent/i18n/lang/zh.yaml | 9 +++ agent/utils/firewall/command_diagnostic.go | 35 ++++++++++ agent/utils/firewall/docker_guard/manager.go | 10 ++- agent/utils/firewall/docker_guard/nftables.go | 3 + .../filter/providers/nftables/adapter.go | 2 +- .../firewall/filter/providers/ufw/adapter.go | 55 ++++++++++----- .../firewall/forwarding/providers/iptables.go | 4 +- .../firewall/forwarding/providers/nftables.go | 12 +++- .../utils/firewall/iptables_helper/command.go | 6 +- .../firewall/iptables_helper/persistence.go | 4 +- .../utils/firewall/nftables_helper/runtime.go | 15 +++- core/cmd/server/docs/docs.go | 57 ++++++++++++++- core/cmd/server/docs/swagger.json | 57 ++++++++++++++- frontend/src/api/interface/firewall.ts | 4 ++ frontend/src/api/modules/firewall.ts | 21 ++++-- .../host/firewall/docker/detail/index.vue | 16 ++++- .../src/views/host/firewall/docker/index.vue | 45 +++++++++--- .../src/views/host/firewall/docker/model.ts | 29 ++++++++ .../host/firewall/docker/status/index.vue | 22 +----- .../src/views/host/firewall/forward/index.vue | 17 ++++- .../src/views/host/firewall/rule/index.vue | 38 +++++++++- .../src/views/host/firewall/status/index.vue | 30 ++++++-- 38 files changed, 725 insertions(+), 94 deletions(-) create mode 100644 agent/utils/firewall/command_diagnostic.go diff --git a/agent/app/api/v2/firewall.go b/agent/app/api/v2/firewall.go index bbc2235d4..b0060c94f 100644 --- a/agent/app/api/v2/firewall.go +++ b/agent/app/api/v2/firewall.go @@ -121,24 +121,31 @@ func (b *BaseApi) OperateForwardingRules(c *gin.Context) { // @Tags Firewall // @Summary Enable forwarding -// @Success 200 +// @Accept json +// @Param request body dto.FirewallInitializationTask true "request" +// @Success 200 {object} dto.FilterChainOperationResponse // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/firewall/forward/enable [post] // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化并启用端口转发","formatEN":"initialize and enable port forwarding"} func (b *BaseApi) EnableForwarding(c *gin.Context) { - if err := forwardingService.Enable(); err != nil { + var request dto.FirewallInitializationTask + if err := helper.CheckBindAndValidate(&request, c); err != nil { + return + } + result, err := forwardingService.QueueInitialization(request) + if err != nil { helper.InternalServer(c, err) return } - helper.Success(c) + helper.SuccessWithData(c, result) } // @Tags Firewall // @Summary Apply/Unload/Init firewall filter chain // @Accept json // @Param request body dto.FilterChainOperation true "request" -// @Success 200 +// @Success 200 {object} dto.FilterChainOperationResponse // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/firewall/filter/operate [post] @@ -148,12 +155,20 @@ func (b *BaseApi) OperateFilterChain(c *gin.Context) { if err := helper.CheckBindAndValidate(&request, c); err != nil { return } + if request.Operate == "init-base" { + result, err := firewallService.QueueFilterChainInitialization(request) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, result) + return + } if err := firewallService.OperateFilterChain(request); err != nil { helper.InternalServer(c, err) return } - - helper.Success(c) + helper.SuccessWithData(c, dto.FilterChainOperationResponse{}) } // @Tags Firewall @@ -533,7 +548,7 @@ func (b *BaseApi) SyncDockerPortGuard(c *gin.Context) { // @Summary Operate Docker port guard // @Accept json // @Param request body dto.DockerPortGuardOperation true "request" -// @Success 200 +// @Success 200 {object} dto.FilterChainOperationResponse // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/firewall/docker/operate [post] @@ -543,6 +558,15 @@ func (b *BaseApi) OperateDockerPortGuard(c *gin.Context) { if err := helper.CheckBindAndValidate(&request, c); err != nil { return } + if request.Operation == "initialize" { + result, err := dockerPortGuardService.QueueInitialization(request) + if err != nil { + handleDockerPortGuardError(c, err) + return + } + helper.SuccessWithData(c, result) + return + } if err := dockerPortGuardService.Operate(c.Request.Context(), request); err != nil { handleDockerPortGuardError(c, err) return diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go index 7006525e4..38b8ee10d 100644 --- a/agent/app/dto/firewall.go +++ b/agent/app/dto/firewall.go @@ -71,6 +71,16 @@ type FirewallBackendOperation struct { type FilterChainOperation struct { Name string `json:"name" validate:"required,eq=1PANEL_BASIC"` Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"` + TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"` +} + +type FilterChainOperationResponse struct { + TaskID string `json:"taskID"` + Queued bool `json:"queued"` +} + +type FirewallInitializationTask struct { + TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"` } type FirewallSystemPort struct { @@ -206,6 +216,7 @@ type DockerPortGuardPolicyBatchDelete struct { type DockerPortGuardOperation struct { Operation string `json:"operation" validate:"required,oneof=initialize bind unbind"` + TaskID string `json:"taskID,omitempty" validate:"omitempty,max=64"` } type FirewallRuleCheckItem struct { diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go index ca30de30a..c62e0e413 100644 --- a/agent/app/service/firewall.go +++ b/agent/app/service/firewall.go @@ -13,8 +13,10 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" filterruntime "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/runtime" @@ -50,6 +52,7 @@ type IFirewallService interface { LoadBaseInfo(chainGroup string) (dto.FirewallSubsystemStatus, error) OperateFirewall(request dto.FirewallLifecycleOperation) error OperateFilterChain(request dto.FilterChainOperation) error + QueueFilterChainInitialization(request dto.FilterChainOperation) (dto.FilterChainOperationResponse, error) Reset(context.Context, dto.FirewallRuleReset) (dto.FirewallRuleResetResponse, error) Inventory(context.Context, dto.FirewallRuleInventory) (dto.FirewallRuleInventoryResponse, error) LoadFirewallNativeDetail(context.Context, dto.FirewallNativeDetail) (string, error) @@ -156,6 +159,61 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e if err != nil { return err } + if err := s.operateFilterChainBase(provider, request); err != nil { + return err + } + if request.Operate != string(firewall.BaseOperationInit) && request.Operate != string(firewall.BaseOperationBind) { + return nil + } + ctx := context.Background() + if err := s.restoreStoredFirewallRules(ctx, filter.Provider(provider)); err != nil { + return err + } + return s.syncConfiguredFirewallPorts(ctx) +} + +func (s *FirewallService) QueueFilterChainInitialization( + request dto.FilterChainOperation, +) (dto.FilterChainOperationResponse, error) { + if request.Operate != string(firewall.BaseOperationInit) { + return dto.FilterChainOperationResponse{}, fmt.Errorf("only filter chain initialization can be queued") + } + provider, err := selectedSystemFirewallProvider() + if err != nil { + return dto.FilterChainOperationResponse{}, err + } + if !supportsManagedFilterChains(provider) { + return dto.FilterChainOperationResponse{}, fmt.Errorf("filter chain operations are not supported for %s", provider) + } + if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil { + return dto.FilterChainOperationResponse{}, err + } + + resourceName := fmt.Sprintf("%s filter", provider) + taskItem, err := task.NewTaskWithOps(resourceName, task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0) + if err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("create firewall initialization task: %w", err) + } + taskItem.AddSubTask(i18n.GetWithName("FirewallInitializeChainsStep", provider), func(t *task.Task) error { + t.Logf("backend=%s", provider) + return s.operateFilterChainBase(provider, request) + }, nil) + taskItem.AddSubTask(i18n.GetWithName("FirewallRestoreRulesStep", provider), func(t *task.Task) error { + return s.restoreStoredFirewallRules(t.TaskCtx, filter.Provider(provider)) + }, nil) + taskItem.AddSubTask(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func(t *task.Task) error { + return s.syncConfiguredFirewallPorts(t.TaskCtx) + }, nil) + if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("save firewall initialization task: %w", err) + } + go func() { + _ = taskItem.Execute() + }() + return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil +} + +func (s *FirewallService) operateFilterChainBase(provider string, request dto.FilterChainOperation) error { if !supportsManagedFilterChains(provider) { return fmt.Errorf("filter chain operations are not supported for %s", provider) } @@ -166,13 +224,10 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e } else if err := s.iptablesHelper.Operate(firewall.BaseOperation(request.Operate)); err != nil { return err } - if request.Operate != string(firewall.BaseOperationInit) && request.Operate != string(firewall.BaseOperationBind) { - return nil - } - ctx := context.Background() - if err := s.restoreStoredFirewallRules(ctx, filter.Provider(provider)); err != nil { - return err - } + return nil +} + +func (s *FirewallService) syncConfiguredFirewallPorts(ctx context.Context) error { configured, err := loadConfiguredFirewallPortWhiteList() if err != nil { return err diff --git a/agent/app/service/firewall_docker.go b/agent/app/service/firewall_docker.go index 6878bac12..0bdf1dc90 100644 --- a/agent/app/service/firewall_docker.go +++ b/agent/app/service/firewall_docker.go @@ -13,6 +13,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" agenti18n "github.com/1Panel-dev/1Panel/agent/i18n" @@ -56,6 +57,7 @@ type IDockerPortGuardService interface { LoadOverview(context.Context) (dto.DockerPortGuardList, error) LoadPublishedPorts(context.Context) ([]dto.DockerPortGuardContainer, error) Operate(context.Context, dto.DockerPortGuardOperation) error + QueueInitialization(dto.DockerPortGuardOperation) (dto.FilterChainOperationResponse, error) DeletePolicies(context.Context, dto.DockerPortGuardPolicyBatchDelete) error UpsertPolicies(context.Context, dto.DockerPortGuardPolicyBatch) error Reconcile(context.Context) error @@ -219,6 +221,60 @@ func (s *DockerPortGuardService) Operate(ctx context.Context, request dto.Docker } } +func (s *DockerPortGuardService) QueueInitialization( + request dto.DockerPortGuardOperation, +) (dto.FilterChainOperationResponse, error) { + if request.Operation != "initialize" { + return dto.FilterChainOperationResponse{}, fmt.Errorf("only Docker port guard initialization can be queued") + } + if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil { + return dto.FilterChainOperationResponse{}, err + } + taskItem, err := task.NewTaskWithOps("Docker port guard", task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0) + if err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("create Docker port guard initialization task: %w", err) + } + var runtime dockerGuardRuntime + var backend string + var policies []docker_guard.Policy + taskItem.AddSubTask(agenti18n.GetMsgByKey("FirewallInspectDockerGuardStep"), func(t *task.Task) error { + var err error + runtime, backend, err = s.runtimeForDocker(t.TaskCtx) + if err != nil { + return err + } + policies, err = s.runtimePolicies(t.TaskCtx) + if err != nil { + return err + } + t.Logf("backend=%s", backend) + return nil + }, nil) + taskItem.AddSubTask(agenti18n.GetWithName("FirewallInitializeDockerGuardStep", "Docker"), func(t *task.Task) error { + dockerPortGuardServiceMu.Lock() + defer dockerPortGuardServiceMu.Unlock() + t.Logf("backend=%s", backend) + err := runtime.Initialize(policies) + recordDockerPortGuardReconcileError(err) + return err + }, nil) + taskItem.AddSubTask(agenti18n.GetMsgByKey("FirewallPersistDockerGuardStep"), func(t *task.Task) error { + if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, backend); err != nil { + return err + } + if err := settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable); err != nil { + return err + } + recordDockerPortGuardReconcileError(nil) + return nil + }, nil) + if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("save Docker port guard initialization task: %w", err) + } + go func() { _ = taskItem.Execute() }() + return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil +} + func (s *DockerPortGuardService) DeletePolicies(ctx context.Context, request dto.DockerPortGuardPolicyBatchDelete) error { dockerPortGuardServiceMu.Lock() defer dockerPortGuardServiceMu.Unlock() diff --git a/agent/app/service/forward.go b/agent/app/service/forward.go index edb008a99..b1a31025b 100644 --- a/agent/app/service/forward.go +++ b/agent/app/service/forward.go @@ -11,9 +11,11 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding" forwardingproviders "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding/providers" @@ -26,6 +28,7 @@ type IForwardingService interface { SearchRules(request dto.ForwardRuleSearch) (int64, interface{}, error) OperateRules(request dto.ForwardRuleOperate) error Enable() error + QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error) Restore(context.Context) error } @@ -249,6 +252,58 @@ func (s *ForwardingService) Enable() error { return err } +func (s *ForwardingService) QueueInitialization( + request dto.FirewallInitializationTask, +) (dto.FilterChainOperationResponse, error) { + if err := task.CheckScopeTaskIsExecuting(task.TaskScopeFirewall, 0); err != nil { + return dto.FilterChainOperationResponse{}, err + } + taskItem, err := task.NewTaskWithOps("port forwarding", task.TaskExec, task.TaskScopeFirewall, request.TaskID, 0) + if err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("create forwarding initialization task: %w", err) + } + var manager *forwarding.Manager + var backend string + taskItem.AddSubTask(i18n.GetMsgByKey("FirewallEnableForwardingStep"), func(t *task.Task) error { + forwardingMutationMu.Lock() + defer forwardingMutationMu.Unlock() + var err error + manager, err = s.manager() + if err != nil { + recordForwardingSyncError(err) + return err + } + backend = manager.Name() + t.Logf("backend=%s", backend) + if err := s.persistForwardingEnabled(); err != nil { + recordForwardingSyncError(err) + return err + } + if err := s.activateManager(manager); err != nil { + recordForwardingSyncError(err) + return err + } + return nil + }, nil) + taskItem.AddSubTask(i18n.GetMsgByKey("FirewallRestoreForwardingRulesStep"), func(t *task.Task) error { + forwardingMutationMu.Lock() + defer forwardingMutationMu.Unlock() + rules, err := s.rules.List(t.TaskCtx) + if err != nil { + recordForwardingSyncError(err) + return err + } + err = manager.Reconcile(forwardingRulesFromModels(rules)) + recordForwardingSyncError(err) + return err + }, nil) + if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil { + return dto.FilterChainOperationResponse{}, fmt.Errorf("save forwarding initialization task: %w", err) + } + go func() { _ = taskItem.Execute() }() + return dto.FilterChainOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil +} + func (s *ForwardingService) Restore(ctx context.Context) error { forwardingMutationMu.Lock() defer forwardingMutationMu.Unlock() diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 2a4cf28dd..24169c523 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: "The {{ .name }} application is not compatible with the ErrDockerFailed: 'Docker is abnormal; check service status' ErrDockerIptablesChainUnavailable: 'The Docker DOCKER-USER chain was not found. Restart Docker and try again' ErrDockerNftablesChainUnavailable: 'The Docker nftables IPv4 firewall chain was not found. Verify that the current Docker version supports the nftables firewall backend, restart Docker, and try again' +ErrUFWRuleAdopt: 'Failed to adopt the UFW rule: {{ .detail }}. If the installed UFW version is earlier than 0.35, upgrade UFW and try again' ErrDockerComposeCmdNotFound: 'Docker Compose not found on host' UseExistImage: 'Image exists; using existing image' UpgradePrepare: 'Prepare application upgrade' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} firewall rules failed to synchron FirewallResetSourceStep: 'Reset and disable source firewall {{ .name }}' FirewallResetSourceResult: 'Source firewall reset completed; {{ .removed }} database rules were deleted' FirewallVerifyTargetStep: 'Verify target firewall rules' +FirewallInitializeChainsStep: 'Initialize and bind {{ .name }} base chains' +FirewallRestoreRulesStep: 'Restore database rules to {{ .name }}' +FirewallSyncWhitelistStep: 'Synchronize firewall port whitelist' +FirewallEnableForwardingStep: 'Enable and bind port forwarding chains' +FirewallRestoreForwardingRulesStep: 'Restore database port forwarding rules' +FirewallInspectDockerGuardStep: 'Inspect Docker firewall backend and policies' +FirewallInitializeDockerGuardStep: 'Initialize and bind {{ .name }} port guard chains' +FirewallPersistDockerGuardStep: 'Persist Docker port guard status' FirewallTargetRuleIneffective: 'The target rule is no longer effective' FirewallVerifyRuleFailed: 'Failed to verify synchronized rule {{ .name }}: {{ .detail }}' TaskSync: 'Sync' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 7864f4243..7dbb9f35a 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: 'La aplicación {{ .name }} no es compatible con la ver ErrDockerFailed: 'El estado de Docker es anómalo, revise el servicio' ErrDockerIptablesChainUnavailable: 'No se encontró la cadena DOCKER-USER de Docker. Reinicie Docker y vuelva a intentarlo' ErrDockerNftablesChainUnavailable: 'No se encontró la cadena de firewall IPv4 de nftables de Docker. Compruebe que la versión actual de Docker admita el backend de firewall nftables, reinicie Docker y vuelva a intentarlo' +ErrUFWRuleAdopt: 'No se pudo administrar la regla UFW: {{ .detail }}. Si la versión actual de UFW es anterior a 0.35, actualice UFW y vuelva a intentarlo' ErrDockerComposeCmdNotFound: 'El comando Docker Compose no existe instálelo primero en el host' UseExistImage: 'La imagen ya existe, usando imagen existente' UpgradePrepare: 'Preparar actualización de la aplicación' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: 'No se pudieron sincronizar {{ .failed }} reglas FirewallResetSourceStep: 'Restablecer y desactivar el firewall de origen {{ .name }}' FirewallResetSourceResult: 'Firewall de origen restablecido; se eliminaron {{ .removed }} reglas de la base de datos' FirewallVerifyTargetStep: 'Verificar las reglas del firewall de destino' +FirewallInitializeChainsStep: 'Inicializar y vincular las cadenas base de {{ .name }}' +FirewallRestoreRulesStep: 'Restaurar las reglas de la base de datos en {{ .name }}' +FirewallSyncWhitelistStep: 'Sincronizar la lista de puertos permitidos del firewall' +FirewallEnableForwardingStep: 'Habilitar y vincular las cadenas de reenvío de puertos' +FirewallRestoreForwardingRulesStep: 'Restaurar las reglas de reenvío de puertos de la base de datos' +FirewallInspectDockerGuardStep: 'Inspeccionar el backend del firewall de Docker y las políticas' +FirewallInitializeDockerGuardStep: 'Inicializar y vincular las cadenas de protección de puertos de {{ .name }}' +FirewallPersistDockerGuardStep: 'Guardar el estado de protección de puertos de Docker' FirewallTargetRuleIneffective: 'La regla de destino ya no es efectiva' FirewallVerifyRuleFailed: 'No se pudo verificar la regla sincronizada {{ .name }}: {{ .detail }}' TaskSync: 'Sincronizar' diff --git a/agent/i18n/lang/fa.yaml b/agent/i18n/lang/fa.yaml index b52a0cd15..a8b43615b 100644 --- a/agent/i18n/lang/fa.yaml +++ b/agent/i18n/lang/fa.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: "برنامه {{ .name }} با نسخه فعلی 1Panel ErrDockerFailed: 'Docker غیرعادی است؛ وضعیت سرویس را بررسی کنید' ErrDockerIptablesChainUnavailable: 'زنجیره DOCKER-USER داکر یافت نشد. داکر را راه‌اندازی مجدد کنید و دوباره تلاش کنید' ErrDockerNftablesChainUnavailable: 'زنجیره فایروال IPv4 مربوط به nftables داکر یافت نشد. بررسی کنید نسخه فعلی داکر از بک‌اند فایروال nftables پشتیبانی کند، سپس داکر را راه‌اندازی مجدد کرده و دوباره تلاش کنید' +ErrUFWRuleAdopt: 'مدیریت قانون UFW ناموفق بود: {{ .detail }}. اگر نسخه فعلی UFW قدیمی‌تر از 0.35 است، ابتدا UFW را ارتقا دهید و دوباره تلاش کنید' ErrDockerComposeCmdNotFound: 'Docker Compose روی میزبان یافت نشد' UseExistImage: 'تصویر وجود دارد؛ استفاده از تصویر موجود' UpgradePrepare: 'آماده‌سازی ارتقاء برنامه' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: 'همگام‌سازی {{ .failed }} قانون FirewallResetSourceStep: 'بازنشانی و غیرفعال‌کردن فایروال مبدأ {{ .name }}' FirewallResetSourceResult: 'فایروال مبدأ بازنشانی شد و {{ .removed }} قانون پایگاه داده حذف شد' FirewallVerifyTargetStep: 'بررسی قوانین فایروال مقصد' +FirewallInitializeChainsStep: 'راه‌اندازی و اتصال زنجیره‌های پایه {{ .name }}' +FirewallRestoreRulesStep: 'بازیابی قوانین پایگاه داده در {{ .name }}' +FirewallSyncWhitelistStep: 'همگام‌سازی فهرست مجاز پورت‌های فایروال' +FirewallEnableForwardingStep: 'فعال‌سازی و اتصال زنجیره‌های هدایت پورت' +FirewallRestoreForwardingRulesStep: 'بازیابی قوانین هدایت پورت پایگاه داده' +FirewallInspectDockerGuardStep: 'بررسی پشتیبان فایروال Docker و سیاست‌ها' +FirewallInitializeDockerGuardStep: 'راه‌اندازی و اتصال زنجیره‌های محافظت پورت {{ .name }}' +FirewallPersistDockerGuardStep: 'ذخیره وضعیت محافظت پورت Docker' FirewallTargetRuleIneffective: 'قانون مقصد دیگر مؤثر نیست' FirewallVerifyRuleFailed: 'بررسی قانون همگام‌شده {{ .name }} ناموفق بود: {{ .detail }}' TaskSync: 'همگام‌سازی' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index cd322dd09..424984718 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: '{{ .name }} アプリケーションは現在の 1Pane ErrDockerFailed: 'Docker の状態が異常です。サービス状態を確認してください' ErrDockerIptablesChainUnavailable: 'Docker の DOCKER-USER チェーンが見つかりません。Docker を再起動してから再試行してください' ErrDockerNftablesChainUnavailable: 'Docker の nftables IPv4 ファイアウォールチェーンが見つかりません。現在の Docker バージョンが nftables ファイアウォールバックエンドをサポートしていることを確認し、Docker を再起動してから再試行してください' +ErrUFWRuleAdopt: 'UFW ルールの管理に失敗しました:{{ .detail }}。現在の UFW バージョンが 0.35 未満の場合は、UFW をアップグレードしてから再試行してください' ErrDockerComposeCmdNotFound: 'Docker Compose コマンドは存在しません。ホストマシンにこのコマンドを先にインストールしてください' UseExistImage: 'イメージは既に存在します。既存のイメージを使用します' UpgradePrepare: 'アプリケーションのアップグレードを準備' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} 件のルールを同期できな FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化' FirewallResetSourceResult: '移行元ファイアウォールをリセットし、データベースルール {{ .removed }} 件を削除しました' FirewallVerifyTargetStep: '移行先ファイアウォールルールを検証' +FirewallInitializeChainsStep: '{{ .name }} のベースチェーンを初期化してバインド' +FirewallRestoreRulesStep: 'データベースルールを {{ .name }} に復元' +FirewallSyncWhitelistStep: 'ファイアウォールポート許可リストを同期' +FirewallEnableForwardingStep: 'ポート転送チェーンを有効化してバインド' +FirewallRestoreForwardingRulesStep: 'データベースのポート転送ルールを復元' +FirewallInspectDockerGuardStep: 'Docker ファイアウォールバックエンドと保護ポリシーを確認' +FirewallInitializeDockerGuardStep: '{{ .name }} のポート保護チェーンを初期化してバインド' +FirewallPersistDockerGuardStep: 'Docker ポート保護状態を保存' FirewallTargetRuleIneffective: '移行先ルールは有効ではありません' FirewallVerifyRuleFailed: '同期ルール {{ .name }} の検証に失敗しました:{{ .detail }}' TaskSync: '同期' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index e18337dcf..def88719d 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: '{{ .name }} 응용 프로그램은 현재 1Panel 버 ErrDockerFailed: 'Docker의 상태가 비정상입니다. 서비스 상태를 확인하세요' ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER 체인을 찾을 수 없습니다. Docker를 다시 시작한 후 재시도하세요' ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 방화벽 체인을 찾을 수 없습니다. 현재 Docker 버전이 nftables 방화벽 백엔드를 지원하는지 확인하고 Docker를 다시 시작한 후 재시도하세요' +ErrUFWRuleAdopt: 'UFW 규칙 관리에 실패했습니다: {{ .detail }}. 현재 UFW 버전이 0.35 미만이면 UFW를 업그레이드한 후 다시 시도하세요' ErrDockerComposeCmdNotFound: 'Docker Compose 명령이 없습니다. 호스트 머신에 먼저 이 명령을 설치하세요' UseExistImage: '이미지가 이미 존재하여 기존 이미지를 사용합니다' UpgradePrepare: '애플리케이션 업그레이드 준비' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '규칙 {{ .failed }}개를 동기화하지 못 FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화' FirewallResetSourceResult: '원본 방화벽을 초기화하고 데이터베이스 규칙 {{ .removed }}개를 삭제했습니다' FirewallVerifyTargetStep: '대상 방화벽 규칙 확인' +FirewallInitializeChainsStep: '{{ .name }} 기본 체인 초기화 및 바인딩' +FirewallRestoreRulesStep: '데이터베이스 규칙을 {{ .name }}에 복원' +FirewallSyncWhitelistStep: '방화벽 포트 허용 목록 동기화' +FirewallEnableForwardingStep: '포트 전달 체인 활성화 및 바인딩' +FirewallRestoreForwardingRulesStep: '데이터베이스 포트 전달 규칙 복원' +FirewallInspectDockerGuardStep: 'Docker 방화벽 백엔드 및 보호 정책 확인' +FirewallInitializeDockerGuardStep: '{{ .name }} 포트 보호 체인 초기화 및 바인딩' +FirewallPersistDockerGuardStep: 'Docker 포트 보호 상태 저장' FirewallTargetRuleIneffective: '대상 규칙이 더 이상 적용되지 않습니다' FirewallVerifyRuleFailed: '동기화된 규칙 {{ .name }} 확인 실패: {{ .detail }}' TaskSync: '동기화' diff --git a/agent/i18n/lang/lo.yaml b/agent/i18n/lang/lo.yaml index a47922759..41439c92c 100644 --- a/agent/i18n/lang/lo.yaml +++ b/agent/i18n/lang/lo.yaml @@ -150,6 +150,7 @@ ErrAppVersionDeprecated: "ແອັບພລິເຄຊັນ {{ .name }} ບ ErrDockerFailed: 'Docker ຜິດປົກກະຕິ; ກະລຸນາກວດສອບສະຖານະການບໍລິການ' ErrDockerIptablesChainUnavailable: 'ບໍ່ພົບ chain DOCKER-USER ຂອງ Docker. ກະລຸນາເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ' ErrDockerNftablesChainUnavailable: 'ບໍ່ພົບ chain ໄຟວໍ nftables IPv4 ຂອງ Docker. ກະລຸນາກວດສອບວ່າ Docker ລຸ້ນປັດຈຸບັນຮອງຮັບ backend ໄຟວໍ nftables, ເລີ່ມ Docker ໃໝ່ແລ້ວລອງອີກຄັ້ງ' +ErrUFWRuleAdopt: 'ຈັດການກົດ UFW ບໍ່ສຳເລັດ: {{ .detail }}. ຖ້າ UFW ລຸ້ນປັດຈຸບັນຕ່ຳກວ່າ 0.35, ກະລຸນາອັບເກຣດ UFW ແລ້ວລອງໃໝ່' ErrDockerComposeCmdNotFound: 'ບໍ່ພົບຄຳສັ່ງ Docker Compose ໃນເຄື່ອງ' UseExistImage: 'ມີຮູບພາບຢູ່ແລ້ວ; ກຳລັງໃຊ້ຮູບພາບທີ່ມີຢູ່' UpgradePrepare: 'ກະກຽມອັບເກຣດແອັບ' @@ -502,6 +503,14 @@ FirewallSyncFailedResetSkipped: 'ຊິງຄ໌ກົດ {{ .failed }} ລາ FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}' FirewallResetSourceResult: 'ຣີເຊັດໄຟວອລຕົ້ນທາງແລ້ວ ແລະ ລຶບກົດຖານຂໍ້ມູນ {{ .removed }} ລາຍການ' FirewallVerifyTargetStep: 'ກວດສອບກົດໄຟວອລປາຍທາງ' +FirewallInitializeChainsStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ພື້ນຖານ {{ .name }}' +FirewallRestoreRulesStep: 'ກູ້ຄືນກົດຖານຂໍ້ມູນໄປຫາ {{ .name }}' +FirewallSyncWhitelistStep: 'ຊິງຄ໌ລາຍການພອດໄຟວໍທີ່ອະນຸຍາດ' +FirewallEnableForwardingStep: 'ເປີດໃຊ້ ແລະ ຜູກ chain ສົ່ງຕໍ່ພອດ' +FirewallRestoreForwardingRulesStep: 'ກູ້ຄືນກົດສົ່ງຕໍ່ພອດຈາກຖານຂໍ້ມູນ' +FirewallInspectDockerGuardStep: 'ກວດສອບ backend firewall Docker ແລະ ນະໂຍບາຍ' +FirewallInitializeDockerGuardStep: 'ເລີ່ມຕົ້ນ ແລະ ຜູກ chain ປ້ອງກັນພອດ {{ .name }}' +FirewallPersistDockerGuardStep: 'ບັນທຶກສະຖານະປ້ອງກັນພອດ Docker' FirewallTargetRuleIneffective: 'ກົດປາຍທາງບໍ່ມີຜົນແລ້ວ' FirewallVerifyRuleFailed: 'ກວດສອບກົດທີ່ຊິງຄ໌ {{ .name }} ບໍ່ສຳເລັດ: {{ .detail }}' TaskSync: 'ຊິງຄ໌' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index cab98f023..261fed1cd 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: 'Aplikasi {{ .name }} tidak sesuai dengan versi 1Panel ErrDockerFailed: 'Keadaan Docker tidak normal, sila periksa status perkhidmatan' ErrDockerIptablesChainUnavailable: 'Rantaian DOCKER-USER Docker tidak ditemui. Mulakan semula Docker dan cuba lagi' ErrDockerNftablesChainUnavailable: 'Rantaian firewall IPv4 nftables Docker tidak ditemui. Pastikan versi Docker semasa menyokong bahagian belakang firewall nftables, mulakan semula Docker dan cuba lagi' +ErrUFWRuleAdopt: 'Gagal mengurus peraturan UFW: {{ .detail }}. Jika versi UFW semasa lebih lama daripada 0.35, tingkatkan UFW dan cuba lagi' ErrDockerComposeCmdNotFound: 'Perintah Docker Compose tidak wujud, sila pasang perintah ini di mesin tuan terlebih dahulu' UseExistImage: 'Imej sudah wujud, menggunakan imej sedia ada' UpgradePrepare: 'Sediakan naik taraf aplikasi' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} peraturan gagal disegerakkan; fir FirewallResetSourceStep: 'Tetapkan semula dan nyahdayakan firewall sumber {{ .name }}' FirewallResetSourceResult: 'Firewall sumber ditetapkan semula; {{ .removed }} peraturan pangkalan data dipadamkan' FirewallVerifyTargetStep: 'Sahkan peraturan firewall sasaran' +FirewallInitializeChainsStep: 'Mulakan dan ikat rantai asas {{ .name }}' +FirewallRestoreRulesStep: 'Pulihkan peraturan pangkalan data ke {{ .name }}' +FirewallSyncWhitelistStep: 'Segerakkan senarai putih port firewall' +FirewallEnableForwardingStep: 'Aktifkan dan ikat rantai pemajuan port' +FirewallRestoreForwardingRulesStep: 'Pulihkan peraturan pemajuan port pangkalan data' +FirewallInspectDockerGuardStep: 'Periksa backend firewall Docker dan polisi' +FirewallInitializeDockerGuardStep: 'Mulakan dan ikat rantai perlindungan port {{ .name }}' +FirewallPersistDockerGuardStep: 'Simpan status perlindungan port Docker' FirewallTargetRuleIneffective: 'Peraturan sasaran tidak lagi berkesan' FirewallVerifyRuleFailed: 'Gagal mengesahkan peraturan disegerakkan {{ .name }}: {{ .detail }}' TaskSync: 'Segerakkan' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 06fd85d4b..e653b0d67 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: 'O aplicativo {{ .name }} não é compatível com a ver ErrDockerFailed: 'O estado do Docker está anormal, verifique o status do serviço' ErrDockerIptablesChainUnavailable: 'A cadeia DOCKER-USER do Docker não foi encontrada. Reinicie o Docker e tente novamente' ErrDockerNftablesChainUnavailable: 'A cadeia de firewall IPv4 do nftables do Docker não foi encontrada. Confirme se a versão atual do Docker oferece suporte ao backend de firewall nftables, reinicie o Docker e tente novamente' +ErrUFWRuleAdopt: 'Falha ao gerenciar a regra UFW: {{ .detail }}. Se a versão atual do UFW for anterior à 0.35, atualize o UFW e tente novamente' ErrDockerComposeCmdNotFound: 'O comando Docker Compose não existe, instale este comando na máquina host primeiro' UseExistImage: 'A imagem já existe, usando imagem existente' UpgradePrepare: 'Preparar atualização do aplicativo' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} regras falharam na sincronizaçã FirewallResetSourceStep: 'Redefinir e desativar o firewall de origem {{ .name }}' FirewallResetSourceResult: 'Firewall de origem redefinido; {{ .removed }} regras do banco de dados foram excluídas' FirewallVerifyTargetStep: 'Verificar regras do firewall de destino' +FirewallInitializeChainsStep: 'Inicializar e vincular as cadeias base do {{ .name }}' +FirewallRestoreRulesStep: 'Restaurar regras do banco de dados para {{ .name }}' +FirewallSyncWhitelistStep: 'Sincronizar a lista de portas permitidas do firewall' +FirewallEnableForwardingStep: 'Habilitar e vincular as cadeias de encaminhamento de portas' +FirewallRestoreForwardingRulesStep: 'Restaurar regras de encaminhamento de portas do banco de dados' +FirewallInspectDockerGuardStep: 'Inspecionar o backend do firewall Docker e as políticas' +FirewallInitializeDockerGuardStep: 'Inicializar e vincular as cadeias de proteção de portas do {{ .name }}' +FirewallPersistDockerGuardStep: 'Salvar o status da proteção de portas do Docker' FirewallTargetRuleIneffective: 'A regra de destino não está mais efetiva' FirewallVerifyRuleFailed: 'Falha ao verificar a regra sincronizada {{ .name }}: {{ .detail }}' TaskSync: 'Sincronizar' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 85d0a97e9..54bcc7a7c 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: 'Приложение {{ .name }} несовмести ErrDockerFailed: 'Состояние Docker аномально, проверьте состояние сервиса' ErrDockerIptablesChainUnavailable: 'Цепочка Docker DOCKER-USER не найдена. Перезапустите Docker и повторите попытку' ErrDockerNftablesChainUnavailable: 'Цепочка IPv4 брандмауэра nftables Docker не найдена. Убедитесь, что текущая версия Docker поддерживает бэкенд брандмауэра nftables, перезапустите Docker и повторите попытку' +ErrUFWRuleAdopt: 'Не удалось принять правило UFW под управление: {{ .detail }}. Если текущая версия UFW ниже 0.35, обновите UFW и повторите попытку' ErrDockerComposeCmdNotFound: 'Команда Docker Compose отсутствует, пожалуйста, установите эту команду на хост-машине сначала' UseExistImage: 'Образ уже существует, используется существующий образ' UpgradePrepare: 'Подготовка обновления приложения' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: 'Не удалось синхронизиров FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}' FirewallResetSourceResult: 'Исходный межсетевой экран сброшен; удалено правил из базы данных: {{ .removed }}' FirewallVerifyTargetStep: 'Проверить правила целевого межсетевого экрана' +FirewallInitializeChainsStep: 'Инициализировать и привязать базовые цепочки {{ .name }}' +FirewallRestoreRulesStep: 'Восстановить правила базы данных в {{ .name }}' +FirewallSyncWhitelistStep: 'Синхронизировать белый список портов межсетевого экрана' +FirewallEnableForwardingStep: 'Включить и привязать цепочки перенаправления портов' +FirewallRestoreForwardingRulesStep: 'Восстановить правила перенаправления портов из базы данных' +FirewallInspectDockerGuardStep: 'Проверить бэкенд межсетевого экрана Docker и политики' +FirewallInitializeDockerGuardStep: 'Инициализировать и привязать цепочки защиты портов {{ .name }}' +FirewallPersistDockerGuardStep: 'Сохранить состояние защиты портов Docker' FirewallTargetRuleIneffective: 'Целевое правило больше не действует' FirewallVerifyRuleFailed: 'Не удалось проверить синхронизированное правило {{ .name }}: {{ .detail }}' TaskSync: 'Синхронизировать' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 580639f92..fcbac0798 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: '{{ .name }} uygulaması mevcut 1Panel sürümü ile uy ErrDockerFailed: 'Docker durumu anormal, lütfen servis durumunu kontrol edin' ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER zinciri bulunamadı. Docker’ı yeniden başlatıp tekrar deneyin' ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 güvenlik duvarı zinciri bulunamadı. Geçerli Docker sürümünün nftables güvenlik duvarı arka ucunu desteklediğini doğrulayın, Docker’ı yeniden başlatıp tekrar deneyin' +ErrUFWRuleAdopt: 'UFW kuralı yönetilemedi: {{ .detail }}. Geçerli UFW sürümü 0.35’ten eskiyse UFW’yi yükseltip tekrar deneyin' ErrDockerComposeCmdNotFound: 'Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin' UseExistImage: 'Görüntü zaten mevcut, mevcut görüntü kullanılıyor' UpgradePrepare: 'Uygulama yükseltmesini hazırla' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} kural eşitlenemedi; kaynak güve FirewallResetSourceStep: 'Kaynak güvenlik duvarı {{ .name }} sıfırla ve devre dışı bırak' FirewallResetSourceResult: 'Kaynak güvenlik duvarı sıfırlandı; {{ .removed }} veritabanı kuralı silindi' FirewallVerifyTargetStep: 'Hedef güvenlik duvarı kurallarını doğrula' +FirewallInitializeChainsStep: '{{ .name }} temel zincirlerini başlat ve bağla' +FirewallRestoreRulesStep: 'Veritabanı kurallarını {{ .name }} üzerine geri yükle' +FirewallSyncWhitelistStep: 'Güvenlik duvarı bağlantı noktası izin listesini eşitle' +FirewallEnableForwardingStep: 'Bağlantı noktası yönlendirme zincirlerini etkinleştir ve bağla' +FirewallRestoreForwardingRulesStep: 'Veritabanı bağlantı noktası yönlendirme kurallarını geri yükle' +FirewallInspectDockerGuardStep: 'Docker güvenlik duvarı arka ucunu ve ilkelerini denetle' +FirewallInitializeDockerGuardStep: '{{ .name }} bağlantı noktası koruma zincirlerini başlat ve bağla' +FirewallPersistDockerGuardStep: 'Docker bağlantı noktası koruma durumunu kaydet' FirewallTargetRuleIneffective: 'Hedef kural artık etkili değil' FirewallVerifyRuleFailed: 'Eşitlenen {{ .name }} kuralı doğrulanamadı: {{ .detail }}' TaskSync: 'Senkronize Et' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index e1003d85b..c22161fb3 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: '{{ .name }} 應用不適配目前 1Panel 版本,跳 ErrDockerFailed: 'Docker 狀態異常,請檢查服務狀態' ErrDockerIptablesChainUnavailable: '未偵測到 Docker 的 DOCKER-USER 鏈,請重新啟動 Docker 後再試' ErrDockerNftablesChainUnavailable: '未偵測到 Docker 的 nftables IPv4 防火牆鏈,請確認目前 Docker 版本支援 nftables 防火牆後端,重新啟動 Docker 後再試' +ErrUFWRuleAdopt: 'UFW 規則納管失敗:{{ .detail }}。如果目前 UFW 版本低於 0.35,請先升級 UFW 後再試' ErrDockerComposeCmdNotFound: 'Docker Compose 指令不存在,請先在宿主機安裝此指令' UseExistImage: '映像已存在,使用現有映像' UpgradePrepare: '準備應用程式升級' @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: '{{ .failed }} 條防火牆規則同步失敗, FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}' FirewallResetSourceResult: '來源防火牆重設完成,已刪除 {{ .removed }} 條資料庫規則' FirewallVerifyTargetStep: '驗證目標防火牆規則' +FirewallInitializeChainsStep: '初始化並綁定 {{ .name }} 基礎鏈' +FirewallRestoreRulesStep: '恢復資料庫規則至 {{ .name }}' +FirewallSyncWhitelistStep: '同步防火牆連接埠白名單' +FirewallEnableForwardingStep: '啟用並綁定連接埠轉發鏈' +FirewallRestoreForwardingRulesStep: '恢復資料庫連接埠轉發規則' +FirewallInspectDockerGuardStep: '檢查 Docker 防火牆後端與防護策略' +FirewallInitializeDockerGuardStep: '初始化並綁定 {{ .name }} 連接埠防護鏈' +FirewallPersistDockerGuardStep: '儲存 Docker 連接埠防護狀態' FirewallTargetRuleIneffective: '目標規則已失效' FirewallVerifyRuleFailed: '驗證同步規則 {{ .name }} 失敗:{{ .detail }}' TaskSync: '同步' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index b386f4176..22f0aa967 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -160,6 +160,7 @@ ErrAppVersionDeprecated: "{{ .name }} 应用不适配当前 1Panel 版本,跳 ErrDockerFailed: "Docker 状态异常,请检查服务状态" ErrDockerIptablesChainUnavailable: "未检测到 Docker 的 DOCKER-USER 链,请重启 Docker 后重试" ErrDockerNftablesChainUnavailable: "未检测到 Docker 的 nftables IPv4 防火墙链,请确认当前 Docker 版本支持 nftables 防火墙后端,重启 Docker 后重试" +ErrUFWRuleAdopt: "UFW 规则纳管失败:{{ .detail }}。如果当前 UFW 版本低于 0.35,请先升级 UFW 后重试" ErrDockerComposeCmdNotFound: "Docker Compose 命令不存在,请先在宿主机安装" UseExistImage: "镜像已存在,使用存量镜像" UpgradePrepare: "准备应用升级" @@ -512,6 +513,14 @@ FirewallSyncFailedResetSkipped: "{{ .failed }} 条防火墙规则同步失败, FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}" FirewallResetSourceResult: "源防火墙重置完成,已删除 {{ .removed }} 条数据库规则" FirewallVerifyTargetStep: "验证目标防火墙规则" +FirewallInitializeChainsStep: "初始化并绑定 {{ .name }} 基础链" +FirewallRestoreRulesStep: "恢复数据库规则到 {{ .name }}" +FirewallSyncWhitelistStep: "同步防火墙端口白名单" +FirewallEnableForwardingStep: "启用并绑定端口转发链" +FirewallRestoreForwardingRulesStep: "恢复数据库端口转发规则" +FirewallInspectDockerGuardStep: "检查 Docker 防火墙后端和防护策略" +FirewallInitializeDockerGuardStep: "初始化并绑定 {{ .name }} 端口防护链" +FirewallPersistDockerGuardStep: "保存 Docker 端口防护状态" FirewallTargetRuleIneffective: "目标规则已失效" FirewallVerifyRuleFailed: "验证同步规则 {{ .name }} 失败:{{ .detail }}" TaskSync: "同步" diff --git a/agent/utils/firewall/command_diagnostic.go b/agent/utils/firewall/command_diagnostic.go new file mode 100644 index 000000000..29c9dd810 --- /dev/null +++ b/agent/utils/firewall/command_diagnostic.go @@ -0,0 +1,35 @@ +package firewall + +import ( + "fmt" + "strings" +) + +const maxCommandDiagnosticInput = 16 * 1024 + +// WrapBatchCommandError keeps successful task logs compact while making failed +// generated firewall batches directly diagnosable from the task log. +func WrapBatchCommandError(command, input string, err error) error { + if err == nil { + return nil + } + return fmt.Errorf("command=%s failed: %w\nbatch input:\n%s", command, err, numberedCommandInput(input)) +} + +func numberedCommandInput(input string) string { + input = strings.TrimSpace(input) + truncated := false + if len(input) > maxCommandDiagnosticInput { + input = input[:maxCommandDiagnosticInput] + truncated = true + } + lines := strings.Split(input, "\n") + var result strings.Builder + for index, line := range lines { + fmt.Fprintf(&result, "%04d %s\n", index+1, line) + } + if truncated { + result.WriteString("... batch input truncated ...\n") + } + return strings.TrimRight(result.String(), "\n") +} diff --git a/agent/utils/firewall/docker_guard/manager.go b/agent/utils/firewall/docker_guard/manager.go index eb305df5b..eeb42527e 100644 --- a/agent/utils/firewall/docker_guard/manager.go +++ b/agent/utils/firewall/docker_guard/manager.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" "github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper" ) @@ -79,7 +80,11 @@ type commandRunner struct{} func (commandRunner) Run(executable string, args ...string) (string, error) { executable = dockerGuardExecutable(executable) manager := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)) - return manager.RunWithOptionalSudoAndStdout(executable, args...) + stdout, err := manager.RunWithOptionalSudoAndStdout(executable, args...) + if err != nil { + return stdout, fmt.Errorf("command=%s %s failed: %w", executable, strings.Join(args, " "), err) + } + return stdout, nil } func (commandRunner) RunInput(executable, input string, args ...string) (string, error) { @@ -88,7 +93,8 @@ func (commandRunner) RunInput(executable, input string, args ...string) (string, return "", nftables_helper.RunScript(input) } manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithStdin(strings.NewReader(input))) - return manager.RunWithOptionalSudoAndStdout(executable, args...) + stdout, err := manager.RunWithOptionalSudoAndStdout(executable, args...) + return stdout, firewallutil.WrapBatchCommandError(executable+" "+strings.Join(args, " "), input, err) } func (commandRunner) Exists(executable string) bool { diff --git a/agent/utils/firewall/docker_guard/nftables.go b/agent/utils/firewall/docker_guard/nftables.go index eb2dfc532..94d9766d4 100644 --- a/agent/utils/firewall/docker_guard/nftables.go +++ b/agent/utils/firewall/docker_guard/nftables.go @@ -401,6 +401,9 @@ func nftHasFirstUniqueJump(output string) bool { continue } fields := strings.Fields(line) + if len(fields) == 0 || fields[0] == "table" || fields[0] == "chain" { + continue + } return len(fields) >= 2 && fields[0] == "jump" && fields[1] == NftChain } return false diff --git a/agent/utils/firewall/filter/providers/nftables/adapter.go b/agent/utils/firewall/filter/providers/nftables/adapter.go index 7a5e4493c..0fd2781be 100644 --- a/agent/utils/firewall/filter/providers/nftables/adapter.go +++ b/agent/utils/firewall/filter/providers/nftables/adapter.go @@ -542,7 +542,7 @@ type systemBackend struct{} func (systemBackend) ListChain(ctx context.Context, scope filter.Scope) (string, error) { return cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout( - "nft", "-n", "-a", "list", "chain", nftables_helper.TableFamily(scope.Family), nftables_helper.TableName, nativeChainName(scope), + "nft", "-n", "-n", "-a", "list", "chain", nftables_helper.TableFamily(scope.Family), nftables_helper.TableName, nativeChainName(scope), ) } diff --git a/agent/utils/firewall/filter/providers/ufw/adapter.go b/agent/utils/firewall/filter/providers/ufw/adapter.go index cb4485b53..452485fbe 100644 --- a/agent/utils/firewall/filter/providers/ufw/adapter.go +++ b/agent/utils/firewall/filter/providers/ufw/adapter.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/re" @@ -164,24 +165,27 @@ func (a *Adapter) Apply(ctx context.Context, plan filter.BackendPlan) (filter.Ap if a.failedCommandApplied(ctx, plan.Rules[0], index) { executed = index + 1 } - return filter.ApplyResult{}, a.compensate(ctx, plan.Rules[0], executed, fmt.Errorf("execute UFW rule: %w", err)) + cause := ufwApplyError(plan.Rules[0], fmt.Errorf("execute UFW rule: %w", err)) + return filter.ApplyResult{}, a.compensate(ctx, plan.Rules[0], executed, cause) } executed = index + 1 } verification, err := a.verify(ctx, plan) if err != nil { - return filter.ApplyResult{}, a.compensate(ctx, plan.Rules[0], executed, fmt.Errorf("verify UFW rule: %w", err)) + cause := ufwApplyError(plan.Rules[0], fmt.Errorf("verify UFW rule: %w", err)) + return filter.ApplyResult{}, a.compensate(ctx, plan.Rules[0], executed, cause) } if !verification.Matched { + cause := ufwApplyError(plan.Rules[0], fmt.Errorf( + "ufw write verification failed for marker %q in scope %s", + plan.Rules[0].Expected.Marker, + plan.Scope.Key(), + )) return filter.ApplyResult{}, a.compensate( ctx, plan.Rules[0], executed, - fmt.Errorf( - "ufw write verification failed for marker %q in scope %s", - plan.Rules[0].Expected.Marker, - plan.Scope.Key(), - ), + cause, ) } return filter.ApplyResult{ @@ -190,6 +194,13 @@ func (a *Adapter) Apply(ctx context.Context, plan filter.BackendPlan) (filter.Ap }, nil } +func ufwApplyError(plan filter.NativeRulePlan, cause error) error { + if plan.Operation == filter.ChangeAdopt { + return buserr.WithDetail("ErrUFWRuleAdopt", cause.Error(), cause) + } + return cause +} + func (a *Adapter) failedCommandApplied(ctx context.Context, plan filter.NativeRulePlan, commandIndex int) bool { snapshot, err := a.Observe(ctx, plan.Expected.Rule.Scope) if err != nil { @@ -197,8 +208,10 @@ func (a *Adapter) failedCommandApplied(ctx context.Context, plan filter.NativeRu } markerCount := countMarker(snapshot, plan.Expected.Marker) switch plan.Operation { - case filter.ChangeCreate, filter.ChangeAdopt: + case filter.ChangeCreate: return markerCount > 0 + case filter.ChangeAdopt: + return plan.Previous == nil || !containsObservedRule(snapshot, *plan.Previous) case filter.ChangeUpdate: if commandIndex == 0 { return markerCount == 0 @@ -307,14 +320,11 @@ func compileChange(snapshot filter.Snapshot, change filter.DesiredChange) (filte position = *target.Locator.Position plan.Previous = &target plan.Expected = observedForRule(normalized, marker, position) - plan.Commands = []filter.NativeCommand{ - deletePositionCommand(position), - insertCommand(position, normalized, marker), - } - plan.RollbackCommands = []filter.NativeCommand{ - insertCommand(position, target.Rule, observedComment(target)), - deleteRuleCommand(normalized, marker), - } + // UFW updates the comment of an existing rule when the same rule is added + // without insert/prepend. This keeps the rule active and in its original + // position throughout adoption. + plan.Commands = []filter.NativeCommand{commentCommand(normalized, marker)} + plan.RollbackCommands = []filter.NativeCommand{commentCommand(target.Rule, observedComment(target))} case filter.ChangeUpdate: target, targetErr := validateMutationTarget(snapshot, change, normalized, marker, true) if targetErr != nil { @@ -655,6 +665,19 @@ func countMarker(snapshot filter.Snapshot, marker string) int { return count } +func containsObservedRule(snapshot filter.Snapshot, expected filter.ObservedRule) bool { + for _, observed := range snapshot.Rules { + if expected.Locator.Position != nil && + (observed.Locator.Position == nil || *observed.Locator.Position != *expected.Locator.Position) { + continue + } + if observed.Marker == expected.Marker && filter.ObservedRuleMatchesExpected(observed, expected.Rule) { + return true + } + } + return false +} + func hasScopeNotice(notices []filter.ScopeNotice, code filter.ScopeNoticeCode) bool { for _, notice := range notices { if notice.Code == code { diff --git a/agent/utils/firewall/forwarding/providers/iptables.go b/agent/utils/firewall/forwarding/providers/iptables.go index be8570357..58f7b6d74 100644 --- a/agent/utils/firewall/forwarding/providers/iptables.go +++ b/agent/utils/firewall/forwarding/providers/iptables.go @@ -11,6 +11,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding" "github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper" "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" @@ -63,7 +64,8 @@ func (systemIptablesBackend) Restore(family, input string) error { } } manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithStdin(strings.NewReader(input))) - return manager.RunWithOptionalSudo(executable, "--noflush", "--wait") + err = manager.RunWithOptionalSudo(executable, "--noflush", "--wait") + return firewallutil.WrapBatchCommandError(executable+" --noflush --wait", input, err) } func (systemIptablesBackend) LoadRulesFromFile(table, chain, fileName string) error { diff --git a/agent/utils/firewall/forwarding/providers/nftables.go b/agent/utils/firewall/forwarding/providers/nftables.go index dc1d0161f..90aef8f0f 100644 --- a/agent/utils/firewall/forwarding/providers/nftables.go +++ b/agent/utils/firewall/forwarding/providers/nftables.go @@ -329,11 +329,19 @@ func nftForwardChain(logical string) string { } func nftRun(args ...string) (string, error) { - return cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout("nft", args...) + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout("nft", args...) + if err != nil { + return stdout, fmt.Errorf("command=nft %s failed: %w", strings.Join(args, " "), err) + } + return stdout, nil } func nftRunCommand(args ...string) error { - return cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudo("nft", args...) + err := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudo("nft", args...) + if err != nil { + return fmt.Errorf("command=nft %s failed: %w", strings.Join(args, " "), err) + } + return nil } func nftRunCommands(commands [][]string) error { diff --git a/agent/utils/firewall/iptables_helper/command.go b/agent/utils/firewall/iptables_helper/command.go index 685fbcf94..b85c0b4e5 100644 --- a/agent/utils/firewall/iptables_helper/command.go +++ b/agent/utils/firewall/iptables_helper/command.go @@ -54,7 +54,11 @@ func runTables(ctx context.Context, executable, tab string, ignoreExist1, withWa args = append(args, "-w") } args = append(args, ruleArgs...) - return cmdMgr.RunWithOptionalSudoAndStdout(executable, args...) + stdout, err := cmdMgr.RunWithOptionalSudoAndStdout(executable, args...) + if err != nil { + return stdout, fmt.Errorf("command=%s %s failed: %w", executable, strings.Join(args, " "), err) + } + return stdout, nil } func runIptables(ctx context.Context, tab string, ignoreExist1, withWait bool, ruleArgs ...string) (string, error) { diff --git a/agent/utils/firewall/iptables_helper/persistence.go b/agent/utils/firewall/iptables_helper/persistence.go index 682bb89d5..827d1895c 100644 --- a/agent/utils/firewall/iptables_helper/persistence.go +++ b/agent/utils/firewall/iptables_helper/persistence.go @@ -11,6 +11,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" ) @@ -190,5 +191,6 @@ func loadRulesFromFile(executable, restoreExecutable, tab, chain, fileName strin func restoreRules(executable, input string) error { manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithStdin(strings.NewReader(input))) - return manager.RunWithOptionalSudo(executable, "--noflush", "--wait") + err := manager.RunWithOptionalSudo(executable, "--noflush", "--wait") + return firewallutil.WrapBatchCommandError(executable+" --noflush --wait", input, err) } diff --git a/agent/utils/firewall/nftables_helper/runtime.go b/agent/utils/firewall/nftables_helper/runtime.go index 5e9073caa..53ccc47df 100644 --- a/agent/utils/firewall/nftables_helper/runtime.go +++ b/agent/utils/firewall/nftables_helper/runtime.go @@ -8,6 +8,7 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" ) @@ -31,11 +32,19 @@ func BasicChains() []string { } func run(args ...string) (string, error) { - return cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout("nft", args...) + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout("nft", args...) + if err != nil { + return stdout, fmt.Errorf("command=nft %s failed: %w", strings.Join(args, " "), err) + } + return stdout, nil } func runCommand(args ...string) error { - return cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudo("nft", args...) + err := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithOptionalSudo("nft", args...) + if err != nil { + return fmt.Errorf("command=nft %s failed: %w", strings.Join(args, " "), err) + } + return nil } func runBatch(commands ...[]string) error { @@ -75,7 +84,7 @@ func runScriptFile(script string, run func(string) error) error { if err := file.Close(); err != nil { return err } - return run(name) + return firewallutil.WrapBatchCommandError("nft -f ", script, run(name)) } func buildBatchScript(commands ...[]string) (string, error) { diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go index c40b286bc..2a4bbac77 100644 --- a/core/cmd/server/docs/docs.go +++ b/core/cmd/server/docs/docs.go @@ -18579,7 +18579,10 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -18763,7 +18766,10 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -18818,9 +18824,26 @@ const docTemplate = `{ }, "/hosts/firewall/forward/enable": { "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FirewallInitializationTask" + } + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -35619,6 +35642,10 @@ const docTemplate = `{ "unbind" ], "type": "string" + }, + "taskID": { + "maxLength": 64, + "type": "string" } }, "required": [ @@ -35882,6 +35909,10 @@ const docTemplate = `{ "unbind-base" ], "type": "string" + }, + "taskID": { + "maxLength": 64, + "type": "string" } }, "required": [ @@ -35890,6 +35921,17 @@ const docTemplate = `{ ], "type": "object" }, + "dto.FilterChainOperationResponse": { + "properties": { + "queued": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, "dto.FirewallBackendFamilyStatus": { "properties": { "bound": { @@ -35988,6 +36030,15 @@ const docTemplate = `{ }, "type": "object" }, + "dto.FirewallInitializationTask": { + "properties": { + "taskID": { + "maxLength": 64, + "type": "string" + } + }, + "type": "object" + }, "dto.FirewallLifecycleOperation": { "properties": { "operation": { diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json index 63a20214c..589344685 100644 --- a/core/cmd/server/docs/swagger.json +++ b/core/cmd/server/docs/swagger.json @@ -18575,7 +18575,10 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -18759,7 +18762,10 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -18814,9 +18820,26 @@ }, "/hosts/firewall/forward/enable": { "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FirewallInitializationTask" + } + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FilterChainOperationResponse" + } } }, "security": [ @@ -35615,6 +35638,10 @@ "unbind" ], "type": "string" + }, + "taskID": { + "maxLength": 64, + "type": "string" } }, "required": [ @@ -35878,6 +35905,10 @@ "unbind-base" ], "type": "string" + }, + "taskID": { + "maxLength": 64, + "type": "string" } }, "required": [ @@ -35886,6 +35917,17 @@ ], "type": "object" }, + "dto.FilterChainOperationResponse": { + "properties": { + "queued": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, "dto.FirewallBackendFamilyStatus": { "properties": { "bound": { @@ -35984,6 +36026,15 @@ }, "type": "object" }, + "dto.FirewallInitializationTask": { + "properties": { + "taskID": { + "maxLength": 64, + "type": "string" + } + }, + "type": "object" + }, "dto.FirewallLifecycleOperation": { "properties": { "operation": { diff --git a/frontend/src/api/interface/firewall.ts b/frontend/src/api/interface/firewall.ts index 39c02db73..832820297 100644 --- a/frontend/src/api/interface/firewall.ts +++ b/frontend/src/api/interface/firewall.ts @@ -40,6 +40,10 @@ export namespace Firewall { backend: Provider; operation: BackendOperation; } + export interface FilterChainOperationResult { + taskID?: string; + queued?: boolean; + } export interface FirewallBase { name: string; backend: string; diff --git a/frontend/src/api/modules/firewall.ts b/frontend/src/api/modules/firewall.ts index 3dfd0aa00..d2bdaf12f 100644 --- a/frontend/src/api/modules/firewall.ts +++ b/frontend/src/api/modules/firewall.ts @@ -18,10 +18,19 @@ export const operateFire = (operation: string, withDockerRestart: boolean) => export const operateForwardRule = (request: { rules: Firewall.RuleForward[]; forceDelete?: boolean }) => http.post('/hosts/firewall/forward/operate', request, TimeoutEnum.T_40S); -export const enableForwarding = () => http.post('/hosts/firewall/forward/enable', {}, TimeoutEnum.T_60S); +export const enableForwarding = (taskID?: string) => + http.post( + '/hosts/firewall/forward/enable', + taskID ? { taskID } : {}, + TimeoutEnum.T_60S, + ); -export const operateFilterChain = (name: string, operate: string) => - http.post('/hosts/firewall/filter/operate', { name, operate }, TimeoutEnum.T_60S); +export const operateFilterChain = (name: string, operate: string, taskID?: string) => + http.post( + '/hosts/firewall/filter/operate', + { name, operate, ...(taskID ? { taskID } : {}) }, + TimeoutEnum.T_60S, + ); export const searchFirewallRules = (request: Firewall.InventoryRequest) => { return http.post('/hosts/firewall/rules/search', request, TimeoutEnum.T_40S); @@ -83,10 +92,10 @@ export const syncDockerPortGuard = () => }, ); -export const operateDockerPortGuard = (operation: 'initialize' | 'bind' | 'unbind') => - http.postWithConfig( +export const operateDockerPortGuard = (operation: 'initialize' | 'bind' | 'unbind', taskID?: string) => + http.postWithConfig( '/hosts/firewall/docker/operate', - { operation }, + { operation, ...(taskID ? { taskID } : {}) }, { timeout: TimeoutEnum.T_60S, skipErrorMessage: true, diff --git a/frontend/src/views/host/firewall/docker/detail/index.vue b/frontend/src/views/host/firewall/docker/detail/index.vue index 38d0cb9e2..138f1b2b3 100644 --- a/frontend/src/views/host/firewall/docker/detail/index.vue +++ b/frontend/src/views/host/firewall/docker/detail/index.vue @@ -88,6 +88,12 @@ {{ group.endpoint.description || '-' }} +
+ {{ $t('commons.table.message') }} + + {{ statusMessage(group.endpoint) }} + +
@@ -159,10 +165,10 @@ import { deleteDockerPortGuardPolicies, upsertDockerPortGuardPolicies } from '@/ import i18n from '@/lang'; import { MsgSuccess } from '@/utils/message'; import { ElMessageBox, type FormInstance, type FormRules } from 'element-plus'; -import { isValidDockerGuardSource } from '@/views/host/firewall/docker/model'; +import { dockerGuardEndpointStatusMessage, isValidDockerGuardSource } from '@/views/host/firewall/docker/model'; import { formatHostAddress, formatHostAddressList, splitTagValues } from '@/views/host/firewall/utils/validation'; -const props = defineProps<{ containers: Firewall.DockerGuardContainer[] }>(); +const props = defineProps<{ base: Firewall.DockerGuardBase; containers: Firewall.DockerGuardContainer[] }>(); const emit = defineEmits<{ search: [] }>(); const drawerVisible = ref(false); @@ -225,6 +231,8 @@ const selectedPolicyUUIDs = computed(() => policyUUIDs(selectedEndpoints.value)) const sourcesLabel = computed(() => form.mode === 'allow_sources' ? i18n.global.t('firewall.allowedSources') : i18n.global.t('firewall.deniedSources'), ); +const statusMessage = (endpoint: Firewall.DockerGuardEndpoint) => + dockerGuardEndpointStatusMessage(props.base, endpoint); type ValidationCallback = (error?: Error) => void; const validateSources = (_rule: unknown, value: string[], callback: ValidationCallback) => { const sources = splitTagValues(value || []); @@ -471,6 +479,10 @@ defineExpose({ acceptParams, openPolicy }); white-space: nowrap; } +.port-card-value.is-warning { + color: var(--el-color-warning); +} + .port-card-actions { display: flex; flex: none; diff --git a/frontend/src/views/host/firewall/docker/index.vue b/frontend/src/views/host/firewall/docker/index.vue index 7a27acca7..bda0567c8 100644 --- a/frontend/src/views/host/firewall/docker/index.vue +++ b/frontend/src/views/host/firewall/docker/index.vue @@ -105,13 +105,21 @@ v-if="group.endpoint.policyUUID" :label="$t('commons.table.status')" > - {{ - $t( - group.endpoint.effective - ? 'firewall.effective' - : 'firewall.notEffective', - ) - }} +
+ {{ + $t( + group.endpoint.effective + ? 'firewall.effective' + : 'firewall.notEffective', + ) + }} +
+ {{ endpointStatusMessage(group.endpoint) }} +
+
- + + @@ -215,6 +224,7 @@ import DockerGuardDetail from '@/views/host/firewall/docker/detail/index.vue'; import DockerGuardImport from '@/views/host/firewall/docker/import/index.vue'; import RuleSync from '@/views/host/firewall/sync/index.vue'; import ConfirmDialog from '@/components/confirm-dialog/index.vue'; +import TaskLog from '@/components/log/task/index.vue'; import { Firewall } from '@/api/interface/firewall'; import { deleteDockerPortGuardPolicies, @@ -228,14 +238,16 @@ import { ElMessageBox } from 'element-plus'; import { Lock } from '@element-plus/icons-vue'; import { downloadWithContent } from '@/utils/file'; import { getCurrentDateFormatted } from '@/utils/date'; -import { dockerGuardEndpointKey } from '@/views/host/firewall/docker/model'; +import { dockerGuardEndpointKey, dockerGuardEndpointStatusMessage } from '@/views/host/firewall/docker/model'; import { formatHostAddressList } from '@/views/host/firewall/utils/validation'; +import { newUUID } from '@/utils/id'; const loading = ref(false); const detailRef = ref>(); const importRef = ref>(); const ruleSyncRef = ref>(); const cleanupConfirmRef = ref>(); +const taskLogRef = ref>(); const orphanDrawerVisible = ref(false); const searchName = ref(''); const selects = ref([]); @@ -387,6 +399,8 @@ const endpointLabel = (endpoint: Firewall.DockerGuardEndpoint) => { }; const displaySources = (endpoint: Firewall.DockerGuardEndpoint) => formatHostAddressList(endpoint.sources, endpoint.family); +const endpointStatusMessage = (endpoint: Firewall.DockerGuardEndpoint) => + dockerGuardEndpointStatusMessage(data.base, endpoint); const search = async () => { loading.value = true; @@ -411,7 +425,12 @@ const operate = async (operation: 'initialize' | 'bind' | 'unbind') => { } } try { - await operateDockerPortGuard(operation); + const result = (await operateDockerPortGuard(operation, operation === 'initialize' ? newUUID() : undefined)) + .data; + if (operation === 'initialize' && result.queued && result.taskID) { + taskLogRef.value?.openWithTaskID(result.taskID, true); + return; + } } catch { await search(); return; @@ -523,4 +542,10 @@ search(); text-overflow: ellipsis; white-space: nowrap; } + +.docker-guard-status-message { + margin-top: 4px; + color: var(--el-color-warning); + line-height: 18px; +} diff --git a/frontend/src/views/host/firewall/docker/model.ts b/frontend/src/views/host/firewall/docker/model.ts index cb6ba9921..3885e0e37 100644 --- a/frontend/src/views/host/firewall/docker/model.ts +++ b/frontend/src/views/host/firewall/docker/model.ts @@ -1,4 +1,5 @@ import { Firewall } from '@/api/interface/firewall'; +import i18n from '@/lang'; import { isValidAddressForFamily } from '@/views/host/firewall/utils/validation'; type DockerGuardEndpointIdentity = Pick; @@ -9,6 +10,34 @@ export const dockerGuardEndpointKey = (endpoint: DockerGuardEndpointIdentity): s export const isValidDockerGuardSource = (family: Firewall.DockerGuardEndpoint['family'], value: string): boolean => isValidAddressForFamily(family, value); +export const dockerGuardFamilyStatusMessage = ( + base: Firewall.DockerGuardBase, + family: 'IPv4' | 'IPv6', + status: Firewall.DockerGuardFamilyStatus, +): string => { + if (status.reason) { + return i18n.global.t(`firewall.dockerGuardStatusReason.${status.reason}`, [family]); + } + const state = i18n.global.t( + !status.initialized + ? 'firewall.notInitialized' + : !status.bound + ? 'commons.status.unbind' + : 'firewall.notEffective', + ); + const chainName = base.backend === 'nftables' ? 'NFT_1PANEL_DOCKER' : '1PANEL_DOCKER'; + return i18n.global.t('firewall.familyChainIssue', [family, chainName, state]); +}; + +export const dockerGuardEndpointStatusMessage = ( + base: Firewall.DockerGuardBase, + endpoint: Firewall.DockerGuardEndpoint, +): string => { + if (!endpoint.policyUUID || endpoint.effective) return ''; + const ipv6 = endpoint.family === 'ipv6'; + return dockerGuardFamilyStatusMessage(base, ipv6 ? 'IPv6' : 'IPv4', ipv6 ? base.ipv6 : base.ipv4); +}; + export const normalizeDockerGuardPolicy = (value: unknown): Firewall.DockerGuardPolicy | undefined => { if (!value || typeof value !== 'object') return; const policy = value as Partial; diff --git a/frontend/src/views/host/firewall/docker/status/index.vue b/frontend/src/views/host/firewall/docker/status/index.vue index 89470260f..3dfef2920 100644 --- a/frontend/src/views/host/firewall/docker/status/index.vue +++ b/frontend/src/views/host/firewall/docker/status/index.vue @@ -94,11 +94,11 @@