mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
fix: improve firewall lifecycle and sync (#13809)
This commit is contained in:
@@ -60,7 +60,7 @@ func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
// @Summary Operate firewall
|
||||
// @Accept json
|
||||
// @Param request body dto.FirewallLifecycleOperation true "request"
|
||||
// @Success 200
|
||||
// @Success 200 {object} dto.FirewallLifecycleOperationResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/operate [post]
|
||||
@@ -71,12 +71,13 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperateFirewall(request); err != nil {
|
||||
result, err := firewallService.QueueFirewallOperation(request)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
|
||||
@@ -18,6 +18,7 @@ type FirewallSubsystemStatus struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
SyncError string `json:"syncError,omitempty"`
|
||||
LifecycleTaskID string `json:"lifecycleTaskID,omitempty"`
|
||||
IPv4 FirewallBackendFamilyStatus `json:"ipv4"`
|
||||
IPv6 FirewallBackendFamilyStatus `json:"ipv6"`
|
||||
}
|
||||
@@ -27,6 +28,11 @@ type FirewallLifecycleOperation struct {
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type FirewallLifecycleOperationResponse struct {
|
||||
TaskID string `json:"taskID,omitempty"`
|
||||
Queued bool `json:"queued"`
|
||||
}
|
||||
|
||||
type FirewallBackendOption struct {
|
||||
Name string `json:"name"`
|
||||
Installed bool `json:"installed"`
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -55,10 +57,16 @@ type firewallRuleRuntimeResolver interface {
|
||||
|
||||
var firewallRuleMutationMu sync.Mutex
|
||||
|
||||
var (
|
||||
firewallLifecycleTaskMu sync.Mutex
|
||||
firewallLifecycleTaskID string
|
||||
firewallLifecycleRequest dto.FirewallLifecycleOperation
|
||||
)
|
||||
|
||||
type IFirewallService interface {
|
||||
UpdatePanelPort(context.Context, uint, uint) error
|
||||
LoadBaseInfo(chainGroup string) (dto.FirewallSubsystemStatus, error)
|
||||
OperateFirewall(request dto.FirewallLifecycleOperation) error
|
||||
QueueFirewallOperation(request dto.FirewallLifecycleOperation) (dto.FirewallLifecycleOperationResponse, error)
|
||||
OperateFilterChain(request dto.FilterChainOperation) error
|
||||
QueueFilterChainInitialization(request dto.FilterChainOperation) (dto.FilterChainOperationResponse, error)
|
||||
Reset(context.Context, dto.FirewallRuleReset) (dto.FirewallRuleResetResponse, error)
|
||||
@@ -103,6 +111,7 @@ func newFirewallService() *FirewallService {
|
||||
|
||||
func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystemStatus, error) {
|
||||
status := dto.FirewallSubsystemStatus{Version: "-", Name: "-", Backend: "-"}
|
||||
status.LifecycleTaskID = currentFirewallLifecycleTaskID()
|
||||
if selected := configuredSystemFirewallBackend(); selected != "" {
|
||||
status.Name, status.Backend = selected, selected
|
||||
}
|
||||
@@ -163,6 +172,157 @@ func (c firewallLifecycleClient) Restart() error {
|
||||
return c.Client.Restart()
|
||||
}
|
||||
|
||||
func currentFirewallLifecycleTaskID() string {
|
||||
firewallLifecycleTaskMu.Lock()
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
return firewallLifecycleTaskID
|
||||
}
|
||||
|
||||
func lockFirewallLifecycleIdle() error {
|
||||
if !firewallLifecycleTaskMu.TryLock() {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
if firewallLifecycleTaskID != "" {
|
||||
firewallLifecycleTaskMu.Unlock()
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOperation) (dto.FirewallLifecycleOperationResponse, error) {
|
||||
response := dto.FirewallLifecycleOperationResponse{}
|
||||
if request.Operation == "disableBanPing" || request.Operation == "enableBanPing" {
|
||||
return response, s.OperateFirewall(request)
|
||||
}
|
||||
if !firewallLifecycleTaskMu.TryLock() {
|
||||
return response, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
if firewallLifecycleTaskID != "" {
|
||||
if request == firewallLifecycleRequest {
|
||||
return dto.FirewallLifecycleOperationResponse{TaskID: firewallLifecycleTaskID, Queued: true}, nil
|
||||
}
|
||||
return response, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
running, err := s.CurrentRuleSyncTask()
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
if running.Executing {
|
||||
return response, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
loadClient := s.baseClient
|
||||
if loadClient == nil {
|
||||
loadClient = selectedSystemFirewallClient
|
||||
}
|
||||
client, err := loadClient()
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
if (client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW) ||
|
||||
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationRestart)) {
|
||||
return response, s.OperateFirewall(request)
|
||||
}
|
||||
operation, label := task.TaskExec, "Start"
|
||||
if request.Operation == string(lifecycle.OperationRestart) {
|
||||
operation, label = task.TaskRestart, task.TaskRestart
|
||||
}
|
||||
name := task.GetTaskName(client.Name(), label, task.TaskScopeFirewall)
|
||||
taskItem, err := task.NewTask(name, operation, task.TaskScopeFirewall, "", 0)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(name, func(t *task.Task) error {
|
||||
return s.runFirewallLifecycleTask(t, client, request)
|
||||
}, nil, 0, 0)
|
||||
if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil {
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
return response, err
|
||||
}
|
||||
firewallLifecycleTaskID, firewallLifecycleRequest = taskItem.TaskID, request
|
||||
go func() {
|
||||
defer func() {
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
firewallLifecycleTaskMu.Lock()
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
if firewallLifecycleTaskID == taskItem.TaskID {
|
||||
firewallLifecycleTaskID = ""
|
||||
}
|
||||
}()
|
||||
if err := taskItem.Execute(); err != nil && taskItem.Task.Status == constant.StatusExecuting {
|
||||
taskItem.LogFailedWithErr(name, err)
|
||||
taskItem.Task.Status = constant.StatusFailed
|
||||
taskItem.Task.ErrorMsg = err.Error()
|
||||
taskItem.Task.EndAt = time.Now()
|
||||
_ = repo.NewITaskRepo().Update(context.Background(), taskItem.Task)
|
||||
}
|
||||
}()
|
||||
return dto.FirewallLifecycleOperationResponse{TaskID: taskItem.TaskID, Queued: true}, nil
|
||||
}
|
||||
|
||||
func runFirewallLifecycleAction(t *task.Task, name string, action func() error) error {
|
||||
t.Log(i18n.GetWithName("TaskStart", name))
|
||||
started := time.Now()
|
||||
err := t.TaskCtx.Err()
|
||||
if err == nil {
|
||||
err = action()
|
||||
}
|
||||
t.LogWithStatus(fmt.Sprintf("%s (%.2fs)", name, time.Since(started).Seconds()), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation) error {
|
||||
ctx := t.TaskCtx
|
||||
provider := filter.Provider(client.Name())
|
||||
operator := lifecycle.NewOperator(firewallLifecycleClient{client})
|
||||
operator.RunAction = func(operation, name string, action func() error) error {
|
||||
return runFirewallLifecycleAction(t, task.GetTaskName(name, operation, ""), action)
|
||||
}
|
||||
operationErr := operator.Operate(lifecycle.Operation(request.Operation), request.WithDockerRestart, func(lifecycle.Client) error {
|
||||
rulesErr := runFirewallLifecycleAction(t, i18n.GetWithName("FirewallRestoreRulesStep", client.Name()), func() error {
|
||||
return s.restoreStoredFirewallRules(ctx, provider, t)
|
||||
})
|
||||
whitelistErr := runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
|
||||
ports, err := loadFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.SyncSystemPorts(ctx, nil, systemPorts(ports))
|
||||
})
|
||||
return errors.Join(rulesErr, whitelistErr)
|
||||
})
|
||||
var recoveryErr *lifecycle.CompletedOperationError
|
||||
if operationErr != nil && !errors.As(operationErr, &recoveryErr) {
|
||||
return operationErr
|
||||
}
|
||||
var forwardingErr error
|
||||
if provider == filter.ProviderFirewalld {
|
||||
forwardingErr = runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallRestoreForwardingRulesStep"), func() error {
|
||||
if s.restoreForwarding != nil {
|
||||
return s.restoreForwarding(ctx)
|
||||
}
|
||||
return newForwardingService().Restore(ctx)
|
||||
})
|
||||
}
|
||||
dockerErr := runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallInspectDockerGuardStep"), func() error {
|
||||
if provider == filter.ProviderFirewalld {
|
||||
active := s.dockerActive
|
||||
if active == nil {
|
||||
active = func() (bool, error) { return controller.CheckActive("docker") }
|
||||
}
|
||||
running, err := active()
|
||||
if err != nil || !running {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.restoreDockerGuard != nil {
|
||||
return s.restoreDockerGuard(ctx)
|
||||
}
|
||||
return ReconcileDockerPortGuard(ctx)
|
||||
})
|
||||
return errors.Join(operationErr, forwardingErr, dockerErr)
|
||||
}
|
||||
|
||||
func (s *FirewallService) OperateFirewall(request dto.FirewallLifecycleOperation) error {
|
||||
switch request.Operation {
|
||||
case "disableBanPing":
|
||||
@@ -248,7 +408,7 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e
|
||||
return nil
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := s.restoreStoredFirewallRules(ctx, filter.Provider(provider)); err != nil {
|
||||
if err := s.restoreStoredFirewallRules(ctx, filter.Provider(provider), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.syncConfiguredFirewallPorts(ctx)
|
||||
@@ -280,7 +440,7 @@ func (s *FirewallService) QueueFilterChainInitialization(
|
||||
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))
|
||||
return s.restoreStoredFirewallRules(t.TaskCtx, filter.Provider(provider), nil)
|
||||
}, nil)
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func(t *task.Task) error {
|
||||
return s.syncConfiguredFirewallPorts(t.TaskCtx)
|
||||
@@ -318,6 +478,10 @@ func (s *FirewallService) operateFilterChainBaseLocked(provider string, request
|
||||
}
|
||||
|
||||
func (s *FirewallService) Reset(ctx context.Context, request dto.FirewallRuleReset) (dto.FirewallRuleResetResponse, error) {
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return dto.FirewallRuleResetResponse{}, err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
|
||||
@@ -2791,7 +2955,7 @@ func (s *FirewallService) addPortsBeforeStart(client lifecycle.Client) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := s.restoreStoredFirewallRules(ctx, provider); err != nil {
|
||||
if err := s.restoreStoredFirewallRules(ctx, provider, nil); err != nil {
|
||||
recordFailure("restore stored firewall rules", err)
|
||||
}
|
||||
if provider == filter.ProviderIptables || provider == filter.ProviderNftables {
|
||||
|
||||
@@ -372,6 +372,10 @@ func loadSystemFirewallFamilyInfo(provider, family string) dto.FirewallBackendFa
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
||||
}
|
||||
@@ -385,7 +389,7 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
|
||||
}
|
||||
if request.Operation == "initialize" {
|
||||
service := newFirewallService()
|
||||
if err := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend)); err != nil {
|
||||
if err := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.syncConfiguredFirewallPorts(ctx)
|
||||
|
||||
@@ -195,6 +195,7 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
|
||||
created, removed, unexecuted := 0, 0, 0
|
||||
stopped := make(map[string]error)
|
||||
failedRemovals := make(map[string]error)
|
||||
var firewalldFinalSnapshot *filter.Snapshot
|
||||
record := func(operation string, rule *firewallSyncRule, cause error, skipped bool) {
|
||||
item := rule.FirewallRuleSyncItem
|
||||
if operation == "TaskDelete" && rule.observed != nil {
|
||||
@@ -234,13 +235,15 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
|
||||
}
|
||||
if skipped {
|
||||
t.Logf("%s %s: %v", label, i18n.GetMsgByKey("FirewallCreateRuleSkipped"), cause)
|
||||
} else if operation == task.TaskSync && cause == nil {
|
||||
t.Logf("%s %s", label, i18n.GetMsgByKey("FirewallSyncRuleUnchanged"))
|
||||
} else {
|
||||
t.LogWithStatus(label, cause)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if t != nil {
|
||||
t.Log(i18n.GetMsgWithMap("FirewallSyncOperationsResult", map[string]interface{}{"created": created, "removed": removed, "failed": result.Failed, "skipped": unexecuted}))
|
||||
t.Log(i18n.GetMsgWithMap("FirewallSyncOperationsResult", map[string]interface{}{"created": created, "removed": removed, "failed": result.Failed, "skipped": unexecuted, "unchanged": result.Skipped - unexecuted}))
|
||||
}
|
||||
}()
|
||||
blocked := false
|
||||
@@ -287,6 +290,10 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
|
||||
return syncObservedPosition(queue[i].observed) > syncObservedPosition(queue[j].observed)
|
||||
})
|
||||
}
|
||||
if scope.Provider == filter.ProviderFirewalld && operation == filter.ChangeCreate && len(failedRemovals) == 0 && stopped[scope.Key()] == nil {
|
||||
firewalldFinalSnapshot = syncFirewalldCreates(ctx, runtime, initial, removed > 0, queue, t, record)
|
||||
continue
|
||||
}
|
||||
for start := 0; start < len(queue); {
|
||||
rule := queue[start]
|
||||
cause := stopped[scope.Key()]
|
||||
@@ -325,21 +332,21 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
|
||||
changes = append(changes, change)
|
||||
} else {
|
||||
after := *entry.Rule
|
||||
after.OrderIndex = nil
|
||||
if len(batch) == 1 && scope.Provider != filter.ProviderFirewalld {
|
||||
markers := make([]string, 0)
|
||||
for _, candidate := range rules {
|
||||
if candidate.Rule != nil && candidate.Rule.Scope.Key() == scope.Key() && candidate.desired.Marker != "" {
|
||||
markers = append(markers, candidate.desired.Marker)
|
||||
}
|
||||
}
|
||||
after.OrderIndex = nil
|
||||
if len(batch) == 1 {
|
||||
after.OrderIndex = firewallsync.InsertionPosition(snapshot, markers, entry.desired.Marker)
|
||||
}
|
||||
changes = append(changes, filter.DesiredChange{Operation: operation, After: &after, Append: scope.Provider == filter.ProviderUFW && after.OrderIndex == nil})
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = runtime.ExecuteSync(ctx, snapshot, changes)
|
||||
_, err = runtime.ExecuteSync(ctx, snapshot, changes)
|
||||
}
|
||||
for _, entry := range batch {
|
||||
record(name, entry, err, false)
|
||||
@@ -351,9 +358,141 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
|
||||
}
|
||||
}
|
||||
}
|
||||
if firewalldFinalSnapshot != nil && result.Failed == 0 && created > 0 {
|
||||
verifyErr := verifyFirewalldSyncSnapshot(*firewalldFinalSnapshot, rules)
|
||||
if t != nil {
|
||||
t.LogWithStatus(i18n.GetWithName("FirewallSyncStep", string(request.TargetProvider)), verifyErr)
|
||||
}
|
||||
if verifyErr != nil {
|
||||
return result, verifyErr
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func syncFirewalldCreates(
|
||||
ctx context.Context,
|
||||
runtime *filterruntime.Engine,
|
||||
initial filter.Snapshot,
|
||||
refresh bool,
|
||||
queue []*firewallSyncRule,
|
||||
t *task.Task,
|
||||
record func(string, *firewallSyncRule, error, bool),
|
||||
) *filter.Snapshot {
|
||||
if len(queue) == 0 {
|
||||
return nil
|
||||
}
|
||||
snapshot := initial
|
||||
snapshot.Rules = append([]filter.ObservedRule(nil), initial.Rules...)
|
||||
indices := make(map[string]int)
|
||||
indexRules := func() {
|
||||
clear(indices)
|
||||
for index, rule := range snapshot.Rules {
|
||||
indices[rule.Locator.Canonical] = index
|
||||
}
|
||||
}
|
||||
indexRules()
|
||||
pending := make([]*firewallSyncRule, 0, len(queue))
|
||||
var readErr error
|
||||
for index, entry := range queue {
|
||||
if refresh {
|
||||
snapshot, readErr = runtime.ObserveMutation(ctx, initial.Scope)
|
||||
if readErr != nil {
|
||||
record(task.TaskCreate, entry, readErr, false)
|
||||
for _, remaining := range queue[index+1:] {
|
||||
record(task.TaskCreate, remaining, readErr, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
indexRules()
|
||||
refresh = false
|
||||
}
|
||||
if t != nil {
|
||||
t.Logf("[%d/%d] %s %s", index+1, len(queue), i18n.GetMsgByKey(task.TaskCreate), entry.SourceUUID)
|
||||
}
|
||||
after := *entry.Rule
|
||||
after.OrderIndex = nil
|
||||
applied, err := runtime.ExecuteSync(ctx, snapshot, []filter.DesiredChange{{Operation: filter.ChangeCreate, After: &after}})
|
||||
if err == nil && len(applied.Applied) != 1 {
|
||||
err = filter.ErrVerificationFailed
|
||||
}
|
||||
if err == nil {
|
||||
observed := applied.Applied[0]
|
||||
if position, exists := indices[observed.Locator.Canonical]; exists {
|
||||
snapshot.Rules[position] = observed
|
||||
} else {
|
||||
indices[observed.Locator.Canonical] = len(snapshot.Rules)
|
||||
snapshot.Rules = append(snapshot.Rules, observed)
|
||||
}
|
||||
snapshot, err = filter.NewSnapshot(snapshot.Scope, snapshot.Rules)
|
||||
}
|
||||
if err != nil {
|
||||
record(task.TaskCreate, entry, err, false)
|
||||
if firewallCreateUnavailable(err) {
|
||||
for _, remaining := range queue[index+1:] {
|
||||
record(task.TaskCreate, remaining, err, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
refresh = true
|
||||
continue
|
||||
}
|
||||
pending = append(pending, entry)
|
||||
}
|
||||
var actual filter.Snapshot
|
||||
if readErr == nil {
|
||||
actual, readErr = runtime.ObserveMutation(ctx, initial.Scope)
|
||||
}
|
||||
if readErr != nil {
|
||||
for _, entry := range pending {
|
||||
record(task.TaskCreate, entry, readErr, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
states := firewalldRuleStates(actual)
|
||||
for _, entry := range pending {
|
||||
key, err := filter.RuleKey(*entry.Rule)
|
||||
if err == nil && states[key] != 1 {
|
||||
err = filter.ErrVerificationFailed
|
||||
}
|
||||
record(task.TaskCreate, entry, err, false)
|
||||
}
|
||||
return &actual
|
||||
}
|
||||
|
||||
func firewalldRuleStates(snapshot filter.Snapshot) map[string]int {
|
||||
states := make(map[string]int, len(snapshot.Rules))
|
||||
for _, observed := range snapshot.Rules {
|
||||
if observed.ParseStatus != filter.ParseStatusSupported || observed.Persistence != filter.PersistenceStatusConverged {
|
||||
continue
|
||||
}
|
||||
if key, err := filter.RuleKey(observed.Rule); err == nil {
|
||||
states[key]++
|
||||
}
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
func verifyFirewalldSyncSnapshot(snapshot filter.Snapshot, rules []*firewallSyncRule) error {
|
||||
states := firewalldRuleStates(snapshot)
|
||||
for _, entry := range rules {
|
||||
if entry.Status == firewallsync.StatusRemove {
|
||||
continue
|
||||
}
|
||||
if entry.Rule == nil {
|
||||
return filter.ErrVerificationFailed
|
||||
}
|
||||
key, err := filter.RuleKey(*entry.Rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if states[key] != 1 {
|
||||
return fmt.Errorf("%w: %s", filter.ErrVerificationFailed, entry.SourceUUID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncObservedPosition(rule *filter.ObservedRule) int {
|
||||
if rule.Locator.Position != nil {
|
||||
return *rule.Locator.Position
|
||||
@@ -361,8 +500,8 @@ func syncObservedPosition(rule *filter.ObservedRule) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *FirewallService) restoreStoredFirewallRules(ctx context.Context, provider filter.Provider) error {
|
||||
result, err := s.syncRules(ctx, "", dto.FirewallRuleSyncRequest{TargetProvider: provider}, nil)
|
||||
func (s *FirewallService) restoreStoredFirewallRules(ctx context.Context, provider filter.Provider, t *task.Task) error {
|
||||
result, err := s.syncRules(ctx, "", dto.FirewallRuleSyncRequest{TargetProvider: provider}, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("restore database firewall rules: %w", err)
|
||||
}
|
||||
@@ -393,6 +532,10 @@ func (s *FirewallService) syncSystemRules(
|
||||
clientIP string,
|
||||
request dto.FirewallRuleSyncRequest,
|
||||
) (dto.FirewallRuleSyncResult, error) {
|
||||
if err := lockFirewallLifecycleIdle(); err != nil {
|
||||
return dto.FirewallRuleSyncResult{}, err
|
||||
}
|
||||
defer firewallLifecycleTaskMu.Unlock()
|
||||
firewallRuleSyncTaskMu.Lock()
|
||||
defer firewallRuleSyncTaskMu.Unlock()
|
||||
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Rule creation failed; no database record was saved and executed commands were not rolled back'
|
||||
FirewallCreateRulePersistenceFailed: 'The rule was created, but its management record could not be saved'
|
||||
FirewallAdoptRulePersistenceFailed: 'The rule was adopted, but its management record could not be saved'
|
||||
FirewallSyncOperationsResult: 'Synchronization operations: {{ .removed }} deleted, {{ .created }} created, {{ .failed }} failed, {{ .skipped }} not executed'
|
||||
FirewallSyncOperationsResult: 'Synchronization operations: {{ .removed }} deleted, {{ .created }} created, {{ .failed }} failed, {{ .skipped }} not executed, {{ .unchanged }} already matching (no changes needed)'
|
||||
FirewallSyncRuleUnchanged: 'Already matching; no changes needed'
|
||||
FirewallSyncStep: 'Synchronize rules to {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} firewall rules failed to synchronize'
|
||||
FirewallResetSourceStep: 'Reset and disable source firewall {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Error al crear la regla. No se guardó ningún registro en la base de datos ni se revirtieron los comandos ejecutados'
|
||||
FirewallCreateRulePersistenceFailed: 'La regla se creó, pero no se pudo guardar su registro de gestión'
|
||||
FirewallAdoptRulePersistenceFailed: 'Se ejecutó la adopción de la regla, pero no se pudo guardar su registro de gestión'
|
||||
FirewallSyncOperationsResult: 'Operaciones de sincronización: {{ .removed }} eliminadas, {{ .created }} creadas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar'
|
||||
FirewallSyncOperationsResult: 'Operaciones de sincronización: {{ .removed }} eliminadas, {{ .created }} creadas, {{ .failed }} fallidas, {{ .skipped }} sin ejecutar, {{ .unchanged }} ya coinciden (sin cambios necesarios)'
|
||||
FirewallSyncRuleUnchanged: 'Ya coincide; no requiere cambios'
|
||||
FirewallSyncStep: 'Sincronizar reglas con {{ .name }}'
|
||||
FirewallSyncFailed: 'No se pudieron sincronizar {{ .failed }} reglas del firewall'
|
||||
FirewallResetSourceStep: 'Restablecer y desactivar el firewall de origen {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'ایجاد قانون ناموفق بود. هیچ رکوردی در پایگاه داده ذخیره نشد و دستورات اجراشده بازگردانی نشدند'
|
||||
FirewallCreateRulePersistenceFailed: 'قانون ایجاد شد، اما ذخیره رکورد مدیریتی آن ناموفق بود'
|
||||
FirewallAdoptRulePersistenceFailed: 'دستور پذیرش قانون برای مدیریت اجرا شد، اما اطلاعات مدیریت ذخیره نشد'
|
||||
FirewallSyncOperationsResult: 'عملیات همگامسازی: {{ .removed }} حذفشده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرانشده'
|
||||
FirewallSyncOperationsResult: 'عملیات همگامسازی: {{ .removed }} حذفشده، {{ .created }} ایجادشده، {{ .failed }} ناموفق، {{ .skipped }} اجرانشده، {{ .unchanged }} از قبل مطابق (بدون نیاز به تغییر)'
|
||||
FirewallSyncRuleUnchanged: 'از قبل مطابق است؛ نیازی به تغییر نیست'
|
||||
FirewallSyncStep: 'همگامسازی قوانین با {{ .name }}'
|
||||
FirewallSyncFailed: 'همگامسازی {{ .failed }} قانون فایروال ناموفق بود'
|
||||
FirewallResetSourceStep: 'بازنشانی و غیرفعالکردن فایروال مبدأ {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'ルールの作成に失敗しました。データベースには保存せず、実行済みのコマンドはロールバックしません'
|
||||
FirewallCreateRulePersistenceFailed: 'ルールは作成されましたが、管理情報の保存に失敗しました'
|
||||
FirewallAdoptRulePersistenceFailed: 'ルールの管理対象への取り込みコマンドは実行されましたが、管理情報の保存に失敗しました'
|
||||
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件'
|
||||
FirewallSyncOperationsResult: '同期操作:削除成功 {{ .removed }} 件、作成成功 {{ .created }} 件、失敗 {{ .failed }} 件、未実行 {{ .skipped }} 件、一致済みで変更不要 {{ .unchanged }} 件'
|
||||
FirewallSyncRuleUnchanged: '一致済み、変更不要'
|
||||
FirewallSyncStep: '{{ .name }} にルールを同期'
|
||||
FirewallSyncFailed: '{{ .failed }} 件のファイアウォールルールを同期できませんでした'
|
||||
FirewallResetSourceStep: '移行元ファイアウォール {{ .name }} をリセットして無効化'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: '규칙 생성에 실패했습니다. 데이터베이스에 저장하지 않았으며 실행된 명령은 롤백하지 않습니다'
|
||||
FirewallCreateRulePersistenceFailed: '규칙은 생성되었지만 관리 정보를 저장하지 못했습니다'
|
||||
FirewallAdoptRulePersistenceFailed: '규칙 관리 등록 명령은 실행되었지만 관리 정보를 저장하지 못했습니다'
|
||||
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개'
|
||||
FirewallSyncOperationsResult: '동기화 작업: 삭제 성공 {{ .removed }}개, 생성 성공 {{ .created }}개, 실패 {{ .failed }}개, 미실행 {{ .skipped }}개, 이미 일치하여 변경 불필요 {{ .unchanged }}개'
|
||||
FirewallSyncRuleUnchanged: '이미 일치하여 변경이 필요하지 않음'
|
||||
FirewallSyncStep: '{{ .name }}에 규칙 동기화'
|
||||
FirewallSyncFailed: '방화벽 규칙 {{ .failed }}개를 동기화하지 못했습니다'
|
||||
FirewallResetSourceStep: '원본 방화벽 {{ .name }} 초기화 및 비활성화'
|
||||
|
||||
@@ -693,7 +693,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'ການສ້າງກົດລົ້ມເຫຼວ. ບໍ່ໄດ້ບັນທຶກໃນຖານຂໍ້ມູນ ແລະ ບໍ່ໄດ້ຍ້ອນກັບຄຳສັ່ງທີ່ດຳເນີນການແລ້ວ'
|
||||
FirewallCreateRulePersistenceFailed: 'ສ້າງກົດແລ້ວ ແຕ່ບັນທຶກຂໍ້ມູນການຈັດການບໍ່ສຳເລັດ'
|
||||
FirewallAdoptRulePersistenceFailed: 'ຄຳສັ່ງນຳກົດເຂົ້າການຈັດການໄດ້ດຳເນີນການແລ້ວ ແຕ່ບໍ່ສາມາດບັນທຶກຂໍ້ມູນການຈັດການໄດ້'
|
||||
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}'
|
||||
FirewallSyncOperationsResult: 'ການດຳເນີນການຊິງຄ໌: ລຶບ {{ .removed }}, ສ້າງ {{ .created }}, ລົ້ມເຫຼວ {{ .failed }}, ບໍ່ໄດ້ດຳເນີນການ {{ .skipped }}, ກົງກັນແລ້ວ {{ .unchanged }} (ບໍ່ຕ້ອງປ່ຽນແປງ)'
|
||||
FirewallSyncRuleUnchanged: 'ກົງກັນແລ້ວ; ບໍ່ຕ້ອງປ່ຽນແປງ'
|
||||
FirewallSyncStep: 'ຊິງຄ໌ກົດໄປຫາ {{ .name }}'
|
||||
FirewallSyncFailed: 'ຊິງຄ໌ກົດໄຟວອລ {{ .failed }} ລາຍການບໍ່ສຳເລັດ'
|
||||
FirewallResetSourceStep: 'ຣີເຊັດ ແລະ ປິດໃຊ້ໄຟວອລຕົ້ນທາງ {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Penciptaan peraturan gagal. Tiada rekod disimpan dalam pangkalan data dan arahan yang dilaksanakan tidak dibatalkan'
|
||||
FirewallCreateRulePersistenceFailed: 'Peraturan telah dicipta, tetapi rekod pengurusannya tidak dapat disimpan'
|
||||
FirewallAdoptRulePersistenceFailed: 'Arahan pengambilalihan peraturan telah dilaksanakan, tetapi rekod pengurusannya tidak dapat disimpan'
|
||||
FirewallSyncOperationsResult: 'Operasi penyegerakan: {{ .removed }} dipadam, {{ .created }} dicipta, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan'
|
||||
FirewallSyncOperationsResult: 'Operasi penyegerakan: {{ .removed }} dipadam, {{ .created }} dicipta, {{ .failed }} gagal, {{ .skipped }} tidak dilaksanakan, {{ .unchanged }} sudah sepadan (tiada perubahan diperlukan)'
|
||||
FirewallSyncRuleUnchanged: 'Sudah sepadan; tiada perubahan diperlukan'
|
||||
FirewallSyncStep: 'Segerakkan peraturan ke {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} peraturan firewall gagal disegerakkan'
|
||||
FirewallResetSourceStep: 'Tetapkan semula dan nyahdayakan firewall sumber {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Falha ao criar a regra. Nenhum registro foi salvo no banco de dados e os comandos executados não foram revertidos'
|
||||
FirewallCreateRulePersistenceFailed: 'A regra foi criada, mas não foi possível salvar seu registro de gerenciamento'
|
||||
FirewallAdoptRulePersistenceFailed: 'A regra foi adotada, mas não foi possível salvar seu registro de gerenciamento'
|
||||
FirewallSyncOperationsResult: 'Operações de sincronização: {{ .removed }} excluídas, {{ .created }} criadas, {{ .failed }} falhas, {{ .skipped }} não executadas'
|
||||
FirewallSyncOperationsResult: 'Operações de sincronização: {{ .removed }} excluídas, {{ .created }} criadas, {{ .failed }} falhas, {{ .skipped }} não executadas, {{ .unchanged }} já correspondem (sem alterações necessárias)'
|
||||
FirewallSyncRuleUnchanged: 'Já corresponde; nenhuma alteração necessária'
|
||||
FirewallSyncStep: 'Sincronizar regras com {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} regras de firewall falharam na sincronização'
|
||||
FirewallResetSourceStep: 'Redefinir e desativar o firewall de origem {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Не удалось создать правило. Запись в базе данных не сохранена, выполненные команды не отменены'
|
||||
FirewallCreateRulePersistenceFailed: 'Правило создано, но не удалось сохранить запись управления'
|
||||
FirewallAdoptRulePersistenceFailed: 'Команды принятия правила под управление выполнены, но сохранить запись управления не удалось'
|
||||
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}'
|
||||
FirewallSyncOperationsResult: 'Операции синхронизации: удалено {{ .removed }}, создано {{ .created }}, ошибок {{ .failed }}, не выполнено {{ .skipped }}, уже совпадают (изменения не нужны): {{ .unchanged }}'
|
||||
FirewallSyncRuleUnchanged: 'Уже совпадает; изменения не нужны'
|
||||
FirewallSyncStep: 'Синхронизировать правила с {{ .name }}'
|
||||
FirewallSyncFailed: 'Не удалось синхронизировать правил межсетевого экрана: {{ .failed }}'
|
||||
FirewallResetSourceStep: 'Сбросить и отключить исходный межсетевой экран {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: 'Kural oluşturma başarısız. Veritabanına kayıt yazılmadı ve yürütülen komutlar geri alınmadı'
|
||||
FirewallCreateRulePersistenceFailed: 'Kural oluşturuldu ancak yönetim kaydı kaydedilemedi'
|
||||
FirewallAdoptRulePersistenceFailed: 'Kuralı yönetime alma komutu yürütüldü, ancak yönetim kaydı kaydedilemedi'
|
||||
FirewallSyncOperationsResult: 'Eşitleme işlemleri: {{ .removed }} silindi, {{ .created }} oluşturuldu, {{ .failed }} başarısız, {{ .skipped }} yürütülmedi'
|
||||
FirewallSyncOperationsResult: 'Eşitleme işlemleri: {{ .removed }} silindi, {{ .created }} oluşturuldu, {{ .failed }} başarısız, {{ .skipped }} yürütülmedi, {{ .unchanged }} zaten eşleşiyor (değişiklik gerekmiyor)'
|
||||
FirewallSyncRuleUnchanged: 'Zaten eşleşiyor; değişiklik gerekmiyor'
|
||||
FirewallSyncStep: 'Kuralları {{ .name }} ile eşitle'
|
||||
FirewallSyncFailed: '{{ .failed }} güvenlik duvarı kuralı eşitlenemedi'
|
||||
FirewallResetSourceStep: 'Kaynak güvenlik duvarı {{ .name }} sıfırla ve devre dışı bırak'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: '規則建立失敗,未寫入資料庫,已執行的命令不回復'
|
||||
FirewallCreateRulePersistenceFailed: '規則已建立,但納管資訊儲存失敗'
|
||||
FirewallAdoptRulePersistenceFailed: '規則納管命令已執行,但納管資訊儲存失敗'
|
||||
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條'
|
||||
FirewallSyncOperationsResult: '同步操作彙總:刪除成功 {{ .removed }} 條,建立成功 {{ .created }} 條,失敗 {{ .failed }} 條,未執行 {{ .skipped }} 條,已一致無需變更 {{ .unchanged }} 條'
|
||||
FirewallSyncRuleUnchanged: '已一致,無需變更'
|
||||
FirewallSyncStep: '同步規則到 {{ .name }}'
|
||||
FirewallSyncFailed: '{{ .failed }} 條防火牆規則同步失敗'
|
||||
FirewallResetSourceStep: '重設並停用來源防火牆 {{ .name }}'
|
||||
|
||||
@@ -702,7 +702,8 @@ FirewallImportRuleConversion: '[{{ .index }}/{{ .total }}] {{ .source }} → {{
|
||||
FirewallCreateRuleExecutionFailed: '规则创建失败,未入库,已执行的命令不回滚'
|
||||
FirewallCreateRulePersistenceFailed: '规则已创建,但纳管信息保存失败'
|
||||
FirewallAdoptRulePersistenceFailed: '规则纳管命令已执行,但纳管信息保存失败'
|
||||
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条'
|
||||
FirewallSyncOperationsResult: '同步操作汇总:删除成功 {{ .removed }} 条,创建成功 {{ .created }} 条,失败 {{ .failed }} 条,未执行 {{ .skipped }} 条,已一致无需变更 {{ .unchanged }} 条'
|
||||
FirewallSyncRuleUnchanged: '已一致,无需变更'
|
||||
FirewallSyncStep: "同步规则到 {{ .name }}"
|
||||
FirewallSyncFailed: "{{ .failed }} 条防火墙规则同步失败"
|
||||
FirewallResetSourceStep: "重置并停用源防火墙 {{ .name }}"
|
||||
|
||||
@@ -409,13 +409,17 @@ func nativeCanonical(rule filter.FirewallRule) string {
|
||||
|
||||
func missingRuleCommands(snapshot filter.Snapshot, rule filter.FirewallRule) ([]filter.NativeCommand, []filter.NativeCommand) {
|
||||
commands, rollback := pairedCommands(rule, "add", "remove")
|
||||
canonical := nativeCanonical(rule)
|
||||
var runtimeExists, permanentExists bool
|
||||
for _, observed := range snapshot.Rules {
|
||||
if observed.Locator.Canonical != nativeCanonical(rule) {
|
||||
if observed.Locator.Canonical != canonical {
|
||||
continue
|
||||
}
|
||||
runtimeExists = runtimeExists || observed.Persistence == filter.PersistenceStatusConverged || observed.Persistence == filter.PersistenceStatusRuntimeOnly
|
||||
permanentExists = permanentExists || observed.Persistence == filter.PersistenceStatusConverged || observed.Persistence == filter.PersistenceStatusPermanentOnly
|
||||
if runtimeExists && permanentExists {
|
||||
break
|
||||
}
|
||||
}
|
||||
var changes, inverses []filter.NativeCommand
|
||||
for index, exists := range []bool{runtimeExists, permanentExists} {
|
||||
|
||||
@@ -249,9 +249,9 @@ func (e *Engine) ExecuteCreate(ctx context.Context, snapshot filter.Snapshot, ch
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Engine) ExecuteSync(ctx context.Context, snapshot filter.Snapshot, changes []filter.DesiredChange) error {
|
||||
func (e *Engine) ExecuteSync(ctx context.Context, snapshot filter.Snapshot, changes []filter.DesiredChange) (filter.ApplyResult, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
return filter.ApplyResult{}, err
|
||||
}
|
||||
changes = append([]filter.DesiredChange(nil), changes...)
|
||||
for index := range changes {
|
||||
@@ -259,11 +259,10 @@ func (e *Engine) ExecuteSync(ctx context.Context, snapshot filter.Snapshot, chan
|
||||
}
|
||||
plan, err := e.adapter.Compile(snapshot, changes)
|
||||
if err != nil {
|
||||
return err
|
||||
return filter.ApplyResult{}, err
|
||||
}
|
||||
plan.CommandOnly = true
|
||||
_, err = e.adapter.Apply(ctx, plan)
|
||||
return err
|
||||
return e.adapter.Apply(ctx, plan)
|
||||
}
|
||||
|
||||
func (e *Engine) Execute(ctx context.Context, snapshot filter.Snapshot, changes []filter.DesiredChange) (filter.BackendPlan, filter.VerifyResult, error) {
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
|
||||
type Operator struct {
|
||||
client Client
|
||||
RunAction func(operation, name string, action func() error) error
|
||||
}
|
||||
|
||||
// DockerRestartError reports that the requested firewall operation completed,
|
||||
@@ -54,11 +55,18 @@ func NewOperator(client Client) *Operator {
|
||||
return &Operator{client: client}
|
||||
}
|
||||
|
||||
func (o *Operator) runAction(operation, name string, action func() error) error {
|
||||
if o.RunAction != nil {
|
||||
return o.RunAction(operation, name, action)
|
||||
}
|
||||
return action()
|
||||
}
|
||||
|
||||
func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareStart func(Client) error) error {
|
||||
var recoveryErrors []error
|
||||
switch operation {
|
||||
case OperationStart:
|
||||
if err := o.client.Start(); err != nil {
|
||||
if err := o.runAction("Start", o.client.Name(), o.client.Start); err != nil {
|
||||
return err
|
||||
}
|
||||
if prepareStart != nil {
|
||||
@@ -69,7 +77,7 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
|
||||
case OperationStop:
|
||||
return o.StopWithPrepare(withDockerRestart, nil)
|
||||
case OperationRestart:
|
||||
if err := o.client.Restart(); err != nil {
|
||||
if err := o.runAction("TaskRestart", o.client.Name(), o.client.Restart); err != nil {
|
||||
return err
|
||||
}
|
||||
if prepareStart != nil {
|
||||
@@ -82,12 +90,12 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
|
||||
}
|
||||
|
||||
if withDockerRestart {
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
if err := o.runAction("TaskRestart", "Docker", func() error { return controller.HandleRestart("docker") }); err != nil {
|
||||
recoveryErrors = append(recoveryErrors, &DockerRestartError{Err: err})
|
||||
}
|
||||
}
|
||||
if o.client.Name() == ProviderFirewalld && operation == OperationStart {
|
||||
if err := restoreFail2BanAfterFirewallStart(); err != nil {
|
||||
if err := o.runAction("TaskRecover", "Fail2Ban", restoreFail2BanAfterFirewallStart); err != nil {
|
||||
recoveryErrors = append(recoveryErrors, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export namespace Firewall {
|
||||
queued?: boolean;
|
||||
}
|
||||
export interface FirewallBase {
|
||||
lifecycleTaskID?: string;
|
||||
name: string;
|
||||
backend: string;
|
||||
conflictBackend?: string;
|
||||
|
||||
@@ -16,7 +16,11 @@ export const searchForwardRule = (request: Firewall.ForwardRuleSearch) =>
|
||||
http.post<ResPage<Firewall.RuleInfo>>('/hosts/firewall/forward/search', request, TimeoutEnum.T_40S);
|
||||
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean) =>
|
||||
http.post('/hosts/firewall/operate', { operation, withDockerRestart }, TimeoutEnum.T_10M);
|
||||
http.post<Firewall.FilterChainOperationResult>(
|
||||
'/hosts/firewall/operate',
|
||||
{ operation, withDockerRestart },
|
||||
TimeoutEnum.T_10M,
|
||||
);
|
||||
|
||||
export const operateForwardRule = (request: { rules: Firewall.RuleForward[]; forceDelete?: boolean }) =>
|
||||
http.postWithConfig<Firewall.FilterChainOperationResult>('/hosts/firewall/forward/operate', request, {
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
v-node-admin
|
||||
type="primary"
|
||||
v-if="baseInfo.isActive"
|
||||
:disabled="lifecycleBusy"
|
||||
@click="onOperate('stop')"
|
||||
link
|
||||
>
|
||||
@@ -79,14 +80,35 @@
|
||||
:content="$t('firewall.firewallNotStart')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button v-permission v-node-admin type="primary" @click="onOperate('start')" link>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
:disabled="lifecycleBusy"
|
||||
@click="onOperate('start')"
|
||||
link
|
||||
>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
:disabled="lifecycleBusy"
|
||||
@click="onOperate('restart')"
|
||||
link
|
||||
>
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-button>
|
||||
<template v-if="baseInfo.lifecycleTaskID">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="openLifecycleTask">
|
||||
{{ $t('commons.status.executing') }}
|
||||
<el-icon class="ml-1"><Document /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="isDirectManaged">
|
||||
<el-divider v-if="isDirectBase || !anyFamilyBound" direction="vertical" />
|
||||
@@ -186,9 +208,9 @@ import NoSuchService from '@/components/layout-content/no-such-service.vue';
|
||||
import DockerRestart from '@/components/docker-proxy/docker-restart.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, ref } from 'vue';
|
||||
import { loadDockerStatus } from '@/api/modules/container';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
import { Document, 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';
|
||||
@@ -222,6 +244,11 @@ const dockerStatus = ref();
|
||||
const withDockerRestart = ref(false);
|
||||
const familyRetrying = ref(false);
|
||||
const taskLogRef = ref();
|
||||
const lifecycleSubmitting = ref(false);
|
||||
const lifecycleBusy = computed(() => lifecycleSubmitting.value || !!baseInfo.value.lifecycleTaskID);
|
||||
let lifecycleTimer: ReturnType<typeof setTimeout>;
|
||||
let disposed = false;
|
||||
let baseInfoRequestID = 0;
|
||||
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');
|
||||
@@ -293,9 +320,13 @@ const emit = defineEmits([
|
||||
]);
|
||||
|
||||
const loadBaseInfo = async (search: boolean) => {
|
||||
const loader = props.currentTab === 'forward' ? loadForwardBaseInfo() : loadFireBaseInfo(props.currentTab);
|
||||
await loader
|
||||
.then(async (res) => {
|
||||
if (disposed) return;
|
||||
const requestID = ++baseInfoRequestID;
|
||||
clearTimeout(lifecycleTimer);
|
||||
try {
|
||||
const res = await (props.currentTab === 'forward' ? loadForwardBaseInfo() : loadFireBaseInfo(props.currentTab));
|
||||
if (disposed || requestID !== baseInfoRequestID) return;
|
||||
const lifecycleCompleted = Boolean(baseInfo.value.lifecycleTaskID) && !res.data.lifecycleTaskID;
|
||||
baseInfo.value = {
|
||||
...res.data,
|
||||
ipv4: res.data.ipv4 || { available: true, initialized: res.data.isInit, bound: res.data.isBind },
|
||||
@@ -316,19 +347,23 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
emit('update:is-bind', isDirectManaged.value ? anyFamilyBound.value : baseInfo.value.isBind);
|
||||
emit('update:version', baseInfo.value.version);
|
||||
|
||||
if (search) {
|
||||
if ((search || lifecycleCompleted) && !baseInfo.value.lifecycleTaskID) {
|
||||
await nextTick();
|
||||
emit('search');
|
||||
} else {
|
||||
emit('update:loading', false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
} catch {
|
||||
if (disposed || requestID !== baseInfoRequestID) return;
|
||||
emit('update:loading', false);
|
||||
emit('update:is-init', false);
|
||||
emit('update:name', '-');
|
||||
emit('update:version', '');
|
||||
});
|
||||
} finally {
|
||||
if (!disposed && requestID === baseInfoRequestID && baseInfo.value.lifecycleTaskID) {
|
||||
lifecycleTimer = setTimeout(() => loadBaseInfo(false), 3000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadDocker = async () => {
|
||||
@@ -434,34 +469,53 @@ const onUnBind = async () => {
|
||||
};
|
||||
|
||||
const onOperate = async (op: string) => {
|
||||
if (lifecycleBusy.value) return;
|
||||
operation.value = op;
|
||||
if (backendName.value === 'iptables' || backendName.value === 'nftables' || !dockerStatus.value) {
|
||||
emit('update:loading', true);
|
||||
await operateFire(operation.value, false)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
})
|
||||
.catch(() => {
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
await submitLifecycleOperation(false);
|
||||
} else {
|
||||
dockerRef.value.acceptParams({ title: i18n.global.t('firewall.dockerRestart') });
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
emit('update:loading', true);
|
||||
await operateFire(operation.value, withDockerRestart.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
})
|
||||
.catch(() => {
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
await submitLifecycleOperation(withDockerRestart.value);
|
||||
};
|
||||
|
||||
const openLifecycleTask = () => {
|
||||
if (baseInfo.value.lifecycleTaskID) {
|
||||
taskLogRef.value?.openWithTaskID(baseInfo.value.lifecycleTaskID, true);
|
||||
}
|
||||
};
|
||||
|
||||
const submitLifecycleOperation = async (restartDocker: boolean) => {
|
||||
if (lifecycleBusy.value) return;
|
||||
lifecycleSubmitting.value = true;
|
||||
emit('update:loading', true);
|
||||
try {
|
||||
const result = (await operateFire(operation.value, restartDocker)).data;
|
||||
if (disposed) return;
|
||||
if (result?.queued && result.taskID) {
|
||||
baseInfo.value.lifecycleTaskID = result.taskID;
|
||||
emit('update:loading', false);
|
||||
openLifecycleTask();
|
||||
await loadBaseInfo(false);
|
||||
return;
|
||||
}
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadBaseInfo(true);
|
||||
} catch {
|
||||
if (!disposed) await loadBaseInfo(true);
|
||||
} finally {
|
||||
lifecycleSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true;
|
||||
clearTimeout(lifecycleTimer);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user