diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go index 1415769df..52b646c4d 100644 --- a/agent/app/service/firewall.go +++ b/agent/app/service/firewall.go @@ -1015,7 +1015,9 @@ func (s *FirewallService) Create( return dto.FirewallRuleCreateResponse{}, err } taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("FirewallCreateRulesStep"), func(t *task.Task) error { - _, err := s.runCreateTask(t, request) + firewallRuleMutationMu.Lock() + defer firewallRuleMutationMu.Unlock() + _, err := s.createRules(t.TaskCtx, request, t) return err }, nil, 0, 0) if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil { @@ -1031,12 +1033,6 @@ func (s *FirewallService) Create( return dto.FirewallRuleCreateResponse{TaskID: taskItem.TaskID, Queued: true}, nil } -func (s *FirewallService) runCreateTask(t *task.Task, request dto.FirewallRuleCreate) (dto.FirewallRuleCreateResponse, error) { - firewallRuleMutationMu.Lock() - defer firewallRuleMutationMu.Unlock() - return s.createRules(t.TaskCtx, request, t) -} - func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallRuleCreate, t *task.Task) (result dto.FirewallRuleCreateResponse, taskErr error) { var firstFailure error defer func() { @@ -1412,7 +1408,13 @@ func (s *FirewallService) Delete(ctx context.Context, request dto.FirewallRuleDe return dto.FirewallRuleDeleteResponse{}, err } taskItem.AddSubTaskWithOps(taskItem.Name, func(t *task.Task) error { - _, err := s.runDeleteTask(t, request) + t.Logf("rules=%d", len(request.UUIDs)+len(request.BeforeRules)) + firewallRuleMutationMu.Lock() + defer firewallRuleMutationMu.Unlock() + if err := t.TaskCtx.Err(); err != nil { + return err + } + _, err := s.deleteRules(t.TaskCtx, request, t) return err }, nil, 0, 0) if err := repo.NewITaskRepo().Save(context.Background(), taskItem.Task); err != nil { @@ -1428,16 +1430,6 @@ func (s *FirewallService) Delete(ctx context.Context, request dto.FirewallRuleDe return dto.FirewallRuleDeleteResponse{TaskID: taskItem.TaskID, Queued: true}, nil } -func (s *FirewallService) runDeleteTask(t *task.Task, request dto.FirewallRuleDelete) (dto.FirewallRuleDeleteResponse, error) { - t.Logf("rules=%d", len(request.UUIDs)+len(request.BeforeRules)) - firewallRuleMutationMu.Lock() - defer firewallRuleMutationMu.Unlock() - if err := t.TaskCtx.Err(); err != nil { - return dto.FirewallRuleDeleteResponse{}, err - } - return s.deleteRules(t.TaskCtx, request, t) -} - func (s *FirewallService) deleteRules(ctx context.Context, request dto.FirewallRuleDelete, t *task.Task) (result dto.FirewallRuleDeleteResponse, taskErr error) { var firstFailure error defer func() { @@ -1619,7 +1611,7 @@ func (s *FirewallService) prepareDelete( } return preparedFirewallRuleDelete{}, err } - if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil { + if err := checkFirewallRuleWhitelistProtection(selectedProvider, stored); err != nil { return preparedFirewallRuleDelete{}, err } if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted { @@ -1754,7 +1746,11 @@ func (s *FirewallService) updateRuleDescription(ctx context.Context, ruleUUID, d if err != nil { return err } - if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil { + selected, err := s.selectedProviderForStoredRule(ctx, stored) + if err != nil { + return err + } + if err := checkFirewallRuleWhitelistProtection(selected, stored); err != nil { return err } if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted { @@ -1900,7 +1896,7 @@ func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string) error if err != nil { return err } - if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil { + if err := checkFirewallRuleWhitelistProtection(selected, stored); err != nil { return err } type appliedDelete struct { @@ -2212,9 +2208,6 @@ func (s *FirewallService) loadManagedMutation( if err != nil { return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err } - if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil { - return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err - } if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted { return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, fmt.Errorf("%w: only created or adopted rules can be changed", filter.ErrInvalidRule) @@ -2223,6 +2216,9 @@ func (s *FirewallService) loadManagedMutation( if err != nil { return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err } + if err := checkFirewallRuleWhitelistProtection(selected, stored); err != nil { + return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err + } desiredRules, err := s.compileStoredFirewallRules(ctx, stored, selected) if err != nil { return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err @@ -2617,16 +2613,27 @@ func (s *FirewallService) sequenceForFirewallRulePosition( byUUID := make(map[string]model.FirewallRule, len(stored)) for _, record := range stored { byUUID[record.UUID] = record + compiled, err := s.compileStoredFirewallRules(ctx, record, snapshot.Scope.Provider) + if err != nil { + if isFirewallPolicyIncompatible(err) { + continue + } + return 0, err + } + for _, desired := range compiled { + if desired.Rule.Scope.Key() == snapshot.Scope.Key() { + byUUID[desired.Rule.UUID] = record + } + } } var previous, next *model.FirewallRule - needsRebalance := false for _, observed := range snapshot.Rules { uuid := strings.TrimPrefix(observed.Marker, "1panel-rule:") if observed.Marker == uuid || uuid == excludedUUID || observed.Locator.Position == nil { continue } record, exists := byUUID[uuid] - if !exists { + if !exists || record.UUID == excludedUUID { continue } position := *observed.Locator.Position @@ -2640,30 +2647,24 @@ func (s *FirewallService) sequenceForFirewallRulePosition( } } if previous != nil && previous.Sequence == nil || next != nil && next.Sequence == nil { - needsRebalance = true + return s.rebalanceFirewallRuleSequences(ctx, snapshot, targetPosition, excludedUUID, byUUID) } - if !needsRebalance && current != nil && + if current != nil && (previous == nil || *previous.Sequence < *current) && (next == nil || *current < *next.Sequence) { return *current, nil } - if !needsRebalance { - switch { - case previous == nil && next == nil: - return model.FirewallRuleSequenceStep, nil - case previous == nil: - return *next.Sequence - model.FirewallRuleSequenceStep, nil - case next == nil: - return *previous.Sequence + model.FirewallRuleSequenceStep, nil - case *next.Sequence-*previous.Sequence > 1: - return *previous.Sequence + (*next.Sequence-*previous.Sequence)/2, nil - default: - needsRebalance = true - } - } - if needsRebalance { + switch { + case previous == nil && next == nil: + return model.FirewallRuleSequenceStep, nil + case previous == nil: + return *next.Sequence - model.FirewallRuleSequenceStep, nil + case next == nil: + return *previous.Sequence + model.FirewallRuleSequenceStep, nil + case *next.Sequence-*previous.Sequence > 1: + return *previous.Sequence + (*next.Sequence-*previous.Sequence)/2, nil + default: return s.rebalanceFirewallRuleSequences(ctx, snapshot, targetPosition, excludedUUID, byUUID) } - return 0, fmt.Errorf("%w: cannot allocate firewall rule sequence", filter.ErrRuleOperation) } func (s *FirewallService) rebalanceFirewallRuleSequences( @@ -2674,6 +2675,7 @@ func (s *FirewallService) rebalanceFirewallRuleSequences( byUUID map[string]model.FirewallRule, ) (int64, error) { targetSequence := int64(targetPosition) * model.FirewallRuleSequenceStep + updated := make(map[string]bool) for _, observed := range snapshot.Rules { if observed.Locator.Position == nil { continue @@ -2683,9 +2685,10 @@ func (s *FirewallService) rebalanceFirewallRuleSequences( continue } record, exists := byUUID[uuid] - if !exists { + if !exists || record.UUID == excludedUUID || updated[record.UUID] { continue } + updated[record.UUID] = true position := *observed.Locator.Position if excludedUUID == "" && position >= targetPosition { position++ @@ -2825,7 +2828,11 @@ func firewallRuleSnapshotPolicy(ctx context.Context, snapshot filter.Snapshot) ( } func firewallRuleSelectedProvider(context.Context) (filter.Provider, error) { - return selectedRuleProvider() + provider, err := selectedSystemFirewallProvider() + if err != nil { + return "", fmt.Errorf("%w: %v", filter.ErrProviderUnavailable, err) + } + return filter.Provider(provider), nil } func rollbackFirewallPlan(ctx context.Context, runtime *filterruntime.Engine, plan filter.BackendPlan, cause error) error { @@ -2838,14 +2845,6 @@ func rollbackFirewallPlan(ctx context.Context, runtime *filterruntime.Engine, pl return cause } -func selectedRuleProvider() (filter.Provider, error) { - provider, err := selectedSystemFirewallProvider() - if err != nil { - return "", fmt.Errorf("%w: %v", filter.ErrProviderUnavailable, err) - } - return filter.Provider(provider), nil -} - func ensureFirewallPorts(ports []int) error { filterruntime.InvalidateInventory() defer filterruntime.InvalidateInventory() @@ -2858,7 +2857,7 @@ func ensureFirewallPorts(ports []int) error { return err } if state.Name == constant.FirewallProviderIptables || state.Name == constant.FirewallProviderNftables { - isInit, _, err := loadDirectFirewallInitStatus(state.Name) + isInit, _, err := loadFirewallInitStatus(state.Name, "base") if err != nil { return err } @@ -2934,10 +2933,6 @@ func newNftablesHelperManager() *nftables_helper.Manager { } } -func loadDirectFirewallInitStatus(provider string) (bool, bool, error) { - return loadFirewallInitStatus(provider, "base") -} - func loadFirewallInitStatus(provider, tab string) (bool, bool, error) { switch provider { case constant.FirewallProviderNftables: @@ -2968,7 +2963,7 @@ func (s *FirewallService) restoreFirewallAfterStart(client lifecycle.Client) err } } if provider == filter.ProviderIptables || provider == filter.ProviderNftables { - isInit, _, err := loadDirectFirewallInitStatus(string(provider)) + isInit, _, err := loadFirewallInitStatus(string(provider), "base") if err != nil { recordFailure("load managed chain status", err) return errors.Join(recoveryErrors...) diff --git a/agent/app/service/firewall_setting.go b/agent/app/service/firewall_setting.go index ee450eda2..e8a5bcb5d 100644 --- a/agent/app/service/firewall_setting.go +++ b/agent/app/service/firewall_setting.go @@ -137,12 +137,12 @@ func savePortWhitelist(ctx context.Context, change func([]firewall.PortWhitelist }) } -func checkFirewallRuleWhitelistProtection(ctx context.Context, record model.FirewallRule) error { +func checkFirewallRuleWhitelistProtection(provider filter.Provider, record model.FirewallRule) error { ports, err := loadFirewallPortWhiteList() if err != nil { return err } - rules, err := record.RulesForProvider(filter.ProviderIptables) + rules, err := record.RulesForProvider(provider) if err != nil { return err } diff --git a/agent/app/service/firewall_sync.go b/agent/app/service/firewall_sync.go index 72f7e5920..193c2c317 100644 --- a/agent/app/service/firewall_sync.go +++ b/agent/app/service/firewall_sync.go @@ -106,6 +106,7 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto } for _, native := range desired { rule := native.Rule + native.Protected = filter.RuleMatchesPortWhitelist(rule, ports) rules = append(rules, &firewallSyncRule{desired: native, FirewallRuleSyncItem: dto.FirewallRuleSyncItem{SourceUUID: record.UUID, Rule: &rule}}) } } @@ -210,7 +211,7 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto } drifted := firewallsync.RuleOrder(snapshot, ordered) for _, rule := range scoped { - if !drifted[rule.desired.Marker] { + if rule.desired.Protected || !drifted[rule.desired.Marker] { continue } if rule.Status == firewallsync.StatusExisting { @@ -586,7 +587,12 @@ func (s *FirewallService) syncSystemRules( return dto.FirewallRuleSyncResult{}, err } if running.Executing { - return runningFirewallRuleSyncResult(request, running.TaskID), nil + return dto.FirewallRuleSyncResult{ + Subsystem: firewallSyncSubsystem(request.Subsystem), + TargetProvider: request.TargetProvider, + TaskID: running.TaskID, + Queued: true, + }, nil } if firewallSyncSubsystem(request.Subsystem) != "system" { return dto.FirewallRuleSyncResult{}, fmt.Errorf("%w: firewall synchronization tasks are only available for the system firewall", filter.ErrInvalidRule) @@ -660,15 +666,6 @@ func currentFirewallRuleSyncTaskLocked() (dto.FirewallRuleSyncTask, error) { return dto.FirewallRuleSyncTask{TaskID: record.ID, Executing: true}, nil } -func runningFirewallRuleSyncResult(request dto.FirewallRuleSyncRequest, taskID string) dto.FirewallRuleSyncResult { - return dto.FirewallRuleSyncResult{ - Subsystem: firewallSyncSubsystem(request.Subsystem), - TargetProvider: request.TargetProvider, - TaskID: taskID, - Queued: true, - } -} - func whitelistRules(provider filter.Provider, ports, required []firewall.SystemPort) []filter.FirewallRule { rules := make([]filter.FirewallRule, 0, len(ports)+len(required)) for _, port := range required { @@ -747,6 +744,10 @@ func (s *FirewallService) SyncPortWhitelist(ctx context.Context) error { if len(failures) > 0 { return errors.Join(failures...) } + if provider == filter.ProviderUFW && len(prepared) > 0 { + return s.syncUFWPortWhitelist(ctx, prepared) + } + for _, item := range prepared { if err := ctx.Err(); err != nil { return err @@ -759,6 +760,55 @@ func (s *FirewallService) SyncPortWhitelist(ctx context.Context) error { return errors.Join(failures...) } +func (s *FirewallService) syncUFWPortWhitelist(ctx context.Context, prepared []preparedFirewallRuleCreate) error { + firewallRuleMutationMu.Lock() + defer firewallRuleMutationMu.Unlock() + runtime := prepared[0].runtime + snapshots, err := runtime.ObserveScopes(ctx, filter.ManagedInputScopes(filter.ProviderUFW)) + if err != nil { + return err + } + for itemIndex, item := range prepared { + if err := ctx.Err(); err != nil { + return err + } + rule := item.request.Rule + var snapshot *filter.Snapshot + for index := range snapshots { + if snapshots[index].Scope.Key() == rule.Scope.Key() { + snapshot = &snapshots[index] + break + } + } + if snapshot == nil { + return fmt.Errorf("%w: missing UFW whitelist scope %s", filter.ErrInventoryUnavailable, rule.Scope.Key()) + } + for _, notice := range snapshot.Notices { + if notice.Code == filter.ScopeNoticeManagedScopeInactive || notice.Code == filter.ScopeNoticeManagedScopeMissing { + return filter.ErrProviderUnavailable + } + } + added, err := s.addWhitelistRuleFromSnapshot(ctx, item, *snapshot) + if err != nil { + return fmt.Errorf("add whitelist rule %s %s/%s [%s]: %w", rule.Scope.Family, rule.DestinationPort, rule.Protocol, rule.SourceAddress, err) + } + if !added { + continue + } + + if itemIndex+1 < len(prepared) { + if err := ctx.Err(); err != nil { + return err + } + snapshots, err = runtime.ObserveScopes(ctx, filter.ManagedInputScopes(filter.ProviderUFW)) + if err != nil { + return err + } + } + } + return nil +} + func (s *FirewallService) addWhitelistRule(ctx context.Context, prepared preparedFirewallRuleCreate) error { firewallRuleMutationMu.Lock() defer firewallRuleMutationMu.Unlock() @@ -770,38 +820,85 @@ func (s *FirewallService) addWhitelistRule(ctx context.Context, prepared prepare if err != nil { return err } + _, err = s.addWhitelistRuleFromSnapshot(ctx, prepared, snapshot) + return err +} + +func (s *FirewallService) addWhitelistRuleFromSnapshot(ctx context.Context, prepared preparedFirewallRuleCreate, snapshot filter.Snapshot) (bool, error) { + rule, runtime := prepared.request.Rule, prepared.runtime for _, observed := range snapshot.Rules { if observed.ParseStatus == filter.ParseStatusSupported { if same, err := filter.SameRuleContent(rule, observed.Rule); err == nil && same { - return nil + return false, nil } } } if err := filter.CheckObservedRuleCollisions(snapshot, rule, nil); err != nil { - return err + return false, err } if rule.Scope.Chain == filter.BasicBeforeChain { + position := int64(1) + rule.OrderIndex = &position rule.UUID = uuid.NewString() - return runtime.ExecuteCreate(ctx, snapshot, []filter.DesiredChange{{Operation: filter.ChangeCreate, After: &rule}}) + return true, runtime.ExecuteCreate(ctx, snapshot, []filter.DesiredChange{{Operation: filter.ChangeCreate, After: &rule}}) } stored, err := s.rules.List(ctx) if err != nil { - return err + return false, err } + model.SortFirewallRules(stored, rule.Scope.Provider) + markers := make([]string, 0, len(stored)) + var existing *filter.DesiredRule + var firstSequence *int64 for _, record := range stored { compiled, err := s.compileStoredFirewallRules(ctx, record, rule.Scope.Provider) if err != nil { - continue + if isFirewallPolicyIncompatible(err) { + continue + } + return false, err } for _, candidate := range compiled { + if candidate.Rule.Scope.Key() == rule.Scope.Key() && candidate.Marker != "" { + markers = append(markers, candidate.Marker) + if firstSequence == nil && record.Sequence != nil { + firstSequence = record.Sequence + } + } if err := filter.CheckRuleCollision(rule, candidate.Rule); errors.Is(err, filter.ErrRuleOperation) { - return runtime.ExecuteCreate(ctx, snapshot, []filter.DesiredChange{{Operation: filter.ChangeCreate, After: &candidate.Rule}}) + copy := candidate + existing = © } else if err != nil { - return err + return false, err } } } - return s.applyCreateRules(ctx, runtime, snapshot, stored, []preparedFirewallRuleCreate{prepared})[0] + if existing != nil { + existing.Rule.OrderIndex = firewallsync.InsertionPosition(snapshot, markers, existing.Marker) + return true, runtime.ExecuteCreate(ctx, snapshot, []filter.DesiredChange{{ + Operation: filter.ChangeCreate, After: &existing.Rule, + Append: rule.Scope.Provider == filter.ProviderUFW && existing.Rule.OrderIndex == nil, + }}) + } + if rule.Scope.Provider != filter.ProviderFirewalld { + position := int64(1) + rule.OrderIndex = &position + record, err := firewallRuleModelForCreate(rule, prepared.request, constant.FirewallRuleOriginCreated) + if err != nil { + return false, err + } + sequence := model.FirewallRuleSequenceStep + if firstSequence != nil { + sequence = *firstSequence - model.FirewallRuleSequenceStep + } + record.UUID, record.Sequence = uuid.NewString(), &sequence + rule.UUID = record.UUID + if err := runtime.ExecuteCreate(ctx, snapshot, []filter.DesiredChange{{Operation: filter.ChangeCreate, After: &rule}}); err != nil { + return false, firewallCreateExecutionError(err) + } + return true, s.saveFirewallRule(ctx, &record) + } + return true, s.applyCreateRules(ctx, runtime, snapshot, stored, []preparedFirewallRuleCreate{prepared})[0] } type forwardingRuleSyncCandidate struct { @@ -813,7 +910,7 @@ func (s *ForwardingService) loadRuleSyncCandidates( ctx context.Context, targetProvider filter.Provider, ) (*forwarding.Manager, []forwardingRuleSyncCandidate, []forwarding.Rule, bool, error) { - target, err := s.manager() + target, err := s.managerFactory() if err != nil { return nil, nil, nil, false, err } diff --git a/agent/app/service/forward.go b/agent/app/service/forward.go index 10f3b9ef2..8c40d34f7 100644 --- a/agent/app/service/forward.go +++ b/agent/app/service/forward.go @@ -75,7 +75,7 @@ func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error) baseInfo := dto.FirewallSubsystemStatus{ Version: "-", Name: forwardingDisplayName(selected), Backend: selected, SyncError: lastForwardingSyncError(), } - manager, err := s.manager() + manager, err := s.managerFactory() if err != nil { if errors.Is(err, errForwardingBackendUnavailable) { baseInfo.Reason = constant.FirewallBackendNotInstalled @@ -124,7 +124,7 @@ func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, [ if err != nil { return 0, nil, err } - manager, err := s.manager() + manager, err := s.managerFactory() if err != nil { return 0, nil, err } @@ -244,7 +244,7 @@ func (s *ForwardingService) operateRules(ctx context.Context, request dto.Forwar func (s *ForwardingService) Enable() error { forwardingMutationMu.Lock() defer forwardingMutationMu.Unlock() - manager, err := s.manager() + manager, err := s.managerFactory() if err != nil { recordForwardingSyncError(err) return err @@ -283,7 +283,7 @@ func (s *ForwardingService) QueueInitialization( forwardingMutationMu.Lock() defer forwardingMutationMu.Unlock() var err error - manager, err = s.manager() + manager, err = s.managerFactory() if err != nil { recordForwardingSyncError(err) return err @@ -329,7 +329,7 @@ func (s *ForwardingService) Restore(ctx context.Context) error { } return err } - manager, err := s.manager() + manager, err := s.managerFactory() if err != nil { recordForwardingSyncError(err) return err @@ -349,7 +349,7 @@ func (s *ForwardingService) Restore(ctx context.Context) error { } func (s *ForwardingService) reconcile(rules []forwarding.Rule) error { - manager, err := s.manager() + manager, err := s.managerFactory() if err != nil { return err } @@ -548,10 +548,6 @@ func forwardingOperationsOnlyRemove(operations []dto.ForwardRuleOperation) bool return true } -func (s *ForwardingService) manager() (*forwarding.Manager, error) { - return s.managerFactory() -} - func newForwardingManager() (*forwarding.Manager, error) { return newForwardingManagerFor(configuredForwardingBackend()) } diff --git a/agent/utils/firewall/filter/providers/ufw/adapter.go b/agent/utils/firewall/filter/providers/ufw/adapter.go index 020d6802e..9c76c378e 100644 --- a/agent/utils/firewall/filter/providers/ufw/adapter.go +++ b/agent/utils/firewall/filter/providers/ufw/adapter.go @@ -336,8 +336,12 @@ func compileChange(snapshot filter.Snapshot, change filter.DesiredChange) (filte return filter.NativeRulePlan{}, fmt.Errorf("%w: create target is out of range", filter.ErrInvalidRule) } command := insertCommand(position, normalized, marker) - if change.Append || position == maximumObservedPosition(snapshot)+1 { + if !change.Append && normalized.OrderIndex != nil && position == 1 { + command = filter.NativeCommand{Executable: "ufw", Args: append([]string{"prepend"}, compileRuleArgs(normalized, marker)...)} + } else if change.Append || position == maximumObservedPosition(snapshot)+1 { command = commentCommand(normalized, marker) + } + if command.Args[0] != "insert" { plan.Expected.Locator.NativeID = "" plan.Expected.Locator.Position = nil } @@ -740,7 +744,7 @@ func validateCommand(command filter.NativeCommand) error { return nil } switch first { - case "insert", "allow", "deny", "reject": + case "insert", "prepend", "allow", "deny", "reject": return nil default: return fmt.Errorf("%w: unsupported ufw command %q", filter.ErrInvalidRule, first) diff --git a/agent/utils/firewall/filter/safety.go b/agent/utils/firewall/filter/safety.go index c5b876147..8eecd2312 100644 --- a/agent/utils/firewall/filter/safety.go +++ b/agent/utils/firewall/filter/safety.go @@ -42,6 +42,10 @@ func RuleMatchesPortWhitelist(rule FirewallRule, ports []PortWhitelist) bool { if err != nil || rule.Action != ActionAccept || rule.SourcePort != "" || rule.DestinationAddress != "" || rule.Interface != "" || len(rule.ConnectionStates) != 0 { return false } + if rule.Scope.Provider == ProviderFirewalld && (rule.NativeKind == NativeKindZonePort || + (rule.NativeKind == NativeKindRule && rule.Scope.Family == FamilyInet && rule.Priority == nil)) { + return false + } families := []Family{rule.Scope.Family} if rule.Scope.Family == FamilyInet { families = []Family{FamilyIPv4, FamilyIPv6} diff --git a/agent/utils/firewall/iptables_helper/manager.go b/agent/utils/firewall/iptables_helper/manager.go index 48005c21d..37c4a159b 100644 --- a/agent/utils/firewall/iptables_helper/manager.go +++ b/agent/utils/firewall/iptables_helper/manager.go @@ -61,7 +61,7 @@ func (m *Manager) Operate(operation firewall.BaseOperation) error { func (m *Manager) enableBase(prepare bool) error { if prepare { - if err := ensureBaseChains(); err != nil { + if err := ensureBaseChainsFamily(false); err != nil { return err } if err := m.initPreRules(); err != nil { @@ -71,7 +71,7 @@ func (m *Manager) enableBase(prepare bool) error { return err } } - if err := bindBaseChains(); err != nil { + if err := setBaseChainBindings(false, true); err != nil { return err } if prepare { @@ -97,10 +97,6 @@ func (m *Manager) disableBase() error { return m.updateSetting("IptablesStatus", constant.StatusDisable) } -func ensureBaseChains() error { - return ensureBaseChainsFamily(false) -} - func ensureBaseChainsFamily(ipv6 bool) error { commands, err := lifecycle.ResolveIptablesCommands() if err != nil { @@ -169,10 +165,6 @@ func cleanupBaseChains(ipv6 bool) error { return nil } -func bindBaseChains() error { - return setBaseChainBindings(false, true) -} - func setBaseChainBindings(ipv6, bind bool) error { commands, err := lifecycle.ResolveIptablesCommands() if err != nil { @@ -250,7 +242,7 @@ func RestoreBaseChains(requiredPorts []firewall.PortWhitelist) error { if err != nil { return err } - if err := ensureBaseChains(); err != nil { + if err := ensureBaseChainsFamily(false); err != nil { return err } input, err := buildBaseChainsRestoreScript(global.Dir.FirewallDir, false, requiredPorts...) @@ -320,6 +312,9 @@ func buildBaseChainsRestoreScript(firewallDir string, ipv6 bool, requiredPorts . } for _, rule := range defaults { if !containsIptablesRule(script.String(), rule) { + if strings.HasPrefix(rule, "-A "+BasicBeforeChain+" ") && strings.Contains(rule, " --dport ") { + rule = strings.Replace(rule, "-A "+BasicBeforeChain+" ", "-I "+BasicBeforeChain+" 1 ", 1) + } script.WriteString(rule + "\n") } } @@ -442,7 +437,7 @@ func buildRequiredPortsRestoreScript( for _, rule := range desired { line := iptablesSystemPortRuleLine(rule) if rule.Family == family && !containsIptablesRule(beforeRaw, line) { - commands = append(commands, line) + commands = append(commands, strings.Replace(line, "-A "+BasicBeforeChain+" ", "-I "+BasicBeforeChain+" 1 ", 1)) beforeRaw += "\n" + line } } diff --git a/agent/utils/firewall/nftables_helper/manager.go b/agent/utils/firewall/nftables_helper/manager.go index d520cd68a..4a70db197 100644 --- a/agent/utils/firewall/nftables_helper/manager.go +++ b/agent/utils/firewall/nftables_helper/manager.go @@ -7,6 +7,8 @@ import ( "net/netip" "os" "path/filepath" + "slices" + "strconv" "strings" "github.com/1Panel-dev/1Panel/agent/constant" @@ -113,7 +115,7 @@ func (m *Manager) ensureBaseChains() error { func requiredPortCommand(tableFamily string, rule firewall.SystemPort) []string { command := []string{ - "add", "rule", tableFamily, TableName, BasicBeforeChain, + "insert", "rule", tableFamily, TableName, BasicBeforeChain, } if rule.SourceAddress != "" { command = append(command, tableFamily, "saddr", rule.SourceAddress) @@ -174,6 +176,29 @@ func containsRequiredPortRule(output, expression string) bool { line = strings.NewReplacer("{", "", "}", "", ", ", ",", " ,", ",").Replace(line) fields := strings.Fields(line) for index, field := range fields { + if index >= 2 && fields[index-2] == "ct" && fields[index-1] == "state" { + states := strings.Split(field, ",") + for i, state := range states { + value, err := strconv.ParseUint(state, 0, 64) + if err != nil { + continue + } + switch value { + case 1: + states[i] = "invalid" + case 2: + states[i] = "established" + case 4: + states[i] = "related" + case 8: + states[i] = "new" + case 64: + states[i] = "untracked" + } + } + slices.Sort(states) + fields[index] = strings.Join(states, ",") + } if prefix, err := netip.ParsePrefix(field); err == nil { prefix = prefix.Masked() fields[index] = prefix.String() diff --git a/frontend/src/views/host/firewall/rule/index.vue b/frontend/src/views/host/firewall/rule/index.vue index 63eb959b0..1b2241b07 100644 --- a/frontend/src/views/host/firewall/rule/index.vue +++ b/frontend/src/views/host/firewall/rule/index.vue @@ -871,12 +871,8 @@ const loadAllInventoryItems = async () => { return response.data.items || []; }; -const resetPagination = () => { - paginationConfig.currentPage = 1; -}; - const searchWithReset = () => { - resetPagination(); + paginationConfig.currentPage = 1; return searchPage(); }; diff --git a/frontend/src/views/host/firewall/status/index.vue b/frontend/src/views/host/firewall/status/index.vue index 2ff0a1d5c..56f6eeecf 100644 --- a/frontend/src/views/host/firewall/status/index.vue +++ b/frontend/src/views/host/firewall/status/index.vue @@ -246,7 +246,6 @@ const familyRetrying = ref(false); const taskLogRef = ref(); const lifecycleSubmitting = ref(false); const lifecycleBusy = computed(() => lifecycleSubmitting.value || !!baseInfo.value.lifecycleTaskID); -let lifecycleTimer: ReturnType; let disposed = false; let baseInfoRequestID = 0; const backendName = computed(() => baseInfo.value.backend || baseInfo.value.name); @@ -306,7 +305,7 @@ const initActionHelper = computed(() => { }); const acceptParams = (): void => { - loadBaseInfo(true); + loadBaseInfo(); loadDocker(); }; const emit = defineEmits([ @@ -319,14 +318,12 @@ const emit = defineEmits([ 'update:version', ]); -const loadBaseInfo = async (search: boolean) => { +const loadBaseInfo = async () => { 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 }, @@ -347,7 +344,7 @@ const loadBaseInfo = async (search: boolean) => { emit('update:is-bind', isDirectManaged.value ? anyFamilyBound.value : baseInfo.value.isBind); emit('update:version', baseInfo.value.version); - if ((search || lifecycleCompleted) && !baseInfo.value.lifecycleTaskID) { + if (!baseInfo.value.lifecycleTaskID) { await nextTick(); emit('search'); } else { @@ -359,10 +356,6 @@ const loadBaseInfo = async (search: boolean) => { emit('update:is-init', false); emit('update:name', '-'); emit('update:version', ''); - } finally { - if (!disposed && requestID === baseInfoRequestID && baseInfo.value.lifecycleTaskID) { - lifecycleTimer = setTimeout(() => loadBaseInfo(false), 3000); - } } }; @@ -413,11 +406,11 @@ const onInit = async () => { } } MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - await loadBaseInfo(true); + await loadBaseInfo(); }; const handleInitializationTaskClose = () => { - loadBaseInfo(true); + loadBaseInfo(); }; const onBind = async () => { @@ -431,7 +424,7 @@ const onBind = async () => { } await operateFilterChain('1PANEL_BASIC', 'bind-base'); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - await loadBaseInfo(true); + await loadBaseInfo(); }; const onRetryFamilyIssues = async () => { @@ -448,7 +441,7 @@ const onRetryFamilyIssues = async () => { await operateFilterChain('1PANEL_BASIC', 'bind-base'); } MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - await loadBaseInfo(true); + await loadBaseInfo(); } finally { familyRetrying.value = false; } @@ -465,7 +458,7 @@ const onUnBind = async () => { } await operateFilterChain('1PANEL_BASIC', 'unbind-base'); MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - await loadBaseInfo(true); + await loadBaseInfo(); }; const onOperate = async (op: string) => { @@ -499,13 +492,12 @@ const submitLifecycleOperation = async (restartDocker: boolean) => { baseInfo.value.lifecycleTaskID = result.taskID; emit('update:loading', false); openLifecycleTask(); - await loadBaseInfo(false); return; } MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - await loadBaseInfo(true); + await loadBaseInfo(); } catch { - if (!disposed) await loadBaseInfo(true); + if (!disposed) await loadBaseInfo(); } finally { lifecycleSubmitting.value = false; } @@ -513,7 +505,6 @@ const submitLifecycleOperation = async (restartDocker: boolean) => { onBeforeUnmount(() => { disposed = true; - clearTimeout(lifecycleTimer); }); defineExpose({