From fb377d2e9944c9bcdcc8bd8a02190c10c1f0590c Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 1 Sep 2026 14:53:01 +0800 Subject: [PATCH] fix(firewall): recover rules after upgrade (#13680) --- agent/app/dto/firewall.go | 32 +++--- agent/app/service/firewall_docker.go | 104 +++++++++++++----- agent/app/service/firewall_sync.go | 86 +++++++++++++++ agent/init/firewall/firewall.go | 3 + agent/init/migration/migrations/init.go | 14 ++- .../migrations/utils/firewall_transfer.go | 23 ++++ .../utils/host_firewall_transfer.go | 73 +++++++++++- .../firewall/filter/providers/ufw/adapter.go | 13 ++- .../utils/firewall/iptables_helper/legacy.go | 84 ++++++++++++++ frontend/src/api/interface/firewall.ts | 2 + frontend/src/lang/modules/en.ts | 2 +- frontend/src/lang/modules/es-es.ts | 3 +- frontend/src/lang/modules/fa.ts | 2 +- frontend/src/lang/modules/ja.ts | 3 +- frontend/src/lang/modules/ko.ts | 2 +- frontend/src/lang/modules/lo.ts | 2 +- frontend/src/lang/modules/ms.ts | 2 +- frontend/src/lang/modules/pt-br.ts | 2 +- frontend/src/lang/modules/ru.ts | 2 +- frontend/src/lang/modules/tr.ts | 3 +- frontend/src/lang/modules/zh-Hant.ts | 2 +- frontend/src/lang/modules/zh.ts | 2 +- .../host/firewall/docker/detail/index.vue | 34 ++++-- .../host/firewall/docker/import/index.vue | 82 +++++++++++--- .../src/views/host/firewall/docker/index.vue | 13 ++- .../src/views/host/firewall/docker/model.ts | 14 ++- .../host/firewall/forward/import/index.vue | 102 +++++++++++++---- .../views/host/firewall/rule/import/index.vue | 92 +++++++++++++--- .../src/views/host/firewall/rule/index.vue | 14 ++- 29 files changed, 670 insertions(+), 142 deletions(-) create mode 100644 agent/utils/firewall/iptables_helper/legacy.go diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go index 98b35611b..be7fda426 100644 --- a/agent/app/dto/firewall.go +++ b/agent/app/dto/firewall.go @@ -159,21 +159,23 @@ type DockerPortGuardFamilyStatus struct { } type DockerPortGuardEndpoint struct { - Family string `json:"family"` - HostIP string `json:"hostIP"` - HostPort uint16 `json:"hostPort"` - Protocol string `json:"protocol"` - ContainerID string `json:"containerID"` - ContainerName string `json:"containerName"` - ContainerPort uint16 `json:"containerPort"` - Compose string `json:"compose,omitempty"` - Application string `json:"application,omitempty"` - PolicyUUID string `json:"policyUUID,omitempty"` - Mode string `json:"mode,omitempty"` - Sources []string `json:"sources"` - Effective bool `json:"effective"` - Description string `json:"description,omitempty"` - TrafficPath string `json:"trafficPath"` + Family string `json:"family"` + HostIP string `json:"hostIP"` + HostPort uint16 `json:"hostPort"` + Protocol string `json:"protocol"` + ContainerID string `json:"containerID"` + ContainerName string `json:"containerName"` + ContainerPort uint16 `json:"containerPort"` + Compose string `json:"compose,omitempty"` + Application string `json:"application,omitempty"` + PolicyUUID string `json:"policyUUID,omitempty"` + Mode string `json:"mode,omitempty"` + Sources []string `json:"sources"` + Effective bool `json:"effective"` + Description string `json:"description,omitempty"` + TrafficPath string `json:"trafficPath"` + ManagementTarget string `json:"managementTarget"` + ManagementReason string `json:"managementReason,omitempty"` } type DockerPortGuardPortGroup struct { diff --git a/agent/app/service/firewall_docker.go b/agent/app/service/firewall_docker.go index 2aa6b3b93..15782558a 100644 --- a/agent/app/service/firewall_docker.go +++ b/agent/app/service/firewall_docker.go @@ -33,11 +33,18 @@ import ( ) const ( - dockerGuardComposeProjectLabel = "com.docker.compose.project" - dockerGuardComposeCreatedBy = "createdBy" - dockerTrafficPathForward = "forward" - dockerTrafficPathInput = "input" - dockerTrafficPathUnknown = "unknown" + dockerGuardComposeProjectLabel = "com.docker.compose.project" + dockerGuardComposeCreatedBy = "createdBy" + dockerTrafficPathForward = "forward" + dockerTrafficPathInput = "input" + dockerTrafficPathUnknown = "unknown" + dockerManagementContainerGuard = "container_guard" + dockerManagementHostFirewall = "host_firewall" + dockerManagementNeedsDiagnosis = "needs_diagnosis" + dockerReasonNATInspectFailed = "nat_inspect_failed" + dockerReasonNATChainUnreachable = "nat_chain_unreachable" + dockerReasonProxyInspectFailed = "proxy_inspect_failed" + dockerReasonNoMatchingPath = "no_matching_path" ) type dockerProxyEndpoint struct { @@ -51,6 +58,11 @@ type dockerForwardRules struct { inspected bool } +type dockerProxyEndpoints struct { + items []dockerProxyEndpoint + inspected bool +} + type dockerGuardRuntime = docker_guard.Runtime type DockerPortGuardService struct { @@ -121,7 +133,7 @@ func (s *DockerPortGuardService) LoadPublishedPorts(ctx context.Context) ([]dto. if info, infoErr := cli.Info(ctx); infoErr == nil { backend = dockerFirewallBackend(info) } - annotateDockerEndpointTrafficPaths(endpoints, backend) + annotateDockerEndpointManagement(endpoints, backend) return groupDockerGuardContainers(endpoints), nil } @@ -155,7 +167,7 @@ func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPo if err != nil { return dto.DockerPortGuardList{}, err } - annotateDockerEndpointTrafficPaths(endpoints, detectedBackend) + annotateDockerEndpointManagement(endpoints, detectedBackend) endpoints, orphanPolicies := matchDockerGuardPolicies(base, policies, endpoints) sort.Slice(endpoints, func(i, j int) bool { return guardEndpointKey(endpoints[i].Family, endpoints[i].HostIP, endpoints[i].HostPort, endpoints[i].Protocol) < guardEndpointKey(endpoints[j].Family, endpoints[j].HostIP, endpoints[j].HostPort, endpoints[j].Protocol) @@ -183,7 +195,7 @@ func matchDockerGuardPolicies( } endpoints[i].PolicyUUID, endpoints[i].Mode, endpoints[i].Sources = policy.UUID, policy.Mode, docker_guard.DecodeSources(policy.Sources) endpoints[i].Description = policy.Description - endpoints[i].Effective = endpoints[i].TrafficPath == dockerTrafficPathForward && + endpoints[i].Effective = endpoints[i].ManagementTarget == dockerManagementContainerGuard && ((policy.Family == docker_guard.FamilyIPv4 && base.IPv4.Effective) || (policy.Family == docker_guard.FamilyIPv6 && base.IPv6.Effective)) delete(byEndpoint, key) } @@ -192,7 +204,8 @@ func matchDockerGuardPolicies( orphanPolicies = append(orphanPolicies, dto.DockerPortGuardEndpoint{ Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol, PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources), Description: policy.Description, - TrafficPath: dockerTrafficPathUnknown, + TrafficPath: dockerTrafficPathUnknown, ManagementTarget: dockerManagementNeedsDiagnosis, + ManagementReason: dockerReasonNoMatchingPath, }) } return endpoints, orphanPolicies @@ -369,15 +382,19 @@ func (s *DockerPortGuardService) rejectHostInputDockerGuardEndpoints( if err != nil { return nil } - annotateDockerEndpointTrafficPaths(endpoints, dockerFirewallBackend(info)) - paths := make(map[string]string, len(endpoints)) + annotateDockerEndpointManagement(endpoints, dockerFirewallBackend(info)) + targets := make(map[string]string, len(endpoints)) for _, endpoint := range endpoints { - paths[guardEndpointKey(endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)] = endpoint.TrafficPath + targets[guardEndpointKey(endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)] = endpoint.ManagementTarget } for _, endpoint := range requested { - if paths[guardEndpointKey(endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)] == dockerTrafficPathInput { + target := targets[guardEndpointKey(endpoint.Family, endpoint.HostIP, endpoint.HostPort, endpoint.Protocol)] + if target == dockerManagementHostFirewall { return fmt.Errorf("%w: endpoint traffic is handled by the host input firewall", ErrDockerGuardInvalid) } + if target == dockerManagementNeedsDiagnosis { + return fmt.Errorf("%w: endpoint traffic management target requires diagnosis", ErrDockerGuardInvalid) + } } return nil } @@ -456,7 +473,8 @@ func dockerGuardRuleSyncDTO(policy model.DockerPortGuardPolicy) *dto.DockerPortG return &dto.DockerPortGuardEndpoint{ Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol, PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources), Description: policy.Description, - TrafficPath: dockerTrafficPathUnknown, + TrafficPath: dockerTrafficPathUnknown, ManagementTarget: dockerManagementNeedsDiagnosis, + ManagementReason: dockerReasonNoMatchingPath, } } @@ -464,7 +482,8 @@ func dockerGuardRuntimeRuleSyncDTO(policy docker_guard.Policy) *dto.DockerPortGu return &dto.DockerPortGuardEndpoint{ Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol, PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: append([]string(nil), policy.Sources...), - TrafficPath: dockerTrafficPathUnknown, + TrafficPath: dockerTrafficPathUnknown, ManagementTarget: dockerManagementNeedsDiagnosis, + ManagementReason: dockerReasonNoMatchingPath, } } @@ -696,6 +715,7 @@ func dockerGuardPolicyEndpoints(policies []model.DockerPortGuardPolicy) []dto.Do Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol, PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources), Description: policy.Description, TrafficPath: dockerTrafficPathUnknown, + ManagementTarget: dockerManagementNeedsDiagnosis, ManagementReason: dockerReasonNoMatchingPath, }) } return endpoints @@ -731,7 +751,7 @@ func groupDockerGuardContainers(endpoints []dto.DockerPortGuardEndpoint) []dto.D for i, endpoint := range container.Endpoints { sources := append([]string(nil), endpoint.Sources...) sort.Strings(sources) - policyKey := fmt.Sprintf("%t|%s|%s|%t|%s|%s", endpoint.PolicyUUID != "", endpoint.Mode, strings.Join(sources, ","), endpoint.Effective, endpoint.Description, endpoint.TrafficPath) + policyKey := fmt.Sprintf("%t|%s|%s|%t|%s|%s|%s", endpoint.PolicyUUID != "", endpoint.Mode, strings.Join(sources, ","), endpoint.Effective, endpoint.Description, endpoint.ManagementTarget, endpoint.ManagementReason) items = append(items, docker.PortRangeItem{ Key: endpoint.Family + "|" + endpoint.HostIP + "|" + endpoint.Protocol + "|" + policyKey, PublicPort: endpoint.HostPort, PrivatePort: endpoint.ContainerPort, @@ -777,7 +797,7 @@ func firstGuardString(values []string) string { return values[0] } -func annotateDockerEndpointTrafficPaths(endpoints []dto.DockerPortGuardEndpoint, backend string) { +func annotateDockerEndpointManagement(endpoints []dto.DockerPortGuardEndpoint, backend string) { rules := map[string]dockerForwardRules{ constant.FirewallFamilyIPv4: loadDockerDNATRules(backend, constant.FirewallFamilyIPv4), constant.FirewallFamilyIPv6: loadDockerDNATRules(backend, constant.FirewallFamilyIPv6), @@ -785,21 +805,34 @@ func annotateDockerEndpointTrafficPaths(endpoints []dto.DockerPortGuardEndpoint, proxies := loadDockerProxyEndpoints() for i := range endpoints { familyRules := rules[endpoints[i].Family] - endpoints[i].TrafficPath = dockerEndpointTrafficPath(backend, familyRules.output, familyRules.inspected, proxies, endpoints[i]) + endpoints[i].TrafficPath, endpoints[i].ManagementTarget, endpoints[i].ManagementReason = + dockerEndpointManagement(backend, familyRules, proxies, endpoints[i]) } } -func dockerEndpointTrafficPath(backend, rules string, inspected bool, proxies []dockerProxyEndpoint, endpoint dto.DockerPortGuardEndpoint) string { - if !inspected { - return dockerTrafficPathUnknown +func dockerEndpointManagement( + backend string, + rules dockerForwardRules, + proxies dockerProxyEndpoints, + endpoint dto.DockerPortGuardEndpoint, +) (string, string, string) { + if !rules.inspected { + return dockerTrafficPathUnknown, dockerManagementNeedsDiagnosis, dockerReasonNATInspectFailed } - if dockerDNATRuleMatches(backend, rules, endpoint) { - return dockerTrafficPathForward + dnatMatched := dockerDNATRuleMatches(backend, rules.output, endpoint) + if dnatMatched && dockerDNATIngressReachable(backend, rules.output) { + return dockerTrafficPathForward, dockerManagementContainerGuard, "" } - if dockerProxyEndpointMatches(proxies, endpoint) { - return dockerTrafficPathInput + if !proxies.inspected { + return dockerTrafficPathUnknown, dockerManagementNeedsDiagnosis, dockerReasonProxyInspectFailed } - return dockerTrafficPathUnknown + if dockerProxyEndpointMatches(proxies.items, endpoint) { + return dockerTrafficPathInput, dockerManagementHostFirewall, "" + } + if dnatMatched { + return dockerTrafficPathUnknown, dockerManagementNeedsDiagnosis, dockerReasonNATChainUnreachable + } + return dockerTrafficPathUnknown, dockerManagementNeedsDiagnosis, dockerReasonNoMatchingPath } func loadDockerDNATRules(backend, family string) dockerForwardRules { @@ -834,13 +867,13 @@ func loadDockerDNATRules(backend, family string) dockerForwardRules { return dockerForwardRules{output: output, inspected: err == nil} } -func loadDockerProxyEndpoints() []dockerProxyEndpoint { +func loadDockerProxyEndpoints() dockerProxyEndpoints { manager := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second), cmd.WithEnv("LC_ALL=C")) output, err := manager.RunWithStdout("ps", "-ww", "-eo", "args=") if err != nil { - return nil + return dockerProxyEndpoints{} } - return parseDockerProxyEndpoints(output) + return dockerProxyEndpoints{items: parseDockerProxyEndpoints(output), inspected: true} } func parseDockerProxyEndpoints(output string) []dockerProxyEndpoint { @@ -902,6 +935,19 @@ func dockerDNATRuleMatches(backend, output string, endpoint dto.DockerPortGuardE return iptablesDNATRuleMatches(output, endpoint) } +func dockerDNATIngressReachable(backend, output string) bool { + if backend == constant.FirewallProviderNftables { + return strings.Contains(output, "hook prerouting") + } + for _, line := range strings.Split(output, "\n") { + fields := strings.Fields(line) + if len(fields) >= 4 && fields[0] == "-A" && fields[1] == "PREROUTING" && commandFlagValue(fields, "-j") == "DOCKER" { + return true + } + } + return false +} + func iptablesDNATRuleMatches(output string, endpoint dto.DockerPortGuardEndpoint) bool { port := strconv.Itoa(int(endpoint.HostPort)) for _, line := range strings.Split(output, "\n") { diff --git a/agent/app/service/firewall_sync.go b/agent/app/service/firewall_sync.go index c49314422..1ce1f256d 100644 --- a/agent/app/service/firewall_sync.go +++ b/agent/app/service/firewall_sync.go @@ -19,6 +19,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard" "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter" "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper" firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync" "gorm.io/gorm" ) @@ -140,6 +141,91 @@ func (s *FirewallService) restoreStoredFirewallRules(ctx context.Context, provid return fmt.Errorf("restore database firewall rules: %s", strings.Join(messages, "; ")) } +func AdoptLegacyHostFirewallRuleOwnership(ctx context.Context) error { + return newFirewallService().adoptLegacyHostFirewallRuleOwnership(ctx) +} + +func (s *FirewallService) adoptLegacyHostFirewallRuleOwnership(ctx context.Context) error { + firewallRuleMutationMu.Lock() + defer firewallRuleMutationMu.Unlock() + + selected, err := s.selectedProvider(ctx) + if err != nil { + return err + } + if selected != filter.ProviderIptables && selected != filter.ProviderUFW { + return fmt.Errorf("%w: selected provider %s does not require legacy ownership transfer", filter.ErrProviderUnavailable, selected) + } + runtime, err := s.adapters.Resolve(selected) + if err != nil { + return err + } + stored, err := s.rules.List(ctx) + if err != nil { + return err + } + for _, scope := range filter.ManagedInputScopes(selected) { + desired, err := s.desiredFirewallRulesForScope(ctx, stored, scope) + if err != nil { + return err + } + if len(desired) == 0 { + continue + } + snapshot, err := runtime.ObserveMutation(ctx, scope) + if err != nil { + return err + } + for { + items, err := filter.MergeInventory(filter.InventoryMergeInput{ + Observed: snapshot.Rules, + Desired: desired, + }) + if err != nil { + return err + } + var candidate *filter.InventoryItem + for index := range items { + item := &items[index] + if item.Match != filter.InventoryMatchChanged || item.Desired == nil || item.Observed == nil || + item.Desired.Origin != filter.RuleOriginAdopted || strings.TrimSpace(item.Desired.Marker) == "" || + strings.TrimSpace(item.Observed.Marker) != "" || item.Observed.Protected || + !filter.ObservedRuleMatchesExpected(*item.Observed, item.Desired.Rule) { + continue + } + candidate = item + break + } + if candidate == nil { + break + } + after := candidate.Desired.Rule + before := firewallsync.ObservedRule(*candidate.Observed) + locator := candidate.Observed.Locator + _, verification, err := runtime.Execute(ctx, snapshot, []filter.DesiredChange{{ + Operation: filter.ChangeAdopt, + Before: &before, + After: &after, + Locator: &locator, + PreviousMarker: candidate.Observed.Marker, + }}) + if err != nil { + return err + } + if !verification.Matched { + return filter.ErrVerificationFailed + } + snapshot = verification.Snapshot + } + } + if selected == filter.ProviderIptables { + if err := iptables_helper.CleanupLegacyAdvancedChains(ctx); err != nil { + return err + } + } + return nil +} + func (s *FirewallService) loadFirewallRuleSyncPlan( ctx context.Context, clientIP string, diff --git a/agent/init/firewall/firewall.go b/agent/init/firewall/firewall.go index 592536287..ea9c4eabc 100644 --- a/agent/init/firewall/firewall.go +++ b/agent/init/firewall/firewall.go @@ -30,6 +30,9 @@ func Init() { global.LOG.Errorf("transfer legacy host firewall records failed, err: %v", err) return } + if err := migrationutils.TransferLegacyHostFirewallRuleOwnership(ctx, clientName, service.AdoptLegacyHostFirewallRuleOwnership); err != nil { + global.LOG.Warnf("transfer legacy host firewall rule ownership failed, err: %v", err) + } if err := migrationutils.TransferFirewallForwarding(ctx); err != nil { global.LOG.Errorf("transfer legacy forwarding rules failed, err: %v", err) return diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 7d353945c..2833f43f3 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1705,12 +1705,22 @@ var NormalizeFirewallBackendSelections = &gormigrate.Migration{ ID: "20260826-normalize-firewall-backend-selections", Migrate: func(tx *gorm.DB) error { return tx.Transaction(func(tx *gorm.DB) error { + result := tx.Model(&model.Setting{}). + Where( + "key = ? AND value NOT IN ?", + constant.FirewallDockerBackendKey, + []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables}, + ). + Update("value", constant.FirewallProviderIptables) + if result.Error != nil { + return result.Error + } if err := tx.Where("key = ?", constant.FirewallDockerBackendKey).FirstOrCreate(&model.Setting{ - Key: constant.FirewallDockerBackendKey, Value: "", + Key: constant.FirewallDockerBackendKey, Value: constant.FirewallProviderIptables, }).Error; err != nil { return err } - result := tx.Model(&model.Setting{}). + result = tx.Model(&model.Setting{}). Where( "key = ? AND value NOT IN ?", constant.FirewallForwardingBackendKey, diff --git a/agent/init/migration/migrations/utils/firewall_transfer.go b/agent/init/migration/migrations/utils/firewall_transfer.go index 7981673bf..61460dd92 100644 --- a/agent/init/migration/migrations/utils/firewall_transfer.go +++ b/agent/init/migration/migrations/utils/firewall_transfer.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" "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" @@ -311,5 +312,27 @@ func cleanupLegacyFirewalldForwarding(rules []legacyFirewalldForward) error { if err := manager.Run("firewall-cmd", "--reload"); err != nil { return fmt.Errorf("reload firewalld after forwarding transfer: %w", err) } + return restartDockerAfterFirewalldReload(cmd.Which, controller.CheckActive, controller.HandleRestart) +} + +func restartDockerAfterFirewalldReload( + which func(string) bool, + checkActive func(string) (bool, error), + restart func(string) error, +) error { + const service = "docker" + if !which(service) { + return nil + } + active, err := checkActive(service) + if err != nil { + return fmt.Errorf("check Docker status after reloading firewalld: %w", err) + } + if !active { + return nil + } + if err := restart(service); err != nil { + return fmt.Errorf("restart Docker after reloading firewalld: %w", err) + } return nil } diff --git a/agent/init/migration/migrations/utils/host_firewall_transfer.go b/agent/init/migration/migrations/utils/host_firewall_transfer.go index f8b4a143f..4a7603844 100644 --- a/agent/init/migration/migrations/utils/host_firewall_transfer.go +++ b/agent/init/migration/migrations/utils/host_firewall_transfer.go @@ -34,9 +34,6 @@ type legacyHostFirewallRecord struct { Description string } -// TransferHostFirewall imports the legacy firewalls table into firewall_rules. -// It intentionally does not inspect or mutate the system firewall: the normal -// inventory merge associates imported rows with observed rules by RuleKey. func TransferHostFirewall(ctx context.Context, provider string) error { if global.DB == nil { return errors.New("host firewall transfer database is required") @@ -44,6 +41,47 @@ func TransferHostFirewall(ctx context.Context, provider string) error { return transferHostFirewall(ctx, global.DB, filter.Provider(strings.ToLower(strings.TrimSpace(provider)))) } +func TransferLegacyHostFirewallRuleOwnership(ctx context.Context, provider string, transfer func(context.Context) error) error { + if global.DB == nil { + return errors.New("host firewall transfer database is required") + } + return transferLegacyHostFirewallRuleOwnership( + ctx, + global.DB, + filter.Provider(strings.ToLower(strings.TrimSpace(provider))), + transfer, + ) +} + +func transferLegacyHostFirewallRuleOwnership( + ctx context.Context, + db *gorm.DB, + provider filter.Provider, + transfer func(context.Context) error, +) error { + if !legacyHostFirewallOwnershipProvider(provider) { + return nil + } + if db == nil { + return errors.New("host firewall transfer database is required") + } + completed, err := migrationRecordExists(db, hostFirewallTransferMigrationID) + if err != nil || completed { + return err + } + if transfer == nil { + return errors.New("legacy host firewall ownership transfer is required") + } + if err := transfer(ctx); err != nil { + return fmt.Errorf("transfer legacy host firewall rule ownership: %w", err) + } + return markMigrationRecord(db, hostFirewallTransferMigrationID) +} + +func legacyHostFirewallOwnershipProvider(provider filter.Provider) bool { + return provider == filter.ProviderIptables || provider == filter.ProviderUFW +} + func transferHostFirewall(ctx context.Context, db *gorm.DB, provider filter.Provider) error { if db == nil { return errors.New("host firewall transfer database is required") @@ -69,6 +107,9 @@ func transferHostFirewall(ctx context.Context, db *gorm.DB, provider filter.Prov if err := importLegacyHostFirewallRules(tx, models); err != nil { return err } + if legacyHostFirewallOwnershipProvider(provider) { + return nil + } return markMigrationRecord(tx, hostFirewallTransferMigrationID) }) } @@ -144,7 +185,7 @@ func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Pr rule.SourcePort = "" rule.DestinationPort = "" default: - if provider != filter.ProviderIptables { + if provider != filter.ProviderIptables || legacyIptablesAdvancedChain(record.Chain) { return nil, fmt.Errorf("%w: advanced rule for provider %q", errUnsupportedLegacyHostFirewallRule, provider) } } @@ -166,6 +207,15 @@ func legacyHostFirewallRules(record legacyHostFirewallRecord, provider filter.Pr return filter.ExpandAtomicRules(rule) } +func legacyIptablesAdvancedChain(chain string) bool { + switch strings.ToUpper(strings.TrimSpace(chain)) { + case "1PANEL_INPUT", "1PANEL_OUTPUT": + return true + default: + return false + } +} + func legacyIptablesChain(record legacyHostFirewallRecord) string { typeName := strings.ToLower(strings.TrimSpace(record.Type)) if typeName == "port" || typeName == "address" || typeName == "ip" { @@ -201,6 +251,9 @@ func legacyUFWHostRules(record legacyHostFirewallRecord, rule filter.FirewallRul if strings.EqualFold(strings.TrimSpace(record.Type), "address") || strings.EqualFold(strings.TrimSpace(record.Type), "ip") { rule.SourceAddress, rule.DestinationAddress = splitLegacyUFWAddress(rule.SourceAddress) } + if legacyUFWSinglePortAllProtocols(record, rule.DestinationPort) { + rule.Protocol = "all" + } if legacyAddressIsEmpty(rule.SourceAddress) && legacyAddressIsEmpty(rule.DestinationAddress) { rule.Scope.Family = filter.FamilyInet } else { @@ -209,6 +262,18 @@ func legacyUFWHostRules(record legacyHostFirewallRecord, rule filter.FirewallRul return filter.ExpandAtomicRules(rule) } +func legacyUFWSinglePortAllProtocols(record legacyHostFirewallRecord, port string) bool { + if !strings.EqualFold(strings.TrimSpace(record.Type), "port") { + return false + } + protocol := strings.ToLower(strings.TrimSpace(record.Protocol)) + if protocol != "tcp/udp" && protocol != "udp/tcp" { + return false + } + port = strings.TrimSpace(port) + return port != "" && !strings.Contains(port, ",") && !strings.Contains(port, "-") +} + func expandLegacyFamilies(rule filter.FirewallRule, families ...filter.Family) ([]filter.FirewallRule, error) { result := make([]filter.FirewallRule, 0, len(families)) for _, family := range families { diff --git a/agent/utils/firewall/filter/providers/ufw/adapter.go b/agent/utils/firewall/filter/providers/ufw/adapter.go index 452485fbe..25aad4d06 100644 --- a/agent/utils/firewall/filter/providers/ufw/adapter.go +++ b/agent/utils/firewall/filter/providers/ufw/adapter.go @@ -320,11 +320,14 @@ func compileChange(snapshot filter.Snapshot, change filter.DesiredChange) (filte position = *target.Locator.Position plan.Previous = &target plan.Expected = observedForRule(normalized, marker, position) - // 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))} + plan.Commands = []filter.NativeCommand{ + deletePositionCommand(position), + insertCommand(position, normalized, marker), + } + plan.RollbackCommands = []filter.NativeCommand{ + insertCommand(position, target.Rule, observedComment(target)), + deleteRuleCommand(normalized, marker), + } case filter.ChangeUpdate: target, targetErr := validateMutationTarget(snapshot, change, normalized, marker, true) if targetErr != nil { diff --git a/agent/utils/firewall/iptables_helper/legacy.go b/agent/utils/firewall/iptables_helper/legacy.go new file mode 100644 index 000000000..15c516fa5 --- /dev/null +++ b/agent/utils/firewall/iptables_helper/legacy.go @@ -0,0 +1,84 @@ +package iptables_helper + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle" + "github.com/mattn/go-shellwords" +) + +const ( + LegacyInputChain = "1PANEL_INPUT" + LegacyOutputChain = "1PANEL_OUTPUT" + legacyInputFileName = "1panel_input.rules" + legacyOutputFileName = "1panel_out.rules" +) + +func CleanupLegacyAdvancedChains(ctx context.Context) error { + commands, err := lifecycle.ResolveIptablesCommands() + if err != nil { + return err + } + output, err := RunWithStdContext(ctx, FilterTab, "-S") + if err != nil { + return fmt.Errorf("inspect legacy iptables advanced chains: %w", err) + } + if script := buildLegacyAdvancedChainCleanupScript(output); script != "" { + if err := restoreRules(commands.Restore4, script); err != nil { + return fmt.Errorf("remove legacy iptables advanced chains: %w", err) + } + } + for _, name := range []string{legacyInputFileName, legacyOutputFileName} { + file := filepath.Join(global.Dir.FirewallDir, name) + if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("remove legacy iptables rules file %s: %w", file, err) + } + } + return nil +} + +func buildLegacyAdvancedChainCleanupScript(output string) string { + legacyChains := map[string]struct{}{LegacyInputChain: {}, LegacyOutputChain: {}} + existing := make(map[string]bool, len(legacyChains)) + deletions := make([]string, 0) + for _, raw := range strings.Split(output, "\n") { + line := strings.TrimSpace(raw) + fields, err := shellwords.Parse(line) + if err != nil { + continue + } + if len(fields) == 2 && fields[0] == "-N" { + if _, legacy := legacyChains[fields[1]]; legacy { + existing[fields[1]] = true + } + continue + } + if len(fields) < 4 || fields[0] != "-A" { + continue + } + for index := 2; index+1 < len(fields); index++ { + if fields[index] != "-j" && fields[index] != "-g" { + continue + } + if _, legacy := legacyChains[fields[index+1]]; legacy { + deletions = append(deletions, strings.Replace(line, "-A ", "-D ", 1)) + } + break + } + } + for _, chain := range []string{LegacyInputChain, LegacyOutputChain} { + if existing[chain] { + deletions = append(deletions, "-F "+chain, "-X "+chain) + } + } + if len(deletions) == 0 { + return "" + } + return "*filter\n" + strings.Join(deletions, "\n") + "\nCOMMIT\n" +} diff --git a/frontend/src/api/interface/firewall.ts b/frontend/src/api/interface/firewall.ts index ddd302a10..6b22d1c5c 100644 --- a/frontend/src/api/interface/firewall.ts +++ b/frontend/src/api/interface/firewall.ts @@ -404,6 +404,8 @@ export namespace Firewall { effective: boolean; description?: string; trafficPath: 'forward' | 'input' | 'unknown'; + managementTarget?: 'container_guard' | 'host_firewall' | 'needs_diagnosis'; + managementReason?: 'nat_inspect_failed' | 'nat_chain_unreachable' | 'proxy_inspect_failed' | 'no_matching_path'; } export interface DockerGuardPortGroup { key: string; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d13d1770c..054f78e1a 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -4094,7 +4094,7 @@ const message = { scopeInactive: 'The managed scope is inactive. New rules may not affect current traffic.', scopeMissing: 'Managed scope {0} is missing and will be created safely when the first rule is applied.', scopeUnmanagedActive: 'Other active scopes were detected: {0}. 1Panel will not modify their rules.', - scopeRuntimeMismatch: 'The runtime and permanent configurations differ for: {0}.', + scopeRuntimeMismatch: 'The active and permanent firewalld configurations differ. Restart the firewall.', dockerRestart: 'Firewall operations require restarting the Docker service', firewallHelper: '{0} system firewall', firewallNotStart: 'The system firewall is not enabled at present. Enable it first.', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 1c65d7728..65d1e6aec 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -4137,7 +4137,8 @@ const message = { scopeInactive: 'El ámbito gestionado está inactivo. Las reglas nuevas podrían no afectar al tráfico actual.', scopeMissing: 'Falta el ámbito gestionado {0}; se creará de forma segura al aplicar la primera regla.', scopeUnmanagedActive: 'Se detectaron otros ámbitos activos: {0}. 1Panel no modificará sus reglas.', - scopeRuntimeMismatch: 'Las configuraciones activa y permanente difieren en: {0}.', + scopeRuntimeMismatch: + 'Las configuraciones activa y permanente de firewalld no coinciden. Reinicie el firewall.', dockerRestart: 'Las operaciones del firewall requieren reiniciar el servicio de Docker', firewallHelper: 'Firewall del sistema {0}', firewallNotStart: 'El firewall del sistema no está habilitado actualmente. Actívalo primero.', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 5178dc98b..c421bf7be 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -4049,7 +4049,7 @@ const message = { scopeInactive: 'محدودهٔ مدیریت‌شده فعال نیست. قوانین جدید ممکن است بر ترافیک فعلی اثر نگذارند.', scopeMissing: 'محدودهٔ مدیریت‌شدهٔ {0} وجود ندارد و هنگام اعمال نخستین قانون به‌صورت امن ایجاد می‌شود.', scopeUnmanagedActive: 'محدوده‌های فعال دیگری یافت شد: {0}. ‏1Panel قوانین آن‌ها را تغییر نمی‌دهد.', - scopeRuntimeMismatch: 'پیکربندی فعال و دائمی در این موارد متفاوت است: {0}.', + scopeRuntimeMismatch: 'پیکربندی‌های فعال و دائمی firewalld متفاوت‌اند. دیواره آتش را مجدداً راه‌اندازی کنید.', dockerRestart: 'عملیات دیواره آتش نیاز به راه‌اندازی مجدد سرویس Docker دارد', firewallHelper: 'دیواره آتش سیستم {0}', firewallNotStart: 'دیواره آتش سیستم در حال حاضر فعال نیست. ابتدا آن را فعال کنید.', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 980dd8248..2237e89b1 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -4075,7 +4075,8 @@ const message = { scopeInactive: '管理対象の範囲が有効ではありません。新しいルールが現在の通信に適用されない場合があります。', scopeMissing: '管理対象スコープ {0} がありません。最初のルール適用時に安全に作成されます。', scopeUnmanagedActive: '他の有効な範囲が見つかりました:{0}。1Panel はそのルールを変更しません。', - scopeRuntimeMismatch: '実行中と永続設定で次の項目が一致しません:{0}。', + scopeRuntimeMismatch: + 'firewalld の実行中の設定と永続設定が一致しません。ファイアウォールを再起動してください。', dockerRestart: 'ファイアウォール操作にはDockerサービスの再起動が必要です', firewallHelper: '{0}システムファイアウォール', firewallNotStart: '現在、システムファイアウォールは有効になっていません。最初に有効にします。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index e336e45db..996087f9a 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -4002,7 +4002,7 @@ const message = { scopeInactive: '관리 범위가 비활성 상태입니다. 새 규칙이 현재 트래픽에 적용되지 않을 수 있습니다.', scopeMissing: '관리 범위 {0}이(가) 없으며 첫 규칙을 적용할 때 안전하게 생성됩니다.', scopeUnmanagedActive: '다른 활성 범위가 감지되었습니다: {0}. 1Panel은 해당 규칙을 변경하지 않습니다.', - scopeRuntimeMismatch: '실행 중 구성과 영구 구성의 다음 항목이 다릅니다: {0}.', + scopeRuntimeMismatch: 'firewalld의 실행 중 구성과 영구 구성이 다릅니다. 방화벽을 재시작하세요.', dockerRestart: '방화벽 작업에는 Docker 서비스 재시작이 필요합니다', firewallHelper: '{0} 시스템 방화벽', firewallNotStart: '현재 시스템 방화벽이 활성화되지 않았습니다. 먼저 활성화하세요.', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 37ebf5671..cdd574f2b 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -3971,7 +3971,7 @@ const message = { scopeInactive: 'ຂອບເຂດທີ່ຈັດການບໍ່ໄດ້ເປີດໃຊ້. ກົດໃໝ່ອາດບໍ່ມີຜົນຕໍ່ການຈະລາຈອນປັດຈຸບັນ.', scopeMissing: 'ບໍ່ພົບຂອບເຂດຈັດການ {0}; ລະບົບຈະສ້າງຢ່າງປອດໄພເມື່ອນຳໃຊ້ກົດທຳອິດ.', scopeUnmanagedActive: 'ພົບຂອບເຂດອື່ນທີ່ໃຊ້ງານ: {0}. 1Panel ຈະບໍ່ແກ້ໄຂກົດເຫຼົ່ານັ້ນ.', - scopeRuntimeMismatch: 'ຄ່າທີ່ໃຊ້ງານແລະຄ່າຖາວອນບໍ່ກົງກັນໃນ: {0}.', + scopeRuntimeMismatch: 'ການຕັ້ງຄ່າ firewalld ທີ່ກຳລັງໃຊ້ງານ ແລະ ແບບຖາວອນບໍ່ກົງກັນ. ກະລຸນາເລີ່ມຕົ້ນໄຟວໍໃໝ່.', dockerRestart: 'ການຈັດການໄຟວໍຕ້ອງມີການເລີ່ມຕົ້ນບໍລິການ Docker ໃໝ່', firewallHelper: 'ໄຟວໍລະບົບ {0}', firewallNotStart: 'ໄຟວໍລະບົບຍັງບໍ່ໄດ້ເປີດໃຊ້. ກະລຸນາເປີດກ່ອນ.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 420ad98d0..9d84579ff 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -4158,7 +4158,7 @@ const message = { scopeInactive: 'Skop terurus tidak aktif. Peraturan baharu mungkin tidak mempengaruhi trafik semasa.', scopeMissing: 'Skop terurus {0} tiada dan akan dicipta dengan selamat apabila peraturan pertama digunakan.', scopeUnmanagedActive: 'Skop aktif lain dikesan: {0}. 1Panel tidak akan mengubah peraturannya.', - scopeRuntimeMismatch: 'Konfigurasi aktif dan kekal berbeza bagi: {0}.', + scopeRuntimeMismatch: 'Konfigurasi firewalld aktif dan kekal tidak sepadan. Mulakan semula firewall.', dockerRestart: 'Operasi firewall memerlukan memulakan semula perkhidmatan Docker', firewallHelper: '{0} firewall sistem', firewallNotStart: 'Firewall sistem belum diaktifkan. Aktifkannya dahulu.', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index e231b4969..eae606cb3 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -4178,7 +4178,7 @@ const message = { scopeInactive: 'O escopo gerenciado está inativo. Novas regras podem não afetar o tráfego atual.', scopeMissing: 'O escopo gerenciado {0} não existe e será criado com segurança ao aplicar a primeira regra.', scopeUnmanagedActive: 'Outros escopos ativos foram detectados: {0}. O 1Panel não modificará suas regras.', - scopeRuntimeMismatch: 'As configurações ativa e permanente diferem em: {0}.', + scopeRuntimeMismatch: 'As configurações ativa e permanente do firewalld não coincidem. Reinicie o firewall.', dockerRestart: 'Operações de firewall exigem reinicialização do serviço Docker', firewallHelper: 'Firewall do sistema {0}', firewallNotStart: 'O firewall do sistema não está habilitado atualmente. Habilite-o primeiro.', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index d4ecd281a..4491acb05 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -4145,7 +4145,7 @@ const message = { scopeInactive: 'Управляемая область не активна. Новые правила могут не влиять на текущий трафик.', scopeMissing: 'Управляемая область {0} отсутствует и будет безопасно создана при применении первого правила.', scopeUnmanagedActive: 'Обнаружены другие активные области: {0}. 1Panel не будет изменять их правила.', - scopeRuntimeMismatch: 'Активная и постоянная конфигурации различаются в следующих элементах: {0}.', + scopeRuntimeMismatch: 'Текущая и постоянная конфигурации firewalld не совпадают. Перезапустите брандмауэр.', dockerRestart: 'Операции с брандмауэром требуют перезапуска службы Docker', firewallHelper: '{0} межсетевой экран', firewallNotStart: 'Межсетевой экран в настоящее время не включен. Сначала включите его.', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 537a4eb5b..2d1eb9d82 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -4158,7 +4158,8 @@ const message = { scopeInactive: 'Yönetilen kapsam etkin değil. Yeni kurallar mevcut trafiği etkilemeyebilir.', scopeMissing: 'Yönetilen {0} kapsamı eksik ve ilk kural uygulanırken güvenli şekilde oluşturulacak.', scopeUnmanagedActive: 'Başka etkin kapsamlar algılandı: {0}. 1Panel bunların kurallarını değiştirmez.', - scopeRuntimeMismatch: 'Etkin ve kalıcı yapılandırmalar şu öğelerde farklı: {0}.', + scopeRuntimeMismatch: + 'Etkin ve kalıcı firewalld yapılandırmaları eşleşmiyor. Güvenlik duvarını yeniden başlatın.', dockerRestart: 'Güvenlik duvarı işlemleri Docker hizmetinin yeniden başlatılmasını gerektirir', firewallHelper: '{0} sistem güvenlik duvarı', firewallNotStart: 'Sistem güvenlik duvarı şu anda etkin değil. Önce etkinleştirin.', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 6c6876e4e..d1f9c908d 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -3821,7 +3821,7 @@ const message = { scopeInactive: '目前託管範圍未啟用,新增規則可能不會作用於現有流量。', scopeMissing: '託管範圍 {0} 尚未建立,套用第一條規則時將以安全方式建立。', scopeUnmanagedActive: '偵測到其他使用中範圍:{0},1Panel 不會修改其中的規則。', - scopeRuntimeMismatch: '執行設定與永久設定在下列項目不一致:{0}。', + scopeRuntimeMismatch: 'firewalld 執行中設定與永久設定不一致,請重新啟動防火牆。', dockerRestart: '防火牆操作需要重新啟動 Docker 服務', firewallHelper: '{0}系統防火牆', firewallNotStart: '尚未啟用系統防火牆,請先啟用', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0de0f5432..eade75a92 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -3869,7 +3869,7 @@ const message = { scopeInactive: '当前托管范围未激活,新增规则可能不会作用于现有流量。', scopeMissing: '托管范围 {0} 尚未创建,应用首条规则时将以安全方式创建。', scopeUnmanagedActive: '检测到其他活动范围:{0},1Panel 不会修改其中的规则。', - scopeRuntimeMismatch: '运行配置与永久配置在以下项目中不一致:{0}。', + scopeRuntimeMismatch: 'firewalld 运行配置与永久配置不一致,请重启防火墙。', dockerRestart: '防火墙操作需要重启 Docker 服务', firewallHelper: '{0}系统防火墙', firewallNotStart: '当前未开启系统防火墙,请先开启!', diff --git a/frontend/src/views/host/firewall/docker/detail/index.vue b/frontend/src/views/host/firewall/docker/detail/index.vue index 9c36d08a9..fcfb7632e 100644 --- a/frontend/src/views/host/firewall/docker/detail/index.vue +++ b/frontend/src/views/host/firewall/docker/detail/index.vue @@ -16,6 +16,8 @@ {{ $t('commons.button.selectAll') }}
- + {{ $t('commons.button.set') }} (); @@ -279,17 +294,17 @@ const toggleSelection = (key: string) => { }; const openPolicy = (endpoints: Firewall.DockerGuardEndpoint[]) => { if (!endpoints.length) return; - const paths = new Set(endpoints.map((endpoint) => endpoint.trafficPath || 'unknown')); - if (paths.size !== 1) { + const targets = new Set(endpoints.map(dockerGuardManagementTarget)); + if (targets.size !== 1) { MsgWarning(i18n.global.t('firewall.dockerTrafficPathMixed')); return; } - const path = [...paths][0]; - if (path === 'input') { + const target = [...targets][0]; + if (target === 'host_firewall') { MsgWarning(i18n.global.t('firewall.dockerInputUseHostFirewall')); return; } - if (path !== 'forward') { + if (target !== 'container_guard') { MsgWarning(i18n.global.t('firewall.dockerTrafficPathUnknown')); return; } @@ -379,8 +394,9 @@ const portMappingLabel = (row: Firewall.DockerGuardPortGroup) => { return `${publishedEndpoint} → ${value}/${row.endpoint.protocol}`; }; const protectionSummary = (row: Firewall.DockerGuardEndpoint) => { - if (row.trafficPath === 'input') return i18n.global.t('firewall.dockerInputUseHostFirewall'); - if (row.trafficPath === 'unknown') return i18n.global.t('firewall.dockerTrafficPathPending'); + const target = dockerGuardManagementTarget(row); + if (target === 'host_firewall') return i18n.global.t('firewall.dockerInputUseHostFirewall'); + if (target === 'needs_diagnosis') return i18n.global.t('firewall.dockerTrafficPathUnknown'); if (!row.policyUUID) return i18n.global.t('firewall.dockerGuardUnprotected'); let summary = i18n.global.t('firewall.denyAll'); if (row.mode === 'deny_sources') { diff --git a/frontend/src/views/host/firewall/docker/import/index.vue b/frontend/src/views/host/firewall/docker/import/index.vue index 974f5a185..c165860ed 100644 --- a/frontend/src/views/host/firewall/docker/import/index.vue +++ b/frontend/src/views/host/firewall/docker/import/index.vue @@ -1,21 +1,36 @@