From a71aea8aec6a368d748a9f337c33cb7c50909e8f Mon Sep 17 00:00:00 2001 From: ssongliu Date: Fri, 4 Sep 2026 21:43:05 +0800 Subject: [PATCH] fix(firewall): hide inactive Docker ports (#13716) --- agent/app/dto/firewall.go | 1 + agent/app/service/firewall_docker.go | 12 +++++----- .../firewall/filter/providers/ufw/adapter.go | 15 +++++++++--- frontend/src/api/interface/firewall.ts | 1 + frontend/src/lang/modules/en.ts | 10 ++++++++ frontend/src/lang/modules/es-es.ts | 10 ++++++++ frontend/src/lang/modules/fa.ts | 10 ++++++++ frontend/src/lang/modules/ja.ts | 10 ++++++++ frontend/src/lang/modules/ko.ts | 10 ++++++++ frontend/src/lang/modules/lo.ts | 9 ++++++++ frontend/src/lang/modules/ms.ts | 10 ++++++++ frontend/src/lang/modules/pt-br.ts | 10 ++++++++ frontend/src/lang/modules/ru.ts | 10 ++++++++ frontend/src/lang/modules/tr.ts | 10 ++++++++ frontend/src/lang/modules/zh-Hant.ts | 8 +++++++ frontend/src/lang/modules/zh.ts | 8 +++++++ .../host/firewall/docker/detail/index.vue | 20 ++++++++++------ .../src/views/host/firewall/docker/index.vue | 12 ++++++---- .../src/views/host/firewall/docker/model.ts | 12 +++++++++- .../src/views/host/firewall/rule/index.vue | 23 ++++++++++++------- 20 files changed, 181 insertions(+), 30 deletions(-) diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go index 4c2d5846d..6ffe7e2e3 100644 --- a/agent/app/dto/firewall.go +++ b/agent/app/dto/firewall.go @@ -165,6 +165,7 @@ type DockerPortGuardEndpoint struct { Protocol string `json:"protocol"` ContainerID string `json:"containerID"` ContainerName string `json:"containerName"` + ContainerState string `json:"containerState,omitempty"` ContainerPort uint16 `json:"containerPort"` Compose string `json:"compose,omitempty"` Application string `json:"application,omitempty"` diff --git a/agent/app/service/firewall_docker.go b/agent/app/service/firewall_docker.go index ba888f374..89a9c1695 100644 --- a/agent/app/service/firewall_docker.go +++ b/agent/app/service/firewall_docker.go @@ -125,7 +125,7 @@ func (s *DockerPortGuardService) LoadPublishedPorts(ctx context.Context) ([]dto. } defer cli.Close() - endpoints, err := discoverDockerEndpoints(ctx, cli) + endpoints, err := discoverDockerEndpoints(ctx, cli, false) if err != nil { return nil, err } @@ -163,7 +163,7 @@ func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPo if reconcileErr := lastDockerPortGuardReconcileError(); reconcileErr != nil { markDockerGuardReconcileFailure(&base, reconcileErr) } - endpoints, err := discoverDockerEndpoints(ctx, cli) + endpoints, err := discoverDockerEndpoints(ctx, cli, true) if err != nil { return dto.DockerPortGuardList{}, err } @@ -378,7 +378,7 @@ func (s *DockerPortGuardService) rejectHostInputDockerGuardEndpoints( if err != nil { return nil } - endpoints, err := discoverDockerEndpoints(ctx, cli) + endpoints, err := discoverDockerEndpoints(ctx, cli, true) if err != nil { return nil } @@ -739,8 +739,8 @@ func dockerFirewallVersion(backend string) string { return version } -func discoverDockerEndpoints(ctx context.Context, cli *client.Client) ([]dto.DockerPortGuardEndpoint, error) { - containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true}) +func discoverDockerEndpoints(ctx context.Context, cli *client.Client, all bool) ([]dto.DockerPortGuardEndpoint, error) { + containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: all}) if err != nil { return nil, err } @@ -763,7 +763,7 @@ func discoverDockerEndpoints(ctx context.Context, cli *client.Client) ([]dto.Doc } else if hostIP == "" { hostIP = "0.0.0.0" } - endpoints = append(endpoints, dto.DockerPortGuardEndpoint{Family: family, HostIP: hostIP, HostPort: port.PublicPort, Protocol: port.Type, ContainerID: item.ID, ContainerName: name, ContainerPort: port.PrivatePort, Compose: compose, Application: application, Sources: []string{}}) + endpoints = append(endpoints, dto.DockerPortGuardEndpoint{Family: family, HostIP: hostIP, HostPort: port.PublicPort, Protocol: port.Type, ContainerID: item.ID, ContainerName: name, ContainerState: item.State, ContainerPort: port.PrivatePort, Compose: compose, Application: application, Sources: []string{}}) } } return endpoints, nil diff --git a/agent/utils/firewall/filter/providers/ufw/adapter.go b/agent/utils/firewall/filter/providers/ufw/adapter.go index c6e1036b4..ca92a9d84 100644 --- a/agent/utils/firewall/filter/providers/ufw/adapter.go +++ b/agent/utils/firewall/filter/providers/ufw/adapter.go @@ -235,7 +235,10 @@ func (a *Adapter) failedCommandApplied(ctx context.Context, plan filter.NativeRu case filter.ChangeCreate: return markerCount > 0 case filter.ChangeAdopt: - return plan.Previous == nil || !containsObservedRule(snapshot, *plan.Previous) + if commandIndex == 0 { + return plan.Previous == nil || !containsObservedRule(snapshot, *plan.Previous) + } + return markerCount > 0 case filter.ChangeUpdate: if commandIndex == 0 { return markerCount == 0 @@ -342,14 +345,20 @@ func compileChange(snapshot filter.Snapshot, change filter.DesiredChange) (filte return filter.NativeRulePlan{}, targetErr } position = *target.Locator.Position + appendAtEnd := position == maximumObservedPosition(snapshot) + restoreAtEnd := change.RestoreAtEnd || appendAtEnd plan.Previous = &target plan.Expected = observedForRule(normalized, marker, position) + if appendAtEnd { + plan.Expected.Locator.NativeID = "" + plan.Expected.Locator.Position = nil + } plan.Commands = []filter.NativeCommand{ deletePositionCommand(position), - insertCommand(position, normalized, marker), + positionedCommand(position, normalized, marker, appendAtEnd), } plan.RollbackCommands = []filter.NativeCommand{ - insertCommand(position, target.Rule, observedComment(target)), + positionedCommand(position, target.Rule, observedComment(target), restoreAtEnd), deleteRuleCommand(normalized, marker), } case filter.ChangeUpdate: diff --git a/frontend/src/api/interface/firewall.ts b/frontend/src/api/interface/firewall.ts index 26eebcb56..bac147114 100644 --- a/frontend/src/api/interface/firewall.ts +++ b/frontend/src/api/interface/firewall.ts @@ -395,6 +395,7 @@ export namespace Firewall { protocol: 'tcp' | 'udp'; containerID?: string; containerName?: string; + containerState?: 'created' | 'running' | 'paused' | 'restarting' | 'removing' | 'exited' | 'dead'; containerPort?: number; compose?: string; application?: string; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 5acd9376f..6dcc651d0 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -4194,6 +4194,16 @@ const message = { dockerTrafficPathMixed: 'The selected ports use different access paths. Configure them separately.', dockerTrafficPathUnknown: 'The access path for this port could not be determined. Check the Docker network configuration and try again.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Docker NAT rules could not be read. Check the firewall commands and permissions, then refresh.', + proxy_inspect_failed: + 'docker-proxy processes could not be inspected. Check access to system process information, then refresh.', + nat_chain_unreachable: + 'A Docker forwarding rule exists for this port, but the NAT ingress chain is inactive. Check the Docker firewall rules or restart Docker, then refresh.', + no_matching_path: + 'No active Docker forwarding rule or proxy process was found for this port. Start or restart the container, then refresh. If the issue persists, check the Docker network configuration.', + }, dockerTrafficPathPending: 'Access path pending', dockerInputPolicyNotEffective: 'The host receives this port directly, so the existing container port protection rule does not apply. Configure it in the host firewall instead.', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 88a4fabd3..a635f4b56 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -4242,6 +4242,16 @@ const message = { dockerTrafficPathMixed: 'Los puertos seleccionados usan rutas de acceso diferentes. Configúralos por separado.', dockerTrafficPathUnknown: 'No se pudo determinar la ruta de acceso de este puerto. Comprueba la red de Docker e inténtalo de nuevo.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'No se pudieron leer las reglas NAT de Docker. Comprueba los comandos y permisos del firewall y actualiza la página.', + proxy_inspect_failed: + 'No se pudieron inspeccionar los procesos docker-proxy. Comprueba el acceso a la información de procesos y actualiza la página.', + nat_chain_unreachable: + 'Existe una regla de reenvío de Docker para este puerto, pero la cadena de entrada NAT no está activa. Comprueba las reglas del firewall de Docker o reinicia Docker y actualiza la página.', + no_matching_path: + 'No se encontró una regla de reenvío de Docker activa ni un proceso proxy para este puerto. Inicia o reinicia el contenedor y actualiza la página. Si continúa, comprueba la red de Docker.', + }, dockerTrafficPathPending: 'Ruta de acceso pendiente', dockerInputPolicyNotEffective: 'El host recibe este puerto directamente, por lo que la regla de protección del puerto del contenedor no se aplica. Configúralo en el firewall del host.', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 8b009d93e..1d5715e7a 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -4152,6 +4152,16 @@ const message = { 'پورت‌های انتخاب‌شده از مسیرهای دسترسی متفاوت استفاده می‌کنند. آن‌ها را جداگانه تنظیم کنید.', dockerTrafficPathUnknown: 'مسیر دسترسی این پورت قابل تشخیص نیست. تنظیمات شبکه Docker را بررسی کرده و دوباره تلاش کنید.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'خواندن قوانین NAT داکر ممکن نبود. فرمان‌ها و مجوزهای فایروال را بررسی و سپس صفحه را تازه‌سازی کنید.', + proxy_inspect_failed: + 'بررسی پردازش‌های docker-proxy ممکن نبود. دسترسی به اطلاعات پردازش‌های سیستم را بررسی و سپس صفحه را تازه‌سازی کنید.', + nat_chain_unreachable: + 'قانون هدایت داکر برای این پورت وجود دارد، اما زنجیره ورودی NAT فعال نیست. قوانین فایروال داکر را بررسی یا داکر را راه‌اندازی مجدد کرده و صفحه را تازه‌سازی کنید.', + no_matching_path: + 'برای این پورت قانون هدایت فعال داکر یا پردازش پراکسی یافت نشد. کانتینر را راه‌اندازی یا مجدداً راه‌اندازی کرده و صفحه را تازه‌سازی کنید. اگر مشکل ادامه داشت، شبکه داکر را بررسی کنید.', + }, dockerTrafficPathPending: 'مسیر دسترسی نامشخص', dockerInputPolicyNotEffective: 'این پورت مستقیماً توسط میزبان دریافت می‌شود، بنابراین قانون فعلی محافظت پورت کانتینر اعمال نمی‌شود. آن را در فایروال میزبان تنظیم کنید.', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index d959227f3..977ff31c6 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -4179,6 +4179,16 @@ const message = { dockerTrafficPathMixed: '選択したポートは異なるアクセス経路を使用しています。個別に設定してください。', dockerTrafficPathUnknown: 'このポートのアクセス経路を確認できません。Docker ネットワーク設定を確認して再試行してください。', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Docker の NAT ルールを読み取れません。ファイアウォールコマンドと実行権限を確認してから更新してください。', + proxy_inspect_failed: + 'docker-proxy プロセスを確認できません。システムのプロセス情報を読み取れることを確認してから更新してください。', + nat_chain_unreachable: + 'このポートの Docker 転送ルールは見つかりましたが、NAT 入口チェーンが有効ではありません。Docker のファイアウォールルールを確認するか Docker を再起動してから更新してください。', + no_matching_path: + 'このポートに有効な Docker 転送ルールまたはプロキシプロセスが見つかりません。対象のコンテナを起動または再起動してから更新してください。解決しない場合は Docker ネットワーク設定を確認してください。', + }, dockerTrafficPathPending: 'アクセス経路を確認中', dockerInputPolicyNotEffective: 'このポートはホストが直接受信するため、既存のコンテナポート保護ルールは適用されません。ホストファイアウォールで設定してください。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 309ca3f72..64a0ab9ca 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -4102,6 +4102,16 @@ const message = { dockerTrafficPathMixed: '선택한 포트가 서로 다른 접근 경로를 사용합니다. 각각 설정하세요.', dockerTrafficPathUnknown: '이 포트의 접근 경로를 확인할 수 없습니다. Docker 네트워크 설정을 확인한 후 다시 시도하세요.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Docker NAT 규칙을 읽을 수 없습니다. 방화벽 명령과 실행 권한을 확인한 후 새로 고침하세요.', + proxy_inspect_failed: + 'docker-proxy 프로세스를 확인할 수 없습니다. 시스템 프로세스 정보에 대한 접근 권한을 확인한 후 새로 고침하세요.', + nat_chain_unreachable: + '이 포트의 Docker 전달 규칙은 있지만 NAT 진입 체인이 활성화되지 않았습니다. Docker 방화벽 규칙을 확인하거나 Docker를 다시 시작한 후 새로 고침하세요.', + no_matching_path: + '이 포트에 활성 Docker 전달 규칙이나 프록시 프로세스가 없습니다. 해당 컨테이너를 시작하거나 다시 시작한 후 새로 고침하세요. 문제가 계속되면 Docker 네트워크 설정을 확인하세요.', + }, dockerTrafficPathPending: '접근 경로 확인 대기', dockerInputPolicyNotEffective: '이 포트는 호스트가 직접 수신하므로 기존 컨테이너 포트 보호 규칙이 적용되지 않습니다. 호스트 방화벽에서 설정하세요.', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 069dddb45..39354303f 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -4069,6 +4069,15 @@ const message = { 'ຕັ້ງຄ່າຂໍ້ຈຳກັດການເຂົ້າເຖິງສຳລັບພອດທີ່ Docker ເຜີຍແຜ່ໃນໂຮສ. ພອດທີ່ບໍ່ໄດ້ປ້ອງກັນຈະໃຊ້ຮູບແບບເຂົ້າເຖິງເລີ່ມຕົ້ນຂອງ Docker.', dockerTrafficPathMixed: 'ພອດທີ່ເລືອກໃຊ້ເສັ້ນທາງເຂົ້າເຖິງຕ່າງກັນ. ກະລຸນາຕັ້ງຄ່າແຍກກັນ.', dockerTrafficPathUnknown: 'ບໍ່ສາມາດກຳນົດເສັ້ນທາງເຂົ້າເຖິງຂອງພອດນີ້ໄດ້. ກວດສອບເຄືອຂ່າຍ Docker ແລ້ວລອງໃໝ່.', + dockerTrafficPathReason: { + nat_inspect_failed: 'ບໍ່ສາມາດອ່ານກົດ NAT ຂອງ Docker ໄດ້. ກວດສອບຄຳສັ່ງແລະສິດຂອງໄຟວໍ ແລ້ວໂຫຼດໃໝ່.', + proxy_inspect_failed: + 'ບໍ່ສາມາດກວດສອບໂປຣເຊສ docker-proxy ໄດ້. ກວດສອບສິດເຂົ້າເຖິງຂໍ້ມູນໂປຣເຊສລະບົບ ແລ້ວໂຫຼດໃໝ່.', + nat_chain_unreachable: + 'ພົບກົດສົ່ງຕໍ່ Docker ສຳລັບພອດນີ້ ແຕ່ NAT ingress chain ບໍ່ເຮັດວຽກ. ກວດສອບກົດໄຟວໍ Docker ຫຼືເລີ່ມ Docker ໃໝ່ ແລ້ວໂຫຼດໃໝ່.', + no_matching_path: + 'ບໍ່ພົບກົດສົ່ງຕໍ່ Docker ຫຼືໂປຣເຊສ proxy ທີ່ເຮັດວຽກສຳລັບພອດນີ້. ເລີ່ມ ຫຼືເລີ່ມຄອນເທນເນີໃໝ່ ແລ້ວໂຫຼດໃໝ່. ຖ້າຍັງມີບັນຫາ ໃຫ້ກວດສອບເຄືອຂ່າຍ Docker.', + }, dockerTrafficPathPending: 'ລໍຖ້າກວດສອບເສັ້ນທາງ', dockerInputPolicyNotEffective: 'ພອດນີ້ຖືກຮັບໂດຍໂຮສໂດຍກົງ ດັ່ງນັ້ນກົດປ້ອງກັນພອດຄອນເທນເນີຈຶ່ງບໍ່ມີຜົນ. ກະລຸນາຕັ້ງຄ່າໃນໄຟວໍຂອງໂຮສ.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 30c2d2f2a..ec8e9fec1 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -4261,6 +4261,16 @@ const message = { 'Tetapkan sekatan akses untuk port yang diterbitkan oleh bekas Docker pada hos. Port tanpa perlindungan mengekalkan tingkah laku akses lalai Docker.', dockerTrafficPathMixed: 'Port yang dipilih menggunakan laluan akses berbeza. Tetapkannya secara berasingan.', dockerTrafficPathUnknown: 'Laluan akses port ini tidak dapat ditentukan. Semak rangkaian Docker dan cuba lagi.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Peraturan NAT Docker tidak dapat dibaca. Semak arahan dan kebenaran tembok api, kemudian muat semula.', + proxy_inspect_failed: + 'Proses docker-proxy tidak dapat diperiksa. Semak akses kepada maklumat proses sistem, kemudian muat semula.', + nat_chain_unreachable: + 'Peraturan pemajuan Docker wujud untuk port ini, tetapi rantaian masuk NAT tidak aktif. Semak peraturan tembok api Docker atau mulakan semula Docker, kemudian muat semula.', + no_matching_path: + 'Tiada peraturan pemajuan Docker aktif atau proses proksi ditemui untuk port ini. Mulakan atau mulakan semula bekas, kemudian muat semula. Jika masalah berterusan, semak konfigurasi rangkaian Docker.', + }, dockerTrafficPathPending: 'Laluan akses belum ditentukan', dockerInputPolicyNotEffective: 'Port ini diterima terus oleh hos, jadi peraturan perlindungan port bekas sedia ada tidak digunakan. Tetapkannya dalam firewall hos.', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 8299e6447..fd2d73247 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -4282,6 +4282,16 @@ const message = { 'As portas selecionadas usam caminhos de acesso diferentes. Configure-as separadamente.', dockerTrafficPathUnknown: 'Não foi possível determinar o caminho de acesso desta porta. Verifique a rede do Docker e tente novamente.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Não foi possível ler as regras NAT do Docker. Verifique os comandos e as permissões do firewall e atualize a página.', + proxy_inspect_failed: + 'Não foi possível inspecionar os processos docker-proxy. Verifique o acesso às informações dos processos e atualize a página.', + nat_chain_unreachable: + 'Existe uma regra de encaminhamento do Docker para esta porta, mas a cadeia de entrada NAT não está ativa. Verifique as regras do firewall do Docker ou reinicie o Docker e atualize a página.', + no_matching_path: + 'Nenhuma regra de encaminhamento do Docker ativa ou processo proxy foi encontrado para esta porta. Inicie ou reinicie o contêiner e atualize a página. Se o problema continuar, verifique a rede do Docker.', + }, dockerTrafficPathPending: 'Caminho de acesso pendente', dockerInputPolicyNotEffective: 'O host recebe esta porta diretamente, portanto a regra de proteção da porta do contêiner não se aplica. Configure-a no firewall do host.', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index f3ce4a0ed..b2fa1c6f4 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -4248,6 +4248,16 @@ const message = { dockerTrafficPathMixed: 'Выбранные порты используют разные пути доступа. Настройте их отдельно.', dockerTrafficPathUnknown: 'Не удалось определить путь доступа к этому порту. Проверьте сеть Docker и повторите попытку.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Не удалось прочитать правила NAT Docker. Проверьте команды и права межсетевого экрана, затем обновите страницу.', + proxy_inspect_failed: + 'Не удалось проверить процессы docker-proxy. Проверьте доступ к информации о системных процессах, затем обновите страницу.', + nat_chain_unreachable: + 'Правило перенаправления Docker для этого порта найдено, но входная цепочка NAT не активна. Проверьте правила Docker или перезапустите Docker, затем обновите страницу.', + no_matching_path: + 'Для этого порта не найдено активное правило перенаправления Docker или процесс прокси. Запустите или перезапустите контейнер, затем обновите страницу. Если проблема сохраняется, проверьте сеть Docker.', + }, dockerTrafficPathPending: 'Путь доступа не определён', dockerInputPolicyNotEffective: 'Хост принимает этот порт напрямую, поэтому существующее правило защиты порта контейнера не применяется. Настройте его в межсетевом экране хоста.', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index cff6b9359..ef8656b68 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -4263,6 +4263,16 @@ const message = { dockerTrafficPathMixed: 'Seçilen portlar farklı erişim yolları kullanıyor. Bunları ayrı ayrı yapılandırın.', dockerTrafficPathUnknown: 'Bu portun erişim yolu belirlenemedi. Docker ağ yapılandırmasını kontrol edip tekrar deneyin.', + dockerTrafficPathReason: { + nat_inspect_failed: + 'Docker NAT kuralları okunamadı. Güvenlik duvarı komutlarını ve izinlerini kontrol edip sayfayı yenileyin.', + proxy_inspect_failed: + 'docker-proxy işlemleri incelenemedi. Sistem işlem bilgilerine erişimi kontrol edip sayfayı yenileyin.', + nat_chain_unreachable: + 'Bu port için bir Docker yönlendirme kuralı var ancak NAT giriş zinciri etkin değil. Docker güvenlik duvarı kurallarını kontrol edin veya Docker’ı yeniden başlatıp sayfayı yenileyin.', + no_matching_path: + 'Bu port için etkin bir Docker yönlendirme kuralı veya proxy işlemi bulunamadı. Konteyneri başlatın ya da yeniden başlatıp sayfayı yenileyin. Sorun sürerse Docker ağ yapılandırmasını kontrol edin.', + }, dockerTrafficPathPending: 'Erişim yolu bekleniyor', dockerInputPolicyNotEffective: 'Bu port doğrudan ana makine tarafından alındığından mevcut konteyner port koruma kuralı uygulanmaz. Ana makine güvenlik duvarında yapılandırın.', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 7262b2c53..335a306df 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -3919,6 +3919,14 @@ const message = { dockerGuardHelper: '為 Docker 容器發佈到主機的連接埠設定存取限制;未設定防護的連接埠維持 Docker 預設存取方式。', dockerTrafficPathMixed: '所選連接埠使用不同的存取方式,請分別設定。', dockerTrafficPathUnknown: '暫時無法確認此連接埠的存取方式,請檢查 Docker 網路設定後重試。', + dockerTrafficPathReason: { + nat_inspect_failed: '無法讀取 Docker NAT 規則,請檢查防火牆命令及執行權限後重新整理。', + proxy_inspect_failed: '無法檢查 docker-proxy 程序,請確認可讀取系統程序資訊後重新整理。', + nat_chain_unreachable: + '已找到此連接埠的 Docker 轉送規則,但 NAT 入口鏈未生效,請檢查 Docker 防火牆規則或重新啟動 Docker 後重新整理。', + no_matching_path: + '未找到此連接埠生效的 Docker 轉送規則或代理程序,請啟動或重新啟動對應容器後重新整理;若仍異常,請檢查 Docker 網路設定。', + }, dockerTrafficPathPending: '存取方式待確認', dockerInputPolicyNotEffective: '此連接埠由主機直接接收,現有容器連接埠防護規則不會生效,請改用主機防火牆設定。', dockerInputUseHostFirewall: '此連接埠需要透過主機防火牆設定存取規則,請前往主機防火牆進行設定。', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index d170513d7..64fbbb88d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -3968,6 +3968,14 @@ const message = { dockerGuardHelper: '为 Docker 容器发布到宿主机的端口配置访问限制;未设置防护的端口保持 Docker 默认访问方式。', dockerTrafficPathMixed: '所选端口使用不同的访问方式,请分别设置。', dockerTrafficPathUnknown: '暂时无法确认该端口的访问方式,请检查 Docker 网络配置后重试。', + dockerTrafficPathReason: { + nat_inspect_failed: '无法读取 Docker NAT 规则,请检查防火墙命令及执行权限后刷新重试。', + proxy_inspect_failed: '无法检查 docker-proxy 进程,请确认系统进程信息可读取后刷新重试。', + nat_chain_unreachable: + '已找到该端口的 Docker 转发规则,但 NAT 入口链未生效,请检查 Docker 防火墙规则或重启 Docker 后刷新。', + no_matching_path: + '未找到该端口生效的 Docker 转发规则或代理进程,请启动或重启对应容器后刷新;若仍异常,请检查 Docker 网络配置。', + }, dockerTrafficPathPending: '访问方式待确认', dockerInputPolicyNotEffective: '该端口由主机直接接收,现有容器端口防护规则不会生效,请改用主机防火墙设置。', dockerInputUseHostFirewall: '该端口需要通过主机防火墙设置访问规则,请前往主机防火墙进行配置。', diff --git a/frontend/src/views/host/firewall/docker/detail/index.vue b/frontend/src/views/host/firewall/docker/detail/index.vue index fcfb7632e..b33da143f 100644 --- a/frontend/src/views/host/firewall/docker/detail/index.vue +++ b/frontend/src/views/host/firewall/docker/detail/index.vue @@ -179,6 +179,7 @@ import i18n from '@/lang'; import { MsgSuccess, MsgWarning } from '@/utils/message'; import { ElMessageBox, type FormInstance, type FormRules } from 'element-plus'; import { + dockerGuardEndpointManagementMessage, dockerGuardEndpointStatusMessage, dockerGuardManagementTarget, isValidDockerGuardSource, @@ -294,6 +295,13 @@ const toggleSelection = (key: string) => { }; const openPolicy = (endpoints: Firewall.DockerGuardEndpoint[]) => { if (!endpoints.length) return; + const endpointToDiagnose = endpoints.find( + (endpoint) => dockerGuardManagementTarget(endpoint) === 'needs_diagnosis', + ); + if (endpointToDiagnose) { + MsgWarning(dockerGuardEndpointManagementMessage(endpointToDiagnose)); + return; + } const targets = new Set(endpoints.map(dockerGuardManagementTarget)); if (targets.size !== 1) { MsgWarning(i18n.global.t('firewall.dockerTrafficPathMixed')); @@ -304,10 +312,6 @@ const openPolicy = (endpoints: Firewall.DockerGuardEndpoint[]) => { MsgWarning(i18n.global.t('firewall.dockerInputUseHostFirewall')); return; } - if (target !== 'container_guard') { - MsgWarning(i18n.global.t('firewall.dockerTrafficPathUnknown')); - return; - } policyEndpoints.value = endpoints; const first = endpoints[0]; form.mode = hasMixedFamilies.value ? 'deny_all' : policyConfigConsistent.value ? first.mode || 'deny_sources' : ''; @@ -395,9 +399,11 @@ const portMappingLabel = (row: Firewall.DockerGuardPortGroup) => { }; const protectionSummary = (row: Firewall.DockerGuardEndpoint) => { 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'); + if (!row.policyUUID) { + if (target === 'host_firewall') return i18n.global.t('firewall.dockerInputUseHostFirewall'); + if (target === 'needs_diagnosis') return dockerGuardEndpointManagementMessage(row); + return i18n.global.t('firewall.dockerGuardUnprotected'); + } let summary = i18n.global.t('firewall.denyAll'); if (row.mode === 'deny_sources') { summary = `${i18n.global.t('firewall.deny')}: ${formatHostAddressList(row.sources, row.family)}`; diff --git a/frontend/src/views/host/firewall/docker/index.vue b/frontend/src/views/host/firewall/docker/index.vue index 818a2edbd..310a0dd1c 100644 --- a/frontend/src/views/host/firewall/docker/index.vue +++ b/frontend/src/views/host/firewall/docker/index.vue @@ -241,8 +241,10 @@ import { downloadWithContent } from '@/utils/file'; import { getCurrentDateFormatted } from '@/utils/date'; import { dockerGuardEndpointKey, + dockerGuardEndpointManagementMessage, dockerGuardEndpointStatusMessage, dockerGuardManagementTarget, + isDockerGuardRuntimeEndpoint, } from '@/views/host/firewall/docker/model'; import { formatHostAddressList } from '@/views/host/firewall/utils/validation'; import { newUUID } from '@/utils/id'; @@ -294,6 +296,7 @@ const containerRows = computed(() => { .filter((container) => container.key !== '__orphan__') .map((container) => { const name = container.name || i18n.global.t('firewall.orphanEndpoints'); + const runtimeEndpoints = container.endpoints.filter(isDockerGuardRuntimeEndpoint); const containerMatches = [name, container.application, container.compose] .filter(Boolean) .some((item) => item!.toLowerCase().includes(keyword)); @@ -304,7 +307,7 @@ const containerRows = computed(() => { .some((item) => String(item).toLowerCase().includes(keyword)); }; const portGroups = container.portGroups.flatMap((group) => { - const endpoints = group.endpoints.filter(endpointMatches); + const endpoints = group.endpoints.filter(isDockerGuardRuntimeEndpoint).filter(endpointMatches); if (!endpoints.length) return []; if (endpoints.length === group.endpoints.length) return [group]; return endpoints.map((endpoint) => ({ @@ -317,7 +320,7 @@ const containerRows = computed(() => { return { ...container, name, - endpoints: container.endpoints.filter(endpointMatches), + endpoints: runtimeEndpoints.filter(endpointMatches), portGroups, }; }) @@ -406,12 +409,11 @@ const displaySources = (endpoint: Firewall.DockerGuardEndpoint) => formatHostAddressList(endpoint.sources, endpoint.family); const endpointStatusMessage = (endpoint: Firewall.DockerGuardEndpoint) => dockerGuardEndpointStatusMessage(data.base, endpoint); -const isDockerPolicyEndpoint = (endpoint: Firewall.DockerGuardEndpoint) => - dockerGuardManagementTarget(endpoint) === 'container_guard' && Boolean(endpoint.policyUUID); +const isDockerPolicyEndpoint = (endpoint: Firewall.DockerGuardEndpoint) => Boolean(endpoint.policyUUID); const endpointPrompt = (endpoint: Firewall.DockerGuardEndpoint) => { const target = dockerGuardManagementTarget(endpoint); if (target === 'host_firewall') return i18n.global.t('firewall.dockerInputUseHostFirewall'); - if (target === 'needs_diagnosis') return i18n.global.t('firewall.dockerTrafficPathUnknown'); + if (target === 'needs_diagnosis') return dockerGuardEndpointManagementMessage(endpoint); return i18n.global.t('firewall.dockerGuardUnprotected'); }; diff --git a/frontend/src/views/host/firewall/docker/model.ts b/frontend/src/views/host/firewall/docker/model.ts index ae46e2c8d..96f5fb4f4 100644 --- a/frontend/src/views/host/firewall/docker/model.ts +++ b/frontend/src/views/host/firewall/docker/model.ts @@ -7,6 +7,9 @@ type DockerGuardEndpointIdentity = Pick `${endpoint.family}|${endpoint.hostIP}|${endpoint.hostPort}|${endpoint.protocol}`; +export const isDockerGuardRuntimeEndpoint = (endpoint: Firewall.DockerGuardEndpoint): boolean => + !endpoint.containerState || ['running', 'paused', 'restarting'].includes(endpoint.containerState); + export const dockerGuardManagementTarget = ( endpoint: Firewall.DockerGuardEndpoint, ): NonNullable => { @@ -16,6 +19,13 @@ export const dockerGuardManagementTarget = ( return 'needs_diagnosis'; }; +export const dockerGuardEndpointManagementMessage = (endpoint: Firewall.DockerGuardEndpoint): string => { + if (endpoint.managementReason) { + return i18n.global.t(`firewall.dockerTrafficPathReason.${endpoint.managementReason}`); + } + return i18n.global.t('firewall.dockerTrafficPathUnknown'); +}; + export const isValidDockerGuardSource = (family: Firewall.DockerGuardEndpoint['family'], value: string): boolean => isValidAddressForFamily(family, value); @@ -45,7 +55,7 @@ export const dockerGuardEndpointStatusMessage = ( if (!endpoint.policyUUID || endpoint.effective) return ''; const target = dockerGuardManagementTarget(endpoint); if (target === 'host_firewall') return i18n.global.t('firewall.dockerInputPolicyNotEffective'); - if (target === 'needs_diagnosis') return i18n.global.t('firewall.dockerTrafficPathUnknown'); + if (target === 'needs_diagnosis') return dockerGuardEndpointManagementMessage(endpoint); const ipv6 = endpoint.family === 'ipv6'; return dockerGuardFamilyStatusMessage(base, ipv6 ? 'IPv6' : 'IPv4', ipv6 ? base.ipv6 : base.ipv4); }; diff --git a/frontend/src/views/host/firewall/rule/index.vue b/frontend/src/views/host/firewall/rule/index.vue index 7b5557acc..334ea743e 100644 --- a/frontend/src/views/host/firewall/rule/index.vue +++ b/frontend/src/views/host/firewall/rule/index.vue @@ -396,7 +396,7 @@ import i18n from '@/lang'; import { getCurrentDateFormatted } from '@/utils/date'; import { downloadWithContent } from '@/utils/file'; import { MsgError, MsgSuccess } from '@/utils/message'; -import { dockerGuardManagementTarget } from '@/views/host/firewall/docker/model'; +import { dockerGuardEndpointManagementMessage, dockerGuardManagementTarget } from '@/views/host/firewall/docker/model'; import { formatHostAddress } from '@/views/host/firewall/utils/validation'; import RuleImport from '@/views/host/firewall/rule/import/index.vue'; import RuleOperate from '@/views/host/firewall/rule/operate/index.vue'; @@ -422,6 +422,7 @@ interface UsageEntry { pid?: number; docker?: boolean; dockerManagementTarget?: Firewall.DockerGuardEndpoint['managementTarget']; + dockerEndpoint?: Firewall.DockerGuardEndpoint; } interface DisplayNotice { @@ -775,19 +776,25 @@ const ruleUsageEntries = (row: RuleRow): UsageEntry[] => { owner: `Docker: ${endpoint.containerName || endpoint.containerID?.slice(0, 12) || '-'}`, docker: true, dockerManagementTarget: dockerGuardManagementTarget(endpoint), + dockerEndpoint: endpoint, })); return [...processes, ...docker]; }; const usageEntryPortText = (entry: UsageEntry) => entry.ports.join(', ') || '-'; +const dockerUsageMessage = (entry: UsageEntry) => { + if (entry.dockerManagementTarget === 'host_firewall') { + return i18n.global.t('firewall.dockerInputUseHostFirewall'); + } + if (entry.dockerManagementTarget === 'container_guard') { + return i18n.global.t('firewall.dockerInputNotProtected'); + } + return entry.dockerEndpoint + ? dockerGuardEndpointManagementMessage(entry.dockerEndpoint) + : i18n.global.t('firewall.dockerTrafficPathUnknown'); +}; const usageEntryLabel = (entry: UsageEntry) => entry.docker - ? `${entry.owner} (${usageEntryPortText(entry)}) — ${i18n.global.t( - entry.dockerManagementTarget === 'host_firewall' - ? 'firewall.dockerInputUseHostFirewall' - : entry.dockerManagementTarget === 'container_guard' - ? 'firewall.dockerInputNotProtected' - : 'firewall.dockerTrafficPathUnknown', - )}` + ? `${entry.owner} (${usageEntryPortText(entry)}) — ${dockerUsageMessage(entry)}` : `${entry.owner} (${usageEntryPortText(entry)})`; const openUsageDetail = (entry: UsageEntry) => { if (entry.docker) {