fix: improve firewall abnormal state diagnostics (#13673)

This commit is contained in:
ssongliu
2026-08-31 16:18:13 +08:00
committed by GitHub
parent 1f12c09eb5
commit 433f1a940f
38 changed files with 725 additions and 94 deletions
+31 -7
View File
@@ -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
+11
View File
@@ -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 {
+62 -7
View File
@@ -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
+56
View File
@@ -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()
+55
View File
@@ -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()
+9
View File
@@ -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'
+9
View File
@@ -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'
+9
View File
@@ -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: 'همگام‌سازی'
+9
View File
@@ -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: '同期'
+9
View File
@@ -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: '동기화'
+9
View File
@@ -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: 'ຊິງຄ໌'
+9
View File
@@ -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'
+9
View File
@@ -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'
+9
View File
@@ -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: 'Синхронизировать'
+9
View File
@@ -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.35ten eskiyse UFWyi 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'
+9
View File
@@ -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: '同步'
+9
View File
@@ -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: "同步"
@@ -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")
}
+8 -2
View File
@@ -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 {
@@ -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
@@ -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),
)
}
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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) {
@@ -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)
}
@@ -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 <generated-batch>", script, run(name))
}
func buildBatchScript(commands ...[]string) (string, error) {
+54 -3
View File
@@ -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": {
+54 -3
View File
@@ -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": {
+4
View File
@@ -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;
+15 -6
View File
@@ -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<Firewall.FilterChainOperationResult>(
'/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<Firewall.FilterChainOperationResult>(
'/hosts/firewall/filter/operate',
{ name, operate, ...(taskID ? { taskID } : {}) },
TimeoutEnum.T_60S,
);
export const searchFirewallRules = (request: Firewall.InventoryRequest) => {
return http.post<Firewall.Inventory>('/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<Firewall.FilterChainOperationResult>(
'/hosts/firewall/docker/operate',
{ operation },
{ operation, ...(taskID ? { taskID } : {}) },
{
timeout: TimeoutEnum.T_60S,
skipErrorMessage: true,
@@ -88,6 +88,12 @@
<span class="port-card-value">{{ group.endpoint.description || '-' }}</span>
</el-tooltip>
</div>
<div v-if="group.endpoint.policyUUID && !group.endpoint.effective" class="port-card-field">
<span class="port-card-label">{{ $t('commons.table.message') }}</span>
<el-tooltip :content="statusMessage(group.endpoint)" placement="top" :show-after="400">
<span class="port-card-value is-warning">{{ statusMessage(group.endpoint) }}</span>
</el-tooltip>
</div>
</el-card>
</div>
</template>
@@ -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;
@@ -105,13 +105,21 @@
v-if="group.endpoint.policyUUID"
:label="$t('commons.table.status')"
>
{{
$t(
group.endpoint.effective
? 'firewall.effective'
: 'firewall.notEffective',
)
}}
<div>
{{
$t(
group.endpoint.effective
? 'firewall.effective'
: 'firewall.notEffective',
)
}}
<div
v-if="!group.endpoint.effective"
class="docker-guard-status-message"
>
{{ endpointStatusMessage(group.endpoint) }}
</div>
</div>
</el-descriptions-item>
<el-descriptions-item
v-if="group.endpoint.description"
@@ -200,10 +208,11 @@
</template>
</DrawerPro>
<DockerGuardDetail ref="detailRef" :containers="containerRows" @search="search" />
<DockerGuardDetail ref="detailRef" :base="data.base" :containers="containerRows" @search="search" />
<DockerGuardImport ref="importRef" @search="search" />
<RuleSync ref="ruleSyncRef" @search="search" />
<ConfirmDialog ref="cleanupConfirmRef" @confirm="submitCleanupBackend" />
<TaskLog ref="taskLogRef" @close="search" />
</div>
</template>
@@ -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<InstanceType<typeof DockerGuardDetail>>();
const importRef = ref<InstanceType<typeof DockerGuardImport>>();
const ruleSyncRef = ref<InstanceType<typeof RuleSync>>();
const cleanupConfirmRef = ref<InstanceType<typeof ConfirmDialog>>();
const taskLogRef = ref<InstanceType<typeof TaskLog>>();
const orphanDrawerVisible = ref(false);
const searchName = ref('');
const selects = ref<Firewall.DockerGuardContainer[]>([]);
@@ -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;
}
</style>
@@ -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<Firewall.DockerGuardEndpoint, 'family' | 'hostIP' | 'hostPort' | 'protocol'>;
@@ -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<Firewall.DockerGuardPolicy>;
@@ -94,11 +94,11 @@
<script lang="ts" setup>
import { Firewall } from '@/api/interface/firewall';
import i18n from '@/lang';
import NoSuchService from '@/components/layout-content/no-such-service.vue';
import { WarningFilled } from '@element-plus/icons-vue';
import { computed } from 'vue';
import { routerToName, routerToNameWithQuery } from '@/utils/router';
import { dockerGuardFamilyStatusMessage } from '@/views/host/firewall/docker/model';
const props = defineProps<{ base: Firewall.DockerGuardBase }>();
const emit = defineEmits<{
@@ -133,24 +133,8 @@ const retryableFamilyIssues = computed(() =>
const familyRetryOperation = computed<'initialize' | 'bind'>(() =>
retryableFamilyIssues.value.some((item) => !item.status.initialized) ? 'initialize' : 'bind',
);
const dockerChainName = computed(() => (props.base.backend === 'nftables' ? 'NFT_1PANEL_DOCKER' : '1PANEL_DOCKER'));
const familyStatusText = (family: string, status: Firewall.DockerGuardFamilyStatus) => {
if (
status.reason === 'command_missing' ||
status.reason === 'docker_chain_missing' ||
status.reason === 'inspect_failed'
) {
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',
);
return i18n.global.t('firewall.familyChainIssue', [family, dockerChainName.value, state]);
};
const familyStatusText = (family: 'IPv4' | 'IPv6', status: Firewall.DockerGuardFamilyStatus) =>
dockerGuardFamilyStatusMessage(props.base, family, status);
</script>
<style lang="scss">
@@ -77,9 +77,15 @@
<el-table-column :label="$t('commons.table.protocol')" :min-width="70" prop="protocol" />
<el-table-column :label="$t('commons.table.status')" :min-width="90" prop="syncStatus">
<template #default="{ row }">
<el-tag :type="syncStatusType(row.syncStatus)">
{{ syncStatusLabel(row.syncStatus) }}
</el-tag>
<el-tooltip
:disabled="!syncStatusMessage(row.syncStatus)"
:content="syncStatusMessage(row.syncStatus)"
placement="top"
>
<el-tag :type="syncStatusType(row.syncStatus)">
{{ syncStatusLabel(row.syncStatus) }}
</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="$t('firewall.sourcePort')" :min-width="70" prop="port" />
@@ -196,6 +202,11 @@ const syncStatusType = (status?: Firewall.RuleForward['syncStatus']) => {
if (status === 'converged') return 'success';
return status === 'runtime_only' ? 'warning' : 'danger';
};
const syncStatusMessage = (status?: Firewall.RuleForward['syncStatus']) => {
if (status === 'missing') return i18n.global.t('firewall.ruleSyncReasonDetail.missingFromTarget');
if (status === 'runtime_only') return i18n.global.t('firewall.ruleSyncReasonDetail.onlyInTarget');
return '';
};
const opRef = ref();
const dialogImportRef = ref();
@@ -426,6 +426,35 @@ interface PriorityPositionRange {
type RuleFilter = 'family:ipv4' | 'family:ipv6' | 'action:accept' | 'action:deny' | `state:${Firewall.InventoryState}`;
const ruleFilterStorageKey = 'firewall-rule-filters';
const chainFilterStorageKey = 'firewall-rule-visible-chains';
const ruleFilterOptions: RuleFilter[] = [
'family:ipv4',
'family:ipv6',
'action:accept',
'action:deny',
'state:managed',
'state:adopted',
'state:external',
'state:protected',
'state:drifted',
];
const iptablesChains = ['1PANEL_BASIC_BEFORE', '1PANEL_BASIC', '1PANEL_BASIC_AFTER'] as const;
const loadCachedFilterValues = <T extends string>(key: string, allowed: readonly T[], defaults: readonly T[]): T[] => {
try {
const cached = JSON.parse(localStorage.getItem(key) || 'null');
if (!Array.isArray(cached)) return [...defaults];
return cached.filter((item): item is T => typeof item === 'string' && allowed.includes(item as T));
} catch {
return [...defaults];
}
};
const cacheFilterValues = (key: string, values: readonly string[]) => {
localStorage.setItem(key, JSON.stringify(values));
};
const fireStatusRef = ref<InstanceType<typeof FireStatus>>();
const ruleOperateRef = ref<InstanceType<typeof RuleOperate>>();
const ruleImportRef = ref<InstanceType<typeof RuleImport>>();
@@ -451,9 +480,10 @@ const showFirewallUnavailablePrompt = computed(
() => (isDirectBackend.value && (!isInit.value || !isBind.value)) || (isServiceBackend.value && !isActive.value),
);
const firewallVersion = ref('');
const selectedRuleFilters = ref<RuleFilter[]>([]);
const iptablesChains = ['1PANEL_BASIC_BEFORE', '1PANEL_BASIC', '1PANEL_BASIC_AFTER'] as const;
const visibleIptablesChains = ref<string[]>([...iptablesChains]);
const selectedRuleFilters = ref<RuleFilter[]>(loadCachedFilterValues(ruleFilterStorageKey, ruleFilterOptions, []));
const visibleIptablesChains = ref<string[]>(
loadCachedFilterValues(chainFilterStorageKey, iptablesChains, iptablesChains),
);
const searchName = ref('');
const inventoryItems = ref<Firewall.InventoryItem[]>([]);
const inventoryTotal = ref(0);
@@ -850,11 +880,13 @@ const searchWithReset = () => {
};
const changeIptablesChainFilter = () => {
cacheFilterValues(chainFilterStorageKey, visibleIptablesChains.value);
selects.value = [];
return searchWithReset();
};
const changeRuleFilter = () => {
cacheFilterValues(ruleFilterStorageKey, selectedRuleFilters.value);
selects.value = [];
return searchWithReset();
};
@@ -168,6 +168,7 @@
<span>{{ $t('firewall.' + operation + 'FirewallHelper') }}</span>
</template>
</DockerRestart>
<TaskLog ref="taskLogRef" @close="handleInitializationTaskClose" />
</div>
</template>
@@ -189,6 +190,8 @@ import { computed, nextTick, ref } from 'vue';
import { loadDockerStatus } from '@/api/modules/container';
import { WarningFilled } from '@element-plus/icons-vue';
import { routerToName, routerToNameWithQuery } from '@/utils/router';
import TaskLog from '@/components/log/task/index.vue';
import { newUUID } from '@/utils/id';
const props = defineProps({
currentTab: String,
@@ -218,6 +221,7 @@ const operation = ref('restart');
const dockerStatus = ref();
const withDockerRestart = ref(false);
const familyRetrying = ref(false);
const taskLogRef = ref();
const backendName = computed(() => baseInfo.value.backend || baseInfo.value.name);
const backendUnavailable = computed(() => baseInfo.value.reason === 'backend_not_installed' || !baseInfo.value.isExist);
const isServiceBackend = computed(() => backendName.value === 'firewalld' || backendName.value === 'ufw');
@@ -360,13 +364,27 @@ const onInit = async () => {
} catch {
return;
}
const initializer =
props.currentTab === 'forward' ? enableForwarding() : operateFilterChain(chainName, 'init-' + props.currentTab);
await initializer;
if (props.currentTab === 'base') {
const result = (await operateFilterChain(chainName, 'init-' + props.currentTab, newUUID())).data;
if (result.queued && result.taskID) {
taskLogRef.value?.openWithTaskID(result.taskID, true);
return;
}
} else {
const result = (await enableForwarding(newUUID())).data;
if (result.queued && result.taskID) {
taskLogRef.value?.openWithTaskID(result.taskID, true);
return;
}
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
await loadBaseInfo(true);
};
const handleInitializationTaskClose = () => {
loadBaseInfo(true);
};
const onBind = async () => {
try {
await ElMessageBox.confirm(i18n.global.t('firewall.bindHelper'), i18n.global.t('commons.button.bind'), {
@@ -386,7 +404,11 @@ const onRetryFamilyIssues = async () => {
familyRetrying.value = true;
try {
if (isDirectForward.value) {
await enableForwarding();
const result = (await enableForwarding(newUUID())).data;
if (result.queued && result.taskID) {
taskLogRef.value?.openWithTaskID(result.taskID, true);
return;
}
} else {
await operateFilterChain('1PANEL_BASIC', 'bind-base');
}