fix(firewall): manage SSH access and queue stop operations (#13831)

* fix(firewall): manage SSH access and queue stop operations

* fix(firewall): reconcile whitelist rules and sync differences
This commit is contained in:
ssongliu
2026-09-16 16:45:33 +08:00
committed by GitHub
parent 86e4ed6f64
commit 782bc1e67c
12 changed files with 381 additions and 62 deletions

View File

@@ -232,11 +232,15 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
return response, err
}
if (client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW) ||
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationRestart)) {
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationStop) &&
request.Operation != string(lifecycle.OperationRestart)) {
return response, s.OperateFirewall(request)
}
operation, label := task.TaskExec, "Start"
if request.Operation == string(lifecycle.OperationRestart) {
switch lifecycle.Operation(request.Operation) {
case lifecycle.OperationStop:
label = "Stop"
case lifecycle.OperationRestart:
operation, label = task.TaskRestart, task.TaskRestart
}
name := task.GetTaskName(client.Name(), label, task.TaskScopeFirewall)
@@ -291,14 +295,17 @@ func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycl
return runFirewallLifecycleAction(t, task.GetTaskName(name, operation, ""), action)
}
operationErr := operator.Operate(lifecycle.Operation(request.Operation), request.WithDockerRestart, func(lifecycle.Client) error {
runFirewallLifecycleAction(t, i18n.GetWithName("FirewallRestoreRulesStep", client.Name()), func() error {
rulesErr := runFirewallLifecycleAction(t, i18n.GetWithName("FirewallRestoreRulesStep", client.Name()), func() error {
return s.restoreStoredFirewallRules(ctx, provider, t)
})
runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
whitelistErr := runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
return s.SyncPortWhitelist(ctx)
})
return nil
return errors.Join(rulesErr, whitelistErr)
})
if request.Operation == string(lifecycle.OperationStop) {
return operationErr
}
var recoveryErr *lifecycle.CompletedOperationError
if operationErr != nil && !errors.As(operationErr, &recoveryErr) {
return operationErr
@@ -1539,8 +1546,8 @@ func (s *FirewallService) prepareDelete(
}
return preparedFirewallRuleDelete{}, err
}
if isProtectedSystemFirewallRule(stored) {
return preparedFirewallRuleDelete{}, filter.ErrProtectedRule
if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil {
return preparedFirewallRuleDelete{}, err
}
if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted {
return preparedFirewallRuleDelete{}, fmt.Errorf("%w: only created or adopted rules can be deleted", filter.ErrInvalidRule)
@@ -1674,8 +1681,8 @@ func (s *FirewallService) updateRuleDescription(ctx context.Context, ruleUUID, d
if err != nil {
return err
}
if isProtectedSystemFirewallRule(stored) {
return filter.ErrProtectedRule
if err := checkFirewallRuleWhitelistProtection(ctx, stored); err != nil {
return err
}
if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted {
return fmt.Errorf("%w: only created or adopted rules can be changed", filter.ErrInvalidRule)
@@ -2145,9 +2152,8 @@ func (s *FirewallService) loadManagedMutation(
if err != nil {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err
}
if isProtectedSystemFirewallRule(stored) {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil,
filter.ErrProtectedRule
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,
@@ -2515,7 +2521,7 @@ func (s *FirewallService) systemPortRecords(ctx context.Context, port dto.Firewa
return records, nil
}
func isProtectedSystemFirewallRule(rule model.FirewallRule) bool {
func hasSystemFirewallRuleOwner(rule model.FirewallRule) bool {
acceptedPrefix := model.FirewallRuleOwner(
constant.FirewallRuleSourceSecurity,
constant.FirewallSystemAcceptedPortSourcePrefix,
@@ -2752,7 +2758,7 @@ func (s *FirewallService) compileRestorableFirewallRules(
if err != nil {
return nil, nil, err
}
if !supportsManagedFilterChains(string(provider)) || !isProtectedSystemFirewallRule(stored) {
if !supportsManagedFilterChains(string(provider)) || !hasSystemFirewallRuleOwner(stored) {
return compiled, nil, nil
}
loadRequired := s.requiredPorts
@@ -2792,8 +2798,12 @@ func (s *FirewallService) desiredFirewallRulesByScope(
model.SortFirewallRules(stored, provider)
desired := make(map[string][]filter.DesiredRule)
var failures []filter.InventoryItem
ports, protectionErr := firewallWhitelistForProtection(ctx)
for _, record := range stored {
compiled, _, err := s.compileRestorableFirewallRules(ctx, record, provider)
if err == nil {
err = protectionErr
}
if err != nil {
rule := filter.FirewallRule{
UUID: record.UUID,
@@ -2806,13 +2816,13 @@ func (s *FirewallService) desiredFirewallRulesByScope(
failures = append(failures, filter.InventoryItem{
Incompatible: isFirewallPolicyIncompatible(err),
Rule: rule, State: filter.InventoryStateDrifted, Match: filter.InventoryMatchNone,
Desired: &filter.DesiredRule{UUID: record.UUID, Rule: rule, Origin: filter.RuleOrigin(record.Origin), Protected: isProtectedSystemFirewallRule(record)},
Desired: &filter.DesiredRule{UUID: record.UUID, Rule: rule, Origin: filter.RuleOrigin(record.Origin), Protected: protectionErr != nil || filter.RuleMatchesPortWhitelist(rule, ports)},
Error: fmt.Sprintf("policy %s: %v", record.UUID, err),
})
continue
}
for _, rule := range compiled {
rule.Protected = isProtectedSystemFirewallRule(record)
rule.Protected = filter.RuleMatchesPortWhitelist(rule.Rule, ports)
rule.Expanded = len(compiled) > 1
key := rule.Rule.Scope.Key()
desired[key] = append(desired[key], rule)

View File

@@ -22,6 +22,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
filterruntime "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/runtime"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
@@ -233,7 +234,10 @@ func (s *FirewallSettingService) applyPortWhitelist(ctx context.Context, plan po
return err
}
return s.executePortWhitelist(ctx, plan, func() error {
return syncWhitelistRules(ctx, firewallService, previous, current, report)
if err := syncWhitelistRules(ctx, firewallService, previous, current, report); err != nil {
return err
}
return firewallService.reconcilePortWhitelist(ctx, provider, ready, report)
}, report)
}
@@ -408,6 +412,70 @@ func (s *FirewallService) deleteWhitelistRule(ctx context.Context, prepared prep
defer firewallRuleMutationMu.Unlock()
rule, runtime := prepared.request.Rule, prepared.runtime
provider := rule.Scope.Provider
ports, err := firewallWhitelistForProtection(ctx)
if err != nil {
return err
}
if rule.Scope.Chain != filter.BasicBeforeChain && filter.RuleMatchesPortWhitelist(rule, ports) {
return nil
}
stored, err := s.rules.List(ctx)
if err != nil {
return err
}
cleanup, _ := ctx.Value(firewallWhitelistCleanupKey{}).(bool)
if !cleanup || rule.Scope.Chain == filter.BasicBeforeChain {
if err := deleteNativeWhitelistRule(ctx, runtime, rule); err != nil {
return err
}
}
for _, record := range stored {
if cleanup && !hasSystemFirewallRuleOwner(record) {
continue
}
compiled, err := s.compileStoredFirewallRules(ctx, record, provider)
if err != nil {
continue
}
remaining := make([]filter.FirewallRule, 0, len(compiled))
for _, candidate := range compiled {
if !matchesWhitelistRemoval(rule, candidate.Rule, record) || filter.RuleMatchesPortWhitelist(candidate.Rule, ports) {
remaining = append(remaining, candidate.Rule)
continue
}
if candidate.Rule.Scope.Key() != rule.Scope.Key() || cleanup && rule.Scope.Chain != filter.BasicBeforeChain {
if err := deleteNativeWhitelistRule(ctx, runtime, candidate.Rule, candidate.Marker); err != nil {
return err
}
}
}
if len(remaining) == len(compiled) {
continue
}
if err := global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx := context.WithValue(ctx, constant.DB, tx)
if err := s.rules.DeleteWithRevision(ctx, record.UUID, record.Revision); err != nil {
return err
}
for _, rule := range remaining {
kept, err := model.FirewallRuleFromDomain(rule)
if err != nil {
return err
}
kept.UUID, kept.Origin, kept.Owner, kept.Sequence = rule.UUID, record.Origin, record.Owner, record.Sequence
if err := s.rules.Create(ctx, &kept); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
}
return nil
}
func deleteNativeWhitelistRule(ctx context.Context, runtime *filterruntime.Engine, rule filter.FirewallRule, markers ...string) error {
for {
if err := ctx.Err(); err != nil {
return err
@@ -418,6 +486,9 @@ func (s *FirewallService) deleteWhitelistRule(ctx context.Context, prepared prep
}
matched := -1
for index, observed := range snapshot.Rules {
if len(markers) > 0 && observed.Marker != markers[0] {
continue
}
if observed.ParseStatus == filter.ParseStatusSupported {
if same, err := filter.SameRuleContent(rule, observed.Rule); err == nil && same {
matched = index
@@ -445,45 +516,105 @@ func (s *FirewallService) deleteWhitelistRule(ctx context.Context, prepared prep
return filter.ErrVerificationFailed
}
}
return nil
}
type firewallWhitelistCleanupKey struct{}
func checkFirewallRuleWhitelistProtection(ctx context.Context, record model.FirewallRule) error {
ports, err := firewallWhitelistForProtection(ctx)
if err != nil {
return err
}
rules, err := record.RulesForProvider(filter.ProviderIptables)
if err != nil {
return err
}
for _, rule := range rules {
if filter.RuleMatchesPortWhitelist(rule, ports) {
return filter.ErrProtectedRule
}
}
return nil
}
func matchesWhitelistRemoval(requested, candidate filter.FirewallRule, record model.FirewallRule) bool {
if isDirectFirewallProvider(requested.Scope.Provider) && requested.Scope.Chain == filter.BasicBeforeChain && hasSystemFirewallRuleOwner(record) {
candidate.Scope.Chain = filter.BasicBeforeChain
}
return whitelistContainsRule([]filter.FirewallRule{requested}, candidate)
}
func isWhitelistPortAllowance(rule filter.FirewallRule) bool {
normalized, err := filter.NormalizeRule(rule)
return err == nil && normalized.Action == filter.ActionAccept &&
(normalized.Protocol == "tcp" || normalized.Protocol == "udp") && normalized.DestinationPort != "" &&
normalized.SourcePort == "" && normalized.DestinationAddress == "" && normalized.Interface == "" && len(normalized.ConnectionStates) == 0
}
func (s *FirewallService) reconcilePortWhitelist(ctx context.Context, provider filter.Provider,
ready func(dto.FirewallSystemPort) (bool, error), report whitelistReporter,
) error {
if !isDirectFirewallProvider(provider) {
return nil
}
ports, err := firewallWhitelistForProtection(ctx)
if err != nil {
return err
}
runtime, err := s.adapters.Resolve(provider)
if err != nil {
return err
}
var obsolete []filter.FirewallRule
for _, scope := range filter.ManagedInputScopes(provider) {
if scope.Chain != filter.BasicBeforeChain {
continue
}
active, err := ready(dto.FirewallSystemPort{Family: string(scope.Family)})
if err != nil {
return err
}
if !active {
continue
}
snapshot, err := runtime.ObserveMutation(ctx, scope)
if errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
for _, observed := range snapshot.Rules {
if observed.ParseStatus == filter.ParseStatusSupported && isWhitelistPortAllowance(observed.Rule) &&
!filter.RuleMatchesPortWhitelist(observed.Rule, ports) {
obsolete = append(obsolete, observed.Rule)
}
}
}
stored, err := s.rules.List(ctx)
if err != nil {
return err
}
for _, record := range stored {
if !hasSystemFirewallRuleOwner(record) {
continue
}
compiled, err := s.compileStoredFirewallRules(ctx, record, provider)
if err != nil {
continue
}
remaining := make([]filter.FirewallRule, 0, len(compiled))
for _, candidate := range compiled {
if !whitelistContainsRule([]filter.FirewallRule{rule}, candidate.Rule) {
remaining = append(remaining, candidate.Rule)
}
}
if len(remaining) == len(compiled) {
continue
}
if err := global.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx := context.WithValue(ctx, constant.DB, tx)
if err := s.rules.DeleteWithRevision(ctx, record.UUID, record.Revision); err != nil {
return err
}
for _, rule := range remaining {
kept, err := model.FirewallRuleFromDomain(rule)
if err != nil {
return err
}
kept.UUID, kept.Origin, kept.Owner, kept.Sequence = rule.UUID, record.Origin, record.Owner, record.Sequence
if err := s.rules.Create(ctx, &kept); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
for _, candidate := range compiled {
if isWhitelistPortAllowance(candidate.Rule) && !filter.RuleMatchesPortWhitelist(candidate.Rule, ports) {
obsolete = append(obsolete, candidate.Rule)
}
}
}
return nil
prepared, err := s.prepareWhitelistRules(ctx, provider, obsolete, ready, report)
if err != nil {
return err
}
return syncWhitelistRules(context.WithValue(ctx, firewallWhitelistCleanupKey{}, true), s, prepared, nil, report)
}
func (s *FirewallSettingService) preparePortWhitelist(ctx context.Context, change portWhitelistChange) (portWhitelistPlan, error) {

View File

@@ -21,6 +21,7 @@ import (
filterruntime "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/runtime"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -36,9 +37,10 @@ type firewallDatabaseSyncAdapter interface {
type firewallSyncRule struct {
dto.FirewallRuleSyncItem
desired filter.DesiredRule
observed *filter.ObservedRule
done bool
desired filter.DesiredRule
observed *filter.ObservedRule
done bool
whitelistCleanup bool
}
func firewallSyncSubsystem(value string) string {
@@ -81,7 +83,26 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
return nil, nil, nil, err
}
model.SortFirewallRules(stored, request.TargetProvider)
var whitelist []firewall.PortWhitelist
var whitelistDesired []filter.FirewallRule
if isDirectFirewallProvider(request.TargetProvider) {
ports, overridden := ctx.Value(firewallWhitelistOverrideKey{}).([]firewall.PortWhitelist)
if !overridden {
ports, err = loadFirewallPortWhiteList()
if err != nil {
return nil, nil, nil, err
}
}
required, err := firewall.RequiredPortWhitelist(ports)
if err != nil {
return nil, nil, nil, err
}
custom := customWhitelist(ports)
whitelist = append(custom, required...)
whitelistDesired = whitelistRules(request.TargetProvider, firewall.ExpandPortWhitelist(custom), firewall.ExpandPortWhitelist(required))
}
rules := make([]*firewallSyncRule, 0, len(stored))
var whitelistCleanup []*firewallSyncRule
preservedMarkers := make(map[string]bool)
compileFailed := false
for _, record := range stored {
@@ -95,10 +116,45 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
preservedMarkers[rule.Marker] = true
}
for _, native := range desired {
if isDirectFirewallProvider(request.TargetProvider) && hasSystemFirewallRuleOwner(record) &&
isWhitelistPortAllowance(native.Rule) && !filter.RuleMatchesPortWhitelist(native.Rule, whitelist) {
stale := native.Rule
whitelistCleanup = append(whitelistCleanup, &firewallSyncRule{whitelistCleanup: true, FirewallRuleSyncItem: dto.FirewallRuleSyncItem{
SourceUUID: record.UUID, Rule: &stale, Status: firewallsync.StatusRemove, Reason: "port allowance is no longer in the whitelist",
}})
preservedMarkers[native.Marker] = true
continue
}
rule := native.Rule
rules = append(rules, &firewallSyncRule{desired: native, FirewallRuleSyncItem: dto.FirewallRuleSyncItem{SourceUUID: record.UUID, Rule: &rule}})
}
}
for _, candidate := range whitelistDesired {
prepared, err := s.prepareCreate(ctx, request.TargetProvider, dto.FirewallRuleCreateItem{Rule: candidate})
if err != nil {
return nil, nil, nil, err
}
rule := prepared.request.Rule
duplicate := false
for _, existing := range rules {
if existing.Rule != nil && whitelistContainsRule([]filter.FirewallRule{*existing.Rule}, rule) {
duplicate = true
break
}
}
if duplicate {
continue
}
key, err := filter.RuleKey(rule)
if err != nil {
return nil, nil, nil, err
}
rule.UUID = uuid.NewSHA1(uuid.NameSpaceOID, []byte(key)).String()
rules = append(rules, &firewallSyncRule{
desired: filter.DesiredRule{UUID: rule.UUID, Rule: rule, RuleKey: key, Origin: filter.RuleOriginCreated, Protected: true},
FirewallRuleSyncItem: dto.FirewallRuleSyncItem{SourceUUID: rule.UUID, Rule: &rule},
})
}
snapshots := make([]filter.Snapshot, 0)
for _, scope := range filter.ManagedInputScopes(request.TargetProvider) {
desired := make([]filter.DesiredRule, 0)
@@ -125,6 +181,16 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
return nil, nil, nil, err
}
snapshots = append(snapshots, snapshot)
if isDirectFirewallProvider(request.TargetProvider) && scope.Chain == filter.BasicBeforeChain {
for _, observed := range snapshot.Rules {
if observed.ParseStatus == filter.ParseStatusSupported && isWhitelistPortAllowance(observed.Rule) && !filter.RuleMatchesPortWhitelist(observed.Rule, whitelist) {
stale := observed.Rule
whitelistCleanup = append(whitelistCleanup, &firewallSyncRule{whitelistCleanup: true, FirewallRuleSyncItem: dto.FirewallRuleSyncItem{
SourceUUID: strings.TrimPrefix(observed.Marker, "1panel-rule:"), Rule: &stale, Status: firewallsync.StatusRemove, Reason: "port allowance is no longer in the whitelist",
}})
}
}
}
inventory, err := filter.MergeInventory(filter.InventoryMergeInput{Observed: snapshot.Rules, Desired: desired})
if err != nil {
return nil, nil, nil, err
@@ -180,10 +246,25 @@ func (s *FirewallService) loadFirewallSyncRules(ctx context.Context, request dto
}
}
}
return runtime, rules, snapshots, nil
return runtime, append(rules, whitelistCleanup...), snapshots, nil
}
func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.FirewallRuleSyncRequest, t *task.Task) (result dto.FirewallRuleSyncResult, err error) {
if isDirectFirewallProvider(request.TargetProvider) {
if request.SourceProvider != "" || request.ResetSource {
return result, fmt.Errorf("%w: system synchronization reads rules from the database", filter.ErrInvalidRule)
}
if err := s.checkSelectedProvider(ctx, request.TargetProvider); err != nil {
return result, err
}
whitelistErr := s.SyncPortWhitelist(ctx)
if t != nil {
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
}
if whitelistErr != nil {
return result, whitelistErr
}
}
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
result = dto.FirewallRuleSyncResult{Subsystem: "system", TargetProvider: request.TargetProvider}
@@ -194,6 +275,11 @@ func (s *FirewallService) syncRules(ctx context.Context, _ string, request dto.F
if err != nil {
return result, err
}
for _, rule := range rules {
if rule.whitelistCleanup {
return result, fmt.Errorf("%w: whitelist cleanup is incomplete; check that the firewall family is initialized and active", filter.ErrRuleOperation)
}
}
created, removed, unexecuted := 0, 0, 0
stopped := make(map[string]error)
failedRemovals := make(map[string]error)
@@ -618,6 +704,8 @@ func runningFirewallRuleSyncResult(request dto.FirewallRuleSyncRequest, taskID s
}
func (s *FirewallService) SyncPortWhitelist(ctx context.Context) error {
firewallWhitelistTaskMu.Lock()
defer firewallWhitelistTaskMu.Unlock()
filterruntime.InvalidateInventory()
defer filterruntime.InvalidateInventory()
ports, err := loadFirewallPortWhiteList()
@@ -645,8 +733,13 @@ func (s *FirewallService) SyncPortWhitelist(ctx context.Context) error {
}
rules := whitelistRules(provider, firewall.ExpandPortWhitelist(customWhitelist(ports)), firewall.ExpandPortWhitelist(required))
prepared, prepareErr := s.prepareWhitelistRules(ctx, provider, rules, ready, report)
syncErr := syncWhitelistRules(ctx, s, nil, prepared, report)
return errors.Join(prepareErr, syncErr)
if prepareErr != nil {
return prepareErr
}
if err := syncWhitelistRules(ctx, s, nil, prepared, report); err != nil {
return err
}
return s.reconcilePortWhitelist(ctx, provider, ready, report)
}
func (s *FirewallService) SyncSystemPorts(ctx context.Context, previous, current []dto.FirewallSystemPort) error {

View File

@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/init/migration/migrations"
migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
@@ -28,7 +29,7 @@ func Init() {
clientName := client.Name()
initialize := false
defer func() {
if err := service.NewIFirewallService().SyncPortWhitelist(ctx); err != nil {
if err := migrations.TransferFirewalldSSHService(ctx, client, service.NewIFirewallService().SyncPortWhitelist); err != nil {
global.LOG.Warnf("synchronize firewall whitelist on startup failed, err: %v", err)
}
if initialize {

View File

@@ -1,6 +1,7 @@
package migrations
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -10,11 +11,52 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"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/lifecycle/providers"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const firewalldSSHServiceMigrationID = "20260916-remove-firewalld-ssh-service"
func TransferFirewalldSSHService(ctx context.Context, client lifecycle.Client, syncWhitelist func(context.Context) error) error {
return transferFirewalldSSHService(ctx, global.DB, client, syncWhitelist)
}
func transferFirewalldSSHService(ctx context.Context, db *gorm.DB, client lifecycle.Client, syncWhitelist func(context.Context) error) error {
if err := syncWhitelist(ctx); err != nil {
return err
}
if client.Name() != lifecycle.ProviderFirewalld {
return nil
}
var count int64
if err := db.WithContext(ctx).Table("migrations").Where("id = ?", firewalldSSHServiceMigrationID).Count(&count).Error; err != nil {
return fmt.Errorf("check firewalld SSH service migration: %w", err)
}
if count > 0 {
return nil
}
active, err := client.Status()
if err != nil || !active {
return err
}
if err := ctx.Err(); err != nil {
return err
}
if err := providers.RemoveFirewalldSSHService(); err != nil {
return fmt.Errorf("transfer firewalld SSH access to whitelist: %w", err)
}
if err := db.WithContext(ctx).Table("migrations").Clauses(clause.OnConflict{DoNothing: true}).
Create(map[string]interface{}{"id": firewalldSSHServiceMigrationID}).Error; err != nil {
return fmt.Errorf("record firewalld SSH service migration: %w", err)
}
return nil
}
var MigrateFirewallPortWhitelistSources = &gormigrate.Migration{
ID: "20260915-migrate-firewall-port-whitelist-sources",
Migrate: func(tx *gorm.DB) error {

View File

@@ -2,6 +2,7 @@ package manager
import (
"errors"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
@@ -18,5 +19,19 @@ func handlerErr(out string, err error) error {
}
func run(name string, args ...string) (string, error) {
return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...)
return runWithTimeout(10*time.Second, name, args...)
}
func runWithTimeout(timeout time.Duration, name string, args ...string) (string, error) {
return cmd.NewCommandMgr(cmd.WithTimeout(timeout), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...)
}
func serviceOperationTimeout(operation, serviceName string) time.Duration {
if operation == "restart" {
switch strings.TrimSuffix(serviceName, ".service") {
case "docker", "dockerd", "docker.dockerd", "snap.docker.dockerd":
return 2 * time.Minute
}
}
return 10 * time.Second
}

View File

@@ -46,7 +46,7 @@ func (s *Openrc) Operate(operate, serviceName string) error {
case "disable":
return handlerErr(run("rc-update", "del", serviceName, "default"))
default:
return handlerErr(run(s.toolCmd, serviceName, operate))
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, serviceName, operate))
}
}

View File

@@ -49,7 +49,7 @@ func (s *Snap) IsEnable(serviceName string) bool {
func (s *Snap) Operate(operate, serviceName string) error {
if s.IsExist(serviceName) {
return handlerErr(run(s.toolCmd, operate, serviceName))
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, operate, serviceName))
}
return fmt.Errorf("snap service %q does not exist", serviceName)
}

View File

@@ -57,7 +57,7 @@ func (s *Systemd) Status(serviceName string) (string, error) {
return run(s.toolCmd, "status", serviceName)
}
func (s *Systemd) Operate(operate, serviceName string) error {
out, err := run(s.toolCmd, operate, serviceName)
out, err := runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, operate, serviceName)
if err != nil {
if fallbackName := systemdAliasFallbackName(serviceName); fallbackName != "" && strings.Contains(out, "alias name or linked unit file") {
return s.Operate(operate, fallbackName)

View File

@@ -39,7 +39,7 @@ func (s *Sysvinit) Status(serviceName string) (string, error) {
}
func (s *Sysvinit) Operate(operate, serviceName string) error {
return handlerErr(run(s.toolCmd, serviceName, operate))
return handlerErr(runWithTimeout(serviceOperationTimeout(operate, serviceName), s.toolCmd, serviceName, operate))
}
func (s *Sysvinit) Reload() error {

View File

@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle/providers"
)
const fail2BanRestoreWithFirewallMarker = "/run/1panel_fail2ban_restore_with_firewall"
@@ -70,7 +71,7 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
return err
}
if prepareStart != nil {
if err := prepareStart(o.client); err != nil {
if err := o.prepareAfterStart(prepareStart); err != nil {
recoveryErrors = append(recoveryErrors, fmt.Errorf("prepare firewall after start: %w", err))
}
}
@@ -81,7 +82,7 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
return err
}
if prepareStart != nil {
if err := prepareStart(o.client); err != nil {
if err := o.prepareAfterStart(prepareStart); err != nil {
recoveryErrors = append(recoveryErrors, fmt.Errorf("prepare firewall after restart: %w", err))
}
}
@@ -105,6 +106,16 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
return nil
}
func (o *Operator) prepareAfterStart(prepare func(Client) error) error {
if err := prepare(o.client); err != nil {
return err
}
if o.client.Name() == ProviderFirewalld {
return providers.RemoveFirewalldSSHService()
}
return nil
}
// StopWithPrepare records dependent service state, runs preparation, stops the
// firewall, and optionally restarts Docker in that order.
func (o *Operator) StopWithPrepare(withDockerRestart bool, prepareStop func() error) error {
@@ -118,11 +129,11 @@ func (o *Operator) StopWithPrepare(withDockerRestart bool, prepareStop func() er
return err
}
}
if err := o.client.Stop(); err != nil {
if err := o.runAction("Stop", o.client.Name(), o.client.Stop); err != nil {
return err
}
if withDockerRestart {
if err := controller.HandleRestart("docker"); err != nil {
if err := o.runAction("TaskRestart", "Docker", func() error { return controller.HandleRestart("docker") }); err != nil {
return &DockerRestartError{Err: err}
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
)
type Firewalld struct{}
@@ -68,6 +69,21 @@ func (f *Firewalld) Start() error {
return nil
}
func RemoveFirewalldSSHService() error {
for _, permanent := range []bool{true, false} {
args := []string{"--zone=" + filter.FirewalldInputZone, "--remove-service=ssh"}
configuration := "runtime"
if permanent {
args = append(args, "--permanent")
configuration = "permanent"
}
if _, err := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("firewall-cmd", args...); err != nil {
return fmt.Errorf("remove firewalld SSH service from %s configuration: %w", configuration, err)
}
}
return nil
}
func (f *Firewalld) Stop() error {
if err := controller.HandleStop("firewalld"); err != nil {
return fmt.Errorf("stop the firewall failed, err: %v", err)