fix(firewall): harden port switching and rule synchronization (#13731)

This commit is contained in:
ssongliu authored and GitHub committed 2026-09-07 18:05:31 +08:00
1 parent 81b72d9b7d
commit da5682a600
46 files changed
+979 -681

No files matched your search

+20
View File
@@ -9,10 +9,30 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) UpdatePanelFirewallPort(c *gin.Context) {
if !global.IsMaster {
c.AbortWithStatus(http.StatusForbidden)
return
}
var request struct {
OldPort uint `json:"oldPort" validate:"required,min=1,max=65535"`
NewPort uint `json:"newPort" validate:"required,min=1,max=65535"`
}
if err := helper.CheckBindAndValidate(&request, c); err != nil {
return
}
if err := firewallService.UpdatePanelPort(c.Request.Context(), request.OldPort, request.NewPort); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Firewall
// @Summary Load firewall base info
// @Accept json
+1
View File
@@ -2,6 +2,7 @@ package dto
type ForwardRuleSearch struct {
PageInfo
All bool `json:"all,omitempty"`
Info string `json:"info"`
Status string `json:"status"`
Strategy string `json:"strategy"`
+205 -175
View File
@@ -20,6 +20,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
filterufw "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/providers/ufw"
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"
@@ -43,16 +44,18 @@ type FirewallService struct {
restoreForwarding func(context.Context) error
restoreDockerGuard func(context.Context) error
baseClient func() (lifecycle.Client, error)
ufwIPv6Enabled func() (bool, error)
}
type firewallRuleRuntimeResolver interface {
Resolve(filter.Provider) (*firewallRuleRuntime, error)
Resolve(filter.Provider) (*filterruntime.Engine, error)
Providers() []filter.Provider
}
var firewallRuleMutationMu sync.Mutex
type IFirewallService interface {
UpdatePanelPort(context.Context, uint, uint) error
LoadBaseInfo(chainGroup string) (dto.FirewallSubsystemStatus, error)
OperateFirewall(request dto.FirewallLifecycleOperation) error
OperateFilterChain(request dto.FilterChainOperation) error
@@ -77,7 +80,7 @@ func NewIFirewallService() IFirewallService {
func newFirewallService() *FirewallService {
return &FirewallService{
rules: repo.NewIFirewallRuleRepo(),
adapters: newFirewallRuleRuntimeRegistry(firewallRuleSnapshotPolicy),
adapters: filterruntime.NewRegistry(firewallRuleSnapshotPolicy),
forwardingSync: newForwardingService(),
dockerSync: newDockerPortGuardService(),
selectedProvider: firewallRuleSelectedProvider,
@@ -94,6 +97,7 @@ func newFirewallService() *FirewallService {
},
restoreDockerGuard: ReconcileDockerPortGuard,
baseClient: selectedSystemFirewallClient,
ufwIPv6Enabled: filterufw.IPv6Enabled,
}
}
@@ -139,6 +143,26 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
return status, nil
}
type firewallLifecycleClient struct{ lifecycle.Client }
func (c firewallLifecycleClient) Start() error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
return c.Client.Start()
}
func (c firewallLifecycleClient) Stop() error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
return c.Client.Stop()
}
func (c firewallLifecycleClient) Restart() error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
return c.Client.Restart()
}
func (s *FirewallService) OperateFirewall(request dto.FirewallLifecycleOperation) error {
switch request.Operation {
case "disableBanPing":
@@ -152,12 +176,16 @@ func (s *FirewallService) OperateFirewall(request dto.FirewallLifecycleOperation
}
return settingRepo.Update(constant.FirewallPingStatusKey, constant.StatusEnable)
}
client, err := selectedSystemFirewallClient()
baseClient := s.baseClient
if baseClient == nil {
baseClient = selectedSystemFirewallClient
}
client, err := baseClient()
if err != nil {
return err
}
operation := lifecycle.Operation(request.Operation)
operationErr := lifecycle.NewOperator(client).Operate(operation, request.WithDockerRestart, s.addPortsBeforeStart)
operationErr := lifecycle.NewOperator(firewallLifecycleClient{client}).Operate(operation, request.WithDockerRestart, s.addPortsBeforeStart)
restoreFirewalld := client.Name() == lifecycle.ProviderFirewalld &&
(operation == lifecycle.OperationStart || operation == lifecycle.OperationRestart)
if operation != lifecycle.OperationStart && operation != lifecycle.OperationRestart {
@@ -268,6 +296,15 @@ func (s *FirewallService) QueueFilterChainInitialization(
}
func (s *FirewallService) operateFilterChainBase(provider string, request dto.FilterChainOperation) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
return s.operateFilterChainBaseLocked(provider, request)
}
func (s *FirewallService) operateFilterChainBaseLocked(provider string, request dto.FilterChainOperation) error {
if err := s.checkSelectedProvider(context.Background(), filter.Provider(provider)); err != nil {
return err
}
if !supportsManagedFilterChains(provider) {
return fmt.Errorf("filter chain operations are not supported for %s", provider)
}
@@ -286,7 +323,7 @@ func (s *FirewallService) syncConfiguredFirewallPorts(ctx context.Context) error
if err != nil {
return err
}
required, err := loadRequiredFirewallPortWhiteList()
required, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
return err
}
@@ -433,20 +470,6 @@ func restoreFirewalldDependents(
return errors.Join(errs...)
}
func (s *FirewallService) deleteFirewallRuleRecords(
ctx context.Context,
stored []model.FirewallRule,
) (int, error) {
deleted := 0
for _, record := range stored {
if err := s.rules.DeleteWithRevision(ctx, record.UUID, record.Revision); err != nil {
return deleted, fmt.Errorf("delete reset firewall rule %q: %w", record.UUID, err)
}
deleted++
}
return deleted, nil
}
func (s *FirewallService) loadProtectedPorts() ([]firewall.PortWhitelist, error) {
if s.protectedPorts == nil {
return nil, nil
@@ -503,16 +526,32 @@ func (s *FirewallService) Inventory(ctx context.Context, request dto.FirewallRul
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
}
response := dto.FirewallRuleInventoryResponse{}
desiredByScope, failures := s.desiredFirewallRulesByScope(ctx, stored, provider)
response := dto.FirewallRuleInventoryResponse{Items: failures}
unavailable := make(map[filter.Family]error)
for _, scope := range scopes {
snapshot, err := runtime.Observe(ctx, scope)
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
var snapshot filter.Snapshot
err := unavailable[scope.Family]
if err == nil {
snapshot, err = runtime.Observe(ctx, scope)
}
if errors.Is(err, filter.ErrFamilyUnavailable) {
if unavailable[scope.Family] == nil {
response.Notices = append(response.Notices, filter.ScopeNotice{Code: filter.ScopeNoticeFamilyUnavailable, Values: []string{string(scope.Family), err.Error()}})
unavailable[scope.Family] = err
}
for _, desired := range desiredByScope[scope.Key()] {
response.Items = append(response.Items, filter.InventoryItem{
Rule: desired.Rule, Desired: &desired, State: filter.InventoryStateDrifted,
Match: filter.InventoryMatchNone, Error: err.Error(),
})
}
continue
}
desired, err := s.desiredFirewallRulesForScope(ctx, stored, scope)
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
}
desired := desiredByScope[scope.Key()]
items, err := filter.MergeInventory(filter.InventoryMergeInput{Observed: snapshot.Rules, Desired: desired})
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
@@ -665,7 +704,7 @@ func isCombinedUFWInventoryScope(scope filter.Scope) bool {
func (s *FirewallService) combinedUFWInventory(
ctx context.Context,
runtime *firewallRuleRuntime,
runtime *filterruntime.Engine,
scope filter.Scope,
) (dto.FirewallRuleInventoryResponse, error) {
scopes := []filter.Scope{scope, scope}
@@ -679,20 +718,18 @@ func (s *FirewallService) combinedUFWInventory(
return dto.FirewallRuleInventoryResponse{}, fmt.Errorf("%w: incomplete UFW multi-family inventory", filter.ErrAdapterUnavailable)
}
response := dto.FirewallRuleInventoryResponse{}
stored, err := s.rules.List(ctx)
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
}
desiredByScope, failures := s.desiredFirewallRulesByScope(ctx, stored, scope.Provider)
response := dto.FirewallRuleInventoryResponse{Items: failures}
seenNotices := make(map[string]struct{})
for index, snapshot := range snapshots {
if snapshot.Scope.Key() != scopes[index].Key() {
return dto.FirewallRuleInventoryResponse{}, fmt.Errorf("%w: unexpected UFW inventory scope %q", filter.ErrInvalidScope, snapshot.Scope.Key())
}
stored, err := s.rules.List(ctx)
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
}
desired, err := s.desiredFirewallRulesForScope(ctx, stored, snapshot.Scope)
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
}
desired := desiredByScope[snapshot.Scope.Key()]
items, err := filter.MergeInventory(filter.InventoryMergeInput{Observed: snapshot.Rules, Desired: desired})
if err != nil {
return dto.FirewallRuleInventoryResponse{}, err
@@ -770,6 +807,9 @@ func (s *FirewallService) Check(
var protectedPorts []firewall.PortWhitelist
var selectedProvider filter.Provider
createStateLoaded := false
var desiredByScope map[string][]filter.DesiredRule
var incompatiblePolicies []filter.InventoryItem
var managedRevision string
type checkState struct {
snapshot filter.Snapshot
desired []filter.DesiredRule
@@ -826,19 +866,31 @@ func (s *FirewallService) Check(
if observeErr != nil {
return dto.FirewallRuleCheckResponse{}, observeErr
}
stored, listErr := s.rules.List(ctx)
if listErr != nil {
return dto.FirewallRuleCheckResponse{}, listErr
if desiredByScope == nil {
stored, listErr := s.rules.List(ctx)
if listErr != nil {
return dto.FirewallRuleCheckResponse{}, listErr
}
desiredByScope, incompatiblePolicies = s.desiredFirewallRulesByScope(ctx, stored, selectedProvider)
for _, failure := range incompatiblePolicies {
if !failure.Incompatible {
return dto.FirewallRuleCheckResponse{}, errors.New(failure.Error)
}
}
managedRevision, err = model.FirewallRulesRevision(stored)
if err != nil {
return dto.FirewallRuleCheckResponse{}, err
}
}
desired, desiredErr := s.desiredFirewallRulesForScope(ctx, stored, rule.Scope)
if desiredErr != nil {
return dto.FirewallRuleCheckResponse{}, desiredErr
for index := range snapshot.Rules {
for _, failure := range incompatiblePolicies {
marker := "1panel-rule:" + failure.Desired.UUID
if snapshot.Rules[index].Marker == marker || strings.HasPrefix(snapshot.Rules[index].Marker, marker+"-") {
snapshot.Rules[index].Protected = true
}
}
}
managedRevision, revisionErr := model.FirewallRulesRevision(stored)
if revisionErr != nil {
return dto.FirewallRuleCheckResponse{}, revisionErr
}
state = checkState{snapshot: snapshot, desired: desired, managedRevision: managedRevision}
state = checkState{snapshot: snapshot, desired: desiredByScope[scopeKey], managedRevision: managedRevision}
states[scopeKey] = state
}
checked, checkErr := filter.CheckCreate(state.snapshot, rule, state.desired, clientIP, protectedPorts...)
@@ -923,7 +975,7 @@ func (s *FirewallService) checkRule(
type preparedFirewallRuleCreate struct {
request dto.FirewallRuleCreateItem
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
snapshot filter.Snapshot
authorization firewallRuleCreateAuthorization
}
@@ -999,7 +1051,7 @@ func (s *FirewallService) prepareCreate(
items []dto.FirewallRuleCreateItem,
) ([]preparedFirewallRuleCreate, int, error) {
type prepareState struct {
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
snapshot filter.Snapshot
managedRevision string
}
@@ -1114,11 +1166,20 @@ func (s *FirewallService) createNativeRuleBatch(ctx context.Context, prepared []
if err != nil {
return err
}
created := make([]createdFirewallBatchRule, 0, len(prepared))
nextSequence, err := s.nextFirewallRuleSequence(ctx)
stored, err := s.rules.List(ctx)
if err != nil {
return err
}
identities := make(map[string]struct{}, len(stored)+len(prepared))
var maximumSequence int64
for _, record := range stored {
identities[record.PolicyKey()] = struct{}{}
if record.Sequence != nil && *record.Sequence > maximumSequence {
maximumSequence = *record.Sequence
}
}
nextSequence := maximumSequence + model.FirewallRuleSequenceStep
created := make([]createdFirewallBatchRule, 0, len(prepared))
for _, entry := range prepared {
domainRule := entry.request.Rule
record, recordErr := firewallRuleModelForCreate(domainRule, entry.request, constant.FirewallRuleOriginCreated)
@@ -1130,12 +1191,14 @@ func (s *FirewallService) createNativeRuleBatch(ctx context.Context, prepared []
record.Sequence = &sequence
nextSequence += model.FirewallRuleSequenceStep
}
if recordErr = s.ensureFirewallRuleIdentityAvailable(ctx, record, ""); recordErr != nil {
return s.cleanupFirewallBatchRecords(ctx, created, recordErr)
key := record.PolicyKey()
if _, exists := identities[key]; exists {
return s.cleanupFirewallBatchRecords(ctx, created, fmt.Errorf("%w: equivalent managed rule already exists", filter.ErrRuleOperation))
}
if recordErr = s.rules.Create(ctx, &record); recordErr != nil {
return s.cleanupFirewallBatchRecords(ctx, created, recordErr)
}
identities[key] = struct{}{}
domainRule.UUID = record.UUID
created = append(created, createdFirewallBatchRule{record: record, rule: domainRule})
}
@@ -1241,7 +1304,7 @@ type preparedFirewallRuleDelete struct {
index int
stored model.FirewallRule
desired filter.DesiredRule
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
compiled int
}
@@ -1280,6 +1343,9 @@ func (s *FirewallService) Delete(
continue
}
groupKey := string(prepared.desired.Rule.Scope.Provider) + ":" + prepared.desired.Rule.Scope.Key()
if prepared.compiled != 1 {
groupKey += ":" + prepared.stored.UUID
}
groupIndex, exists := groupIndexes[groupKey]
if !exists {
groupIndex = len(groups)
@@ -1473,7 +1539,7 @@ func (s *FirewallService) checkSelectedProvider(ctx context.Context, requested f
func (s *FirewallService) createRule(
ctx context.Context,
runtime *firewallRuleRuntime,
runtime *filterruntime.Engine,
snapshot filter.Snapshot,
request dto.FirewallRuleCreateItem,
authorization firewallRuleCreateAuthorization,
@@ -1562,8 +1628,15 @@ func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string, allow
if err != nil {
return err
}
if ports, ok := ctx.Value(panelPortWhitelistKey{}).([]firewall.PortWhitelist); ok {
for _, desired := range desiredRules {
if panelRuleStillRequired(desired.Rule, ports) {
return filter.ErrProtectedRule
}
}
}
type appliedDelete struct {
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
plan filter.BackendPlan
}
applied := make([]appliedDelete, 0, len(desiredRules))
@@ -1747,7 +1820,7 @@ type managedMutationRequest struct {
Snapshot filter.Snapshot
Locator filter.Locator
AdapterOperation filter.ChangeOperation
Runtime *firewallRuleRuntime
Runtime *filterruntime.Engine
}
type preparedManagedUpdate struct {
@@ -1756,7 +1829,7 @@ type preparedManagedUpdate struct {
After filter.FirewallRule
Snapshot filter.Snapshot
Observed filter.ObservedRule
Runtime *firewallRuleRuntime
Runtime *filterruntime.Engine
}
func (s *FirewallService) prepareManagedUpdate(
@@ -1834,7 +1907,7 @@ func supportsManagedNativeKindTransition(before, after filter.FirewallRule) bool
func (s *FirewallService) loadManagedMutation(
ctx context.Context,
ruleUUID string,
) (model.FirewallRule, filter.DesiredRule, filter.Snapshot, filter.ObservedRule, *firewallRuleRuntime, error) {
) (model.FirewallRule, filter.DesiredRule, filter.Snapshot, filter.ObservedRule, *filterruntime.Engine, error) {
stored, err := s.rules.GetByUUID(ctx, ruleUUID)
if err != nil {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err
@@ -1980,7 +2053,7 @@ func (s *FirewallService) ensureFirewallRuleIdentityAvailable(
return nil
}
func (s *FirewallService) resolveRuntime(ctx context.Context, provider filter.Provider) (*firewallRuleRuntime, error) {
func (s *FirewallService) resolveRuntime(ctx context.Context, provider filter.Provider) (*filterruntime.Engine, error) {
if s.selectedProvider != nil {
selected, err := s.selectedProvider(ctx)
if err != nil {
@@ -2005,7 +2078,7 @@ func (s *FirewallService) cleanupFailedCreate(ctx context.Context, rule model.Fi
func (s *FirewallService) cleanupAppliedCreate(
ctx context.Context,
runtime *firewallRuleRuntime,
runtime *filterruntime.Engine,
plan filter.BackendPlan,
rule model.FirewallRule,
cause error,
@@ -2147,10 +2220,18 @@ func (s *FirewallService) deleteSystemPort(ctx context.Context, port dto.Firewal
}
continue
}
deleted := false
for _, rule := range stored {
if err := s.deleteProtectedSystemPortRule(ctx, rule.UUID); err != nil {
if errors.Is(err, filter.ErrProtectedRule) && ctx.Value(panelPortWhitelistKey{}) != nil {
continue
}
return err
}
deleted = true
}
if !deleted {
return nil
}
}
}
@@ -2419,53 +2500,6 @@ func firewallRuleMarkerPosition(snapshot filter.Snapshot, ruleUUID string) (int,
return 0, fmt.Errorf("%w: committed firewall rule %q has no position", filter.ErrVerificationFailed, ruleUUID)
}
func mergeFirewallInventory(
observed []filter.ObservedRule,
stored []model.FirewallRule,
protectedObservedKeys map[string]struct{},
usage map[string]filter.RuntimeUsage,
) ([]filter.InventoryItem, error) {
desired := make([]filter.DesiredRule, 0, len(stored))
for _, storedRule := range stored {
rule, err := desiredFirewallRuleFromModel(storedRule)
if err != nil {
return nil, err
}
desired = append(desired, rule)
}
items, err := filter.MergeInventory(filter.InventoryMergeInput{
Observed: observed,
Desired: desired,
ProtectedObservedKeys: protectedObservedKeys,
})
if err != nil {
return nil, err
}
return filter.AttachRuntimeUsage(items, usage), nil
}
func desiredFirewallRuleFromModel(stored model.FirewallRule) (filter.DesiredRule, error) {
rules, err := stored.RulesForProvider(filter.ProviderIptables)
if err != nil {
return filter.DesiredRule{}, err
}
if len(rules) != 1 {
return filter.DesiredRule{}, fmt.Errorf(
"%w: policy %q expands to %d canonical rules", filter.ErrUnsupportedScope, stored.UUID, len(rules),
)
}
rule := rules[0]
rule.UUID = stored.UUID
ruleKey, err := filter.RuleKey(rule)
if err != nil {
return filter.DesiredRule{}, err
}
return filter.DesiredRule{
UUID: stored.UUID, Rule: rule, RuleKey: ruleKey, Origin: filter.RuleOrigin(stored.Origin),
Marker: "1panel-rule:" + stored.UUID,
}, nil
}
func (s *FirewallService) compileStoredFirewallRules(
ctx context.Context,
stored model.FirewallRule,
@@ -2482,29 +2516,52 @@ func (s *FirewallService) compileStoredFirewallRules(
return runtime.CompileDesired(ctx, stored.UUID, filter.RuleOrigin(stored.Origin), rules)
}
func (s *FirewallService) desiredFirewallRulesForScope(
ctx context.Context,
stored []model.FirewallRule,
scope filter.Scope,
) ([]filter.DesiredRule, error) {
model.SortFirewallRules(stored, scope.Provider)
desired := make([]filter.DesiredRule, 0, len(stored))
for _, record := range stored {
compiled, err := s.compileStoredFirewallRules(ctx, record, scope.Provider)
if err != nil {
return nil, err
}
for _, rule := range compiled {
if rule.Rule.Scope.Key() == scope.Key() {
rule.Protected = isProtectedSystemFirewallRule(record)
desired = append(desired, rule)
}
}
}
return desired, nil
func isFirewallPolicyIncompatible(err error) bool {
return errors.Is(err, filter.ErrInvalidRule) || errors.Is(err, filter.ErrUnsupportedScope) ||
errors.Is(err, filter.ErrInvalidScope) || errors.Is(err, filter.ErrCompositeRule)
}
func firewallRuleSnapshotPolicy(_ context.Context, snapshot filter.Snapshot) (filter.Snapshot, error) {
func (s *FirewallService) desiredFirewallRulesByScope(
ctx context.Context,
stored []model.FirewallRule,
provider filter.Provider,
) (map[string][]filter.DesiredRule, []filter.InventoryItem) {
model.SortFirewallRules(stored, provider)
desired := make(map[string][]filter.DesiredRule)
var failures []filter.InventoryItem
for _, record := range stored {
compiled, err := s.compileStoredFirewallRules(ctx, record, provider)
if err != nil {
rule := filter.FirewallRule{
UUID: record.UUID,
Scope: filter.Scope{Provider: provider, Family: filter.Family(record.Family), Direction: filter.DirectionInput}.Normalize(),
Protocol: record.Protocol, SourceAddress: record.SourceAddress, SourcePort: record.SourcePort,
DestinationAddress: record.DestinationAddress, DestinationPort: record.DestinationPort,
Interface: record.Interface, ConnectionStates: strings.FieldsFunc(record.ConnectionStates, func(r rune) bool { return r == ',' }),
Action: filter.Action(record.Action), Description: record.Description, Priority: record.Priority,
}
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)},
Error: fmt.Sprintf("policy %s: %v", record.UUID, err),
})
continue
}
for _, rule := range compiled {
rule.Protected = isProtectedSystemFirewallRule(record)
rule.Expanded = len(compiled) > 1
key := rule.Rule.Scope.Key()
desired[key] = append(desired[key], rule)
}
}
return desired, failures
}
func firewallRuleSnapshotPolicy(ctx context.Context, snapshot filter.Snapshot) (filter.Snapshot, error) {
if ports, ok := ctx.Value(panelPortWhitelistKey{}).([]firewall.PortWhitelist); ok {
return filter.ProtectSnapshot(snapshot, ports)
}
ports, err := loadFirewallPortWhiteList()
if err != nil {
return filter.Snapshot{}, err
@@ -2516,19 +2573,7 @@ func firewallRuleSelectedProvider(context.Context) (filter.Provider, error) {
return selectedRuleProvider()
}
type firewallSnapshotPolicy = filterruntime.SnapshotPolicy
type firewallRuleRuntime = filterruntime.Engine
type firewallRuleRuntimeRegistry = filterruntime.Registry
func newFirewallRuleRuntimeRegistry(policy firewallSnapshotPolicy) firewallRuleRuntimeRegistry {
return filterruntime.NewRegistry(policy)
}
func newFirewallRuleRuntime(adapter filter.Adapter, policy firewallSnapshotPolicy) *firewallRuleRuntime {
return filterruntime.New(adapter, policy)
}
func rollbackFirewallPlan(ctx context.Context, runtime *firewallRuleRuntime, plan filter.BackendPlan, cause error) error {
func rollbackFirewallPlan(ctx context.Context, runtime *filterruntime.Engine, plan filter.BackendPlan, cause error) error {
if runtime == nil {
return cause
}
@@ -2657,7 +2702,7 @@ func OperateFirewallPort(oldPorts, newPorts []int) error {
added = append(added, firewall.PortWhitelist{Port: strconv.Itoa(port), Protocol: "tcp"})
}
if state.Name == constant.FirewallProviderIptables || state.Name == constant.FirewallProviderNftables {
required, err := loadRequiredFirewallPortWhiteList()
required, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
return err
}
@@ -2800,22 +2845,30 @@ func loadFirewallPortWhiteList() ([]firewall.PortWhitelist, error) {
if err != nil {
return nil, err
}
required, err := loadRequiredFirewallPortWhiteList()
required, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
return nil, err
}
return firewall.NormalizePortWhitelist(append(configured, required...)), nil
}
func loadRequiredFirewallPortWhiteList() ([]firewall.PortWhitelist, error) {
panelPort := LoadPanelPort()
func LoadRequiredFirewallPortWhiteList() ([]firewall.PortWhitelist, error) {
return loadRequiredFirewallPorts(LoadPanelPort())
}
func loadRequiredFirewallPorts(panelPort string) ([]firewall.PortWhitelist, error) {
if panelPort == "" {
return nil, fmt.Errorf("find 1panel service port failed")
}
return firewall.NormalizePortWhitelist([]firewall.PortWhitelist{
{Port: panelPort, Protocol: "tcp"},
{Port: loadSSHPort(), Protocol: "tcp"},
}), nil
directives, _, err := parseSSHConfigTree(sshPath)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("load required SSH ports: %w", err)
}
ports := []firewall.PortWhitelist{{Port: panelPort, Protocol: "tcp"}}
for _, port := range loadSSHPortValues(directives) {
ports = append(ports, firewall.PortWhitelist{Port: port, Protocol: "tcp"})
}
return firewall.NormalizeRequiredPorts(ports)
}
func ReleaseFirewallPortWhitelistAfterUpdate(oldValue string) error {
@@ -2827,7 +2880,7 @@ func ReleaseFirewallPortWhitelistAfterUpdate(oldValue string) error {
if err != nil {
return err
}
required, err := loadRequiredFirewallPortWhiteList()
required, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
return err
}
@@ -2881,14 +2934,14 @@ func newIptablesHelperManager() *iptables_helper.Manager {
return &iptables_helper.Manager{
UpdateSetting: settingRepo.Update,
PanelPort: LoadPanelPort,
LoadRequiredPorts: loadRequiredFirewallPortWhiteList,
LoadRequiredPorts: LoadRequiredFirewallPortWhiteList,
}
}
func newNftablesHelperManager() *nftables_helper.Manager {
return &nftables_helper.Manager{
UpdateSetting: settingRepo.Update,
LoadRequiredPorts: loadRequiredFirewallPortWhiteList,
LoadRequiredPorts: LoadRequiredFirewallPortWhiteList,
}
}
@@ -2951,7 +3004,7 @@ func (s *FirewallService) addPortsBeforeStart(client lifecycle.Client) error {
recordFailure("load configured accepted ports", err)
return errors.Join(recoveryErrors...)
}
required, err := loadRequiredFirewallPortWhiteList()
required, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
recordFailure("load required accepted ports", err)
return errors.Join(recoveryErrors...)
@@ -2987,26 +3040,3 @@ func systemPorts(ports []firewall.PortWhitelist) []dto.FirewallSystemPort {
func excludeFirewallPorts(ports, excluded []firewall.PortWhitelist) []firewall.PortWhitelist {
return firewall.ExcludePorts(ports, excluded)
}
const (
sshConfigPath = "/etc/ssh/sshd_config"
defaultFirewallSSHPort = "22"
)
func loadSSHPort() string {
content, err := os.ReadFile(sshConfigPath)
if err != nil {
return defaultFirewallSSHPort
}
for _, line := range strings.Split(string(content), "\n") {
if !strings.HasPrefix(line, "Port ") {
continue
}
port := strings.TrimSpace(strings.TrimPrefix(line, "Port "))
value, _ := strconv.Atoi(port)
if value > 0 && value < 65535 {
return port
}
}
return defaultFirewallSSHPort
}
+12 -10
View File
@@ -138,28 +138,30 @@ func (s *DockerPortGuardService) LoadPublishedPorts(ctx context.Context) ([]dto.
}
func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPortGuardList, error) {
selectedBackend := selectedDockerFirewallBackend("")
base := s.runtimeStatus(s.guardRuntime(selectedBackend), selectedBackend)
base.Version = s.loadFirewallVersion(selectedBackend)
policies, err := s.policies.ListManaged(ctx)
if err != nil {
return dto.DockerPortGuardList{}, err
}
unavailable := func() dto.DockerPortGuardList {
backend := selectedDockerFirewallBackend("")
base := s.runtimeStatus(s.guardRuntime(backend), backend)
base.Version = s.loadFirewallVersion(backend)
base.Message = agenti18n.Get("ErrDockerFailed")
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}
}
cli, err := s.client()
if err != nil {
base.Message = agenti18n.Get("ErrDockerFailed")
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}, nil
return unavailable(), nil
}
defer cli.Close()
info, err := cli.Info(ctx)
if err != nil {
base.Message = agenti18n.Get("ErrDockerFailed")
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}, nil
return unavailable(), nil
}
detectedBackend := dockerFirewallBackend(info)
base.Backend = selectedDockerFirewallBackend(detectedBackend)
base = s.runtimeStatus(s.guardRuntime(base.Backend), base.Backend)
base.Version = s.loadFirewallVersion(base.Backend)
backend := selectedDockerFirewallBackend(detectedBackend)
base := s.runtimeStatus(s.guardRuntime(backend), backend)
base.Version = s.loadFirewallVersion(backend)
if reconcileErr := lastDockerPortGuardReconcileError(); reconcileErr != nil {
markDockerGuardReconcileFailure(&base, reconcileErr)
}
+172
View File
@@ -0,0 +1,172 @@
package service
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"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/filter"
filterufw "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/providers/ufw"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/nftables_helper"
)
type panelPortWhitelistKey struct{}
func (s *FirewallService) panelSystemPorts(provider string, port uint) ([]dto.FirewallSystemPort, error) {
ports := []dto.FirewallSystemPort{{Port: strconv.Itoa(int(port)), Protocol: "tcp"}}
if provider != constant.FirewallProviderUFW {
return ports, nil
}
ipv6Enabled := s.ufwIPv6Enabled
if ipv6Enabled == nil {
ipv6Enabled = filterufw.IPv6Enabled
}
enabled, err := ipv6Enabled()
if err != nil {
return nil, err
}
ports[0].Family = constant.FirewallFamilyIPv4
if enabled {
ports = append(ports, dto.FirewallSystemPort{Family: constant.FirewallFamilyIPv6, Port: ports[0].Port, Protocol: "tcp"})
}
return ports, nil
}
func (s *FirewallService) UpdatePanelPort(ctx context.Context, oldPort, port uint) error {
if oldPort == 0 || oldPort > 65535 || port == 0 || port > 65535 {
return fmt.Errorf("invalid panel port transition %d -> %d", oldPort, port)
}
if LoadPanelPort() != strconv.Itoa(int(oldPort)) {
return fmt.Errorf("panel port changed before firewall update")
}
if oldPort == port {
return nil
}
client, err := s.baseClient()
if err != nil {
if configuredSystemFirewallBackend() == "" && len(lifecycle.InstalledProviders()) == 0 {
return nil
}
return err
}
provider := client.Name()
active, err := client.Status()
if err != nil {
return err
}
if !supportsManagedFilterChains(provider) && !active {
return nil
}
required, err := loadRequiredFirewallPorts(strconv.Itoa(int(port)))
if err != nil {
return err
}
if supportsManagedFilterChains(provider) {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
prepared := append([]firewall.PortWhitelist{{Port: strconv.Itoa(int(oldPort)), Protocol: "tcp"}}, required...)
if err := syncPanelRequiredPorts(provider, prepared); err != nil {
return err
}
warnPanelPortCleanupFailure(oldPort, syncPanelRequiredPorts(provider, required))
return nil
}
configured, err := loadConfiguredFirewallPortWhiteList()
if err != nil {
return err
}
protected := firewall.NormalizePortWhitelist(append(configured, required...))
ports, err := s.panelSystemPorts(provider, port)
if err != nil {
return err
}
for _, port := range ports {
if err := s.ensureSystemPort(ctx, port); err != nil {
return err
}
}
cleanup := *s
cleanup.protectedPorts = func() ([]firewall.PortWhitelist, error) { return protected, nil }
ctx = context.WithValue(ctx, panelPortWhitelistKey{}, protected)
for index := range ports {
ports[index].Port = strconv.Itoa(int(oldPort))
}
if provider == constant.FirewallProviderUFW {
for _, port := range ports {
port.Protocol = "all"
ports = append(ports, port)
}
}
for _, port := range ports {
if panelPortStillRequired(port, protected) {
continue
}
if err := cleanup.deleteSystemPort(ctx, port); err != nil {
warnPanelPortCleanupFailure(oldPort, err)
}
}
return nil
}
func warnPanelPortCleanupFailure(port uint, err error) {
if err != nil && global.LOG != nil {
global.LOG.Warnf("clean up old panel firewall port %d failed: %v", port, err)
}
}
func syncPanelRequiredPorts(provider string, ports []firewall.PortWhitelist) error {
loadPorts := func() ([]firewall.PortWhitelist, error) { return ports, nil }
if provider == constant.FirewallProviderIptables {
manager := newIptablesHelperManager()
manager.LoadRequiredPorts = loadPorts
return manager.SyncRequiredPorts(true)
}
initialized := false
for _, family := range []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6} {
familyInitialized, _, err := nftables_helper.LoadFamilyInitStatus(family, "base")
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
initialized = initialized || familyInitialized
}
if !initialized {
return nil
}
manager := newNftablesHelperManager()
manager.LoadRequiredPorts = loadPorts
return manager.SyncRequiredPorts()
}
func panelPortStillRequired(port dto.FirewallSystemPort, protected []firewall.PortWhitelist) bool {
rule := systemPortRule(filter.ProviderUFW, port)
for _, required := range protected {
if port.Family != "" && required.Family != "" && port.Family != required.Family {
continue
}
other := rule
other.Protocol, other.DestinationPort = required.Protocol, required.Port
if filter.RulesOverlap(rule, other) {
return true
}
}
return false
}
func panelRuleStillRequired(rule filter.FirewallRule, protected []firewall.PortWhitelist) bool {
family := string(rule.Scope.Family)
if rule.Scope.Family == filter.FamilyInet {
family = ""
}
return panelPortStillRequired(dto.FirewallSystemPort{
Family: family, Port: rule.DestinationPort, Protocol: rule.Protocol,
}, protected)
}
+17 -2
View File
@@ -195,7 +195,17 @@ func (s *FirewallSettingService) Operate(ctx context.Context, request dto.Firewa
}
switch request.Subsystem {
case "system":
return s.operateSystem(request)
if err := s.operateSystem(request); err != nil {
return err
}
if request.Operation == "initialize" {
service := newFirewallService()
if err := service.restoreStoredFirewallRules(ctx, filter.Provider(request.Backend)); err != nil {
return err
}
return service.syncConfiguredFirewallPorts(ctx)
}
return nil
case "forwarding":
return s.operateForwarding(request)
case "docker":
@@ -260,6 +270,8 @@ func dockerGuardBackendInitialized(backend string) (bool, error) {
}
func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperation) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
if _, err := lifecycle.NewClientFor(request.Backend); err != nil {
return err
}
@@ -294,7 +306,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
if request.Operation == "select" {
return nil
}
initErr := newFirewallService().OperateFilterChain(dto.FilterChainOperation{
initErr := newFirewallService().operateFilterChainBaseLocked(request.Backend, dto.FilterChainOperation{
Name: constant.FirewallBasicChain, Operate: string(firewall.BaseOperationInit),
})
if initErr != nil {
@@ -321,6 +333,9 @@ func systemFirewallBackendInitializedWithClientFactory(
if supportsManagedFilterChains(backend) {
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
initialized, _, err := loadSystemFirewallFamilyStatus(backend, family)
if family == constant.FirewallFamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return false, err
}
+21 -9
View File
@@ -18,6 +18,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/forwarding"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
firewallsync "github.com/1Panel-dev/1Panel/agent/utils/firewall/sync"
@@ -66,7 +67,7 @@ type firewallRuleSyncEntry struct {
}
type firewallRuleSyncScopePlan struct {
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
snapshot filter.Snapshot
entries []*firewallRuleSyncEntry
}
@@ -98,6 +99,8 @@ func (s *FirewallService) previewSystemRuleSync(
clientIP string,
request dto.FirewallRuleSyncRequest,
) (dto.FirewallRuleSyncPreview, error) {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
plan, err := s.loadFirewallRuleSyncPlan(ctx, clientIP, request)
if err != nil {
return dto.FirewallRuleSyncPreview{}, err
@@ -110,6 +113,8 @@ func (s *FirewallService) syncRules(
clientIP string,
request dto.FirewallRuleSyncRequest,
) (dto.FirewallRuleSyncResult, error) {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
plan, err := s.loadFirewallRuleSyncPlan(ctx, clientIP, request)
if err != nil {
return dto.FirewallRuleSyncResult{}, err
@@ -121,9 +126,6 @@ func (s *FirewallService) syncRules(
}
func (s *FirewallService) restoreStoredFirewallRules(ctx context.Context, provider filter.Provider) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
result, err := s.syncRules(ctx, "", dto.FirewallRuleSyncRequest{
Subsystem: "system",
TargetProvider: provider,
@@ -164,11 +166,12 @@ func (s *FirewallService) adoptLegacyHostFirewallRuleOwnership(ctx context.Conte
if err != nil {
return err
}
desiredByScope, failures := s.desiredFirewallRulesByScope(ctx, stored, selected)
if len(failures) > 0 {
return errors.New(failures[0].Error)
}
for _, scope := range filter.ManagedInputScopes(selected) {
desired, err := s.desiredFirewallRulesForScope(ctx, stored, scope)
if err != nil {
return err
}
desired := desiredByScope[scope.Key()]
if len(desired) == 0 {
continue
}
@@ -261,6 +264,13 @@ func (s *FirewallService) loadFirewallRuleSyncPlan(
seenSnapshots := make(map[string]struct{}, len(scopes))
for _, scope := range scopes {
snapshot, observeErr := runtime.ObserveMutation(ctx, scope)
if errors.Is(observeErr, filter.ErrFamilyUnavailable) {
for _, entry := range entriesByScope[scope.Key()] {
entry.err = observeErr
entry.item.Status, entry.item.Reason = firewallRuleSyncBlocked, observeErr.Error()
}
continue
}
if observeErr != nil {
return firewallSystemSyncPlan{}, observeErr
}
@@ -446,6 +456,8 @@ func (s *FirewallService) syncSystemRules(
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("FirewallVerifyTargetStep"), func(t *task.Task) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
verified, verifyErr := s.loadFirewallRuleSyncPlan(t.TaskCtx, clientIP, taskRequest)
if verifyErr != nil {
return verifyErr
@@ -1089,7 +1101,7 @@ type firewallScopeReconciler struct {
ctx context.Context
clientIP string
protectedPorts []firewall.PortWhitelist
runtime *firewallRuleRuntime
runtime *filterruntime.Engine
snapshot filter.Snapshot
entries []*firewallRuleSyncEntry
}
+5 -2
View File
@@ -25,7 +25,7 @@ import (
type IForwardingService interface {
LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
SearchRules(request dto.ForwardRuleSearch) (int64, interface{}, error)
SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error)
OperateRules(request dto.ForwardRuleOperate) error
Enable() error
QueueInitialization(dto.FirewallInitializationTask) (dto.FilterChainOperationResponse, error)
@@ -123,7 +123,7 @@ func forwardingDisplayName(backend string) string {
}
}
func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, interface{}, error) {
func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, []dto.ForwardRule, error) {
if request.Strategy != "" {
return 0, nil, nil
}
@@ -153,6 +153,9 @@ func (s *ForwardingService) SearchRules(request dto.ForwardRuleSearch) (int64, i
inventory = filtered
total := len(inventory)
start, end := (request.Page-1)*request.PageSize, request.Page*request.PageSize
if request.All {
start, end = 0, total
}
if start > total {
return int64(total), make([]dto.ForwardRule, 0), nil
}
+12 -2
View File
@@ -69,7 +69,12 @@ func Init() {
global.LOG.Errorf("find 1panel service port failed")
return
}
if err := iptables_helper.RestoreBaseChains(panelPort); err != nil {
requiredPorts, err := service.LoadRequiredFirewallPortWhiteList()
if err != nil {
global.LOG.Errorf("load required firewall ports failed, err: %v", err)
return
}
if err := iptables_helper.RestoreBaseChains(panelPort, requiredPorts); err != nil {
global.LOG.Errorf("restore iptables base chains failed, err: %v", err)
return
}
@@ -99,7 +104,12 @@ func repairIptablesIPv6BaseChains(clientName string) {
return
}
panelPort := service.LoadPanelPort()
if err := iptables_helper.RepairIPv6BaseChains(panelPort); err != nil {
requiredPorts, err := service.LoadRequiredFirewallPortWhiteList()
if err != nil {
global.LOG.Warnf("load required firewall ports for IPv6 repair failed, err: %v", err)
return
}
if err := iptables_helper.RepairIPv6BaseChains(panelPort, requiredPorts); err != nil {
global.LOG.Warnf("repair IPv6 iptables base chains failed, err: %v", err)
return
}
+1
View File
@@ -23,6 +23,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/base", baseApi.LoadFirewallBaseInfo)
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
hostRouter.POST("/firewall/port", baseApi.UpdatePanelFirewallPort)
hostRouter.GET("/firewall/settings", baseApi.LoadFirewallSettings)
hostRouter.POST("/firewall/settings/operate", baseApi.OperateFirewallBackend)
hostRouter.POST("/firewall/forward/base", baseApi.LoadForwardingBaseInfo)
+1
View File
@@ -8,6 +8,7 @@ import (
var (
ErrAdapterUnavailable = errors.New("firewall rule adapter is unavailable")
ErrInventoryUnavailable = errors.New("firewall rule inventory is unavailable")
ErrFamilyUnavailable = errors.New("firewall address family is unavailable")
)
type ChangeOperation string
-23
View File
@@ -331,21 +331,6 @@ func snapshotHasOwnedRule(snapshot Snapshot, desired DesiredRule) bool {
return false
}
func RuleCovers(existing, requested FirewallRule) bool {
existing, existingErr := NormalizeRule(existing)
requested, requestedErr := NormalizeRule(requested)
if existingErr != nil || requestedErr != nil || existing.Scope.Key() != requested.Scope.Key() {
return false
}
return familyCovers(existing.Scope.Family, requested.Scope.Family) &&
protocolCovers(existing.Protocol, requested.Protocol) &&
addressCovers(existing.SourceAddress, requested.SourceAddress) &&
addressCovers(existing.DestinationAddress, requested.DestinationAddress) &&
portCovers(existing.SourcePort, requested.SourcePort) &&
portCovers(existing.DestinationPort, requested.DestinationPort) &&
(existing.Interface == "" || existing.Interface == requested.Interface)
}
func RulesOverlap(left, right FirewallRule) bool {
left, leftErr := NormalizeRule(left)
right, rightErr := NormalizeRule(right)
@@ -361,18 +346,10 @@ func RulesOverlap(left, right FirewallRule) bool {
(left.Interface == "" || right.Interface == "" || left.Interface == right.Interface)
}
func familyCovers(existing, requested Family) bool {
return existing == FamilyInet || existing == requested
}
func familiesOverlap(left, right Family) bool {
return left == FamilyInet || right == FamilyInet || left == right
}
func protocolCovers(existing, requested string) bool {
return existing == "all" || existing == requested
}
func protocolsOverlap(left, right string) bool {
return left == "all" || right == "all" || left == right
}
+8
View File
@@ -41,6 +41,10 @@ func RuleKey(rule FirewallRule) (string, error) {
if err != nil {
return "", err
}
return normalizedRuleKey(normalized)
}
func normalizedRuleKey(normalized FirewallRule) (string, error) {
identity := ruleIdentity{
Scope: normalized.Scope.Key(),
Family: normalized.Scope.Family,
@@ -71,6 +75,10 @@ func InstanceKey(rule ObservedRule) (string, error) {
if err != nil {
return "", err
}
return instanceKeyWithRuleKey(rule, ruleKey)
}
func instanceKeyWithRuleKey(rule ObservedRule, ruleKey string) (string, error) {
locator, err := validatedLocator(rule.Locator, rule.Rule.Scope)
if err != nil {
return "", err
+12 -9
View File
@@ -43,6 +43,7 @@ type DesiredRule struct {
RuleKey string `json:"ruleKey"`
Origin RuleOrigin `json:"origin"`
Protected bool `json:"protected,omitempty"`
Expanded bool `json:"expanded,omitempty"`
Marker string `json:"marker,omitempty"`
ObservedInstanceKey string `json:"observedInstanceKey,omitempty"`
}
@@ -54,12 +55,14 @@ type RuntimeUsage struct {
}
type InventoryItem struct {
Rule FirewallRule `json:"rule"`
Observed *ObservedRule `json:"observed,omitempty"`
Desired *DesiredRule `json:"desired,omitempty"`
State InventoryState `json:"state"`
Match InventoryMatch `json:"match"`
Usage *RuntimeUsage `json:"usage,omitempty"`
Incompatible bool `json:"incompatible,omitempty"`
Error string `json:"error,omitempty"`
Rule FirewallRule `json:"rule"`
Observed *ObservedRule `json:"observed,omitempty"`
Desired *DesiredRule `json:"desired,omitempty"`
State InventoryState `json:"state"`
Match InventoryMatch `json:"match"`
Usage *RuntimeUsage `json:"usage,omitempty"`
}
type Inventory struct {
@@ -97,12 +100,12 @@ func MergeInventory(input InventoryMergeInput) ([]InventoryItem, error) {
return nil, fmt.Errorf("normalize observed firewall rule %d: %w", index, err)
}
candidate.rule.Rule = normalized
candidate.ruleKey, err = RuleKey(normalized)
candidate.ruleKey, err = normalizedRuleKey(normalized)
if err != nil {
return nil, err
}
byRuleKey[candidate.ruleKey] = append(byRuleKey[candidate.ruleKey], index)
if instanceKey, err := InstanceKey(candidate.rule); err == nil {
if instanceKey, err := instanceKeyWithRuleKey(candidate.rule, candidate.ruleKey); err == nil {
candidate.instanceKey = instanceKey
byInstanceKey[instanceKey] = append(byInstanceKey[instanceKey], index)
}
@@ -119,7 +122,7 @@ func MergeInventory(input InventoryMergeInput) ([]InventoryItem, error) {
return nil, fmt.Errorf("normalize desired firewall rule %q: %w", desired.UUID, err)
}
desired.Rule = normalized
calculatedKey, err := RuleKey(normalized)
calculatedKey, err := normalizedRuleKey(normalized)
if err != nil {
return nil, err
}
+5 -97
View File
@@ -91,6 +91,7 @@ const (
ScopeNoticeUnmanagedActiveScopes ScopeNoticeCode = "unmanaged_active_scopes"
ScopeNoticeRuntimePermanentMismatch ScopeNoticeCode = "runtime_permanent_mismatch"
ScopeNoticeManagedScopeMissing ScopeNoticeCode = "managed_scope_missing"
ScopeNoticeFamilyUnavailable ScopeNoticeCode = "family_unavailable"
)
const (
@@ -257,56 +258,6 @@ func isBasicChain(chain string) bool {
}
}
type ScopePattern struct {
Provider Provider
Families []Family
Table string
Zone string
Chains []string
Directions []Direction
}
func (p ScopePattern) Matches(scope Scope) bool {
scope = scope.Normalize()
return p.Provider == scope.Provider &&
containsFamily(p.Families, scope.Family) &&
strings.EqualFold(p.Table, scope.Table) &&
strings.EqualFold(p.Zone, scope.Zone) &&
containsFold(p.Chains, scope.Chain) &&
containsDirection(p.Directions, scope.Direction)
}
func MVPScopePatterns() []ScopePattern {
return []ScopePattern{
{
Provider: ProviderIptables,
Families: []Family{FamilyIPv4, FamilyIPv6},
Table: "filter",
Chains: []string{BasicBeforeChain, IptablesInputChain, BasicAfterChain},
Directions: []Direction{DirectionInput},
},
{
Provider: ProviderNftables,
Families: []Family{FamilyIPv4, FamilyIPv6},
Table: "filter",
Chains: []string{BasicBeforeChain, IptablesInputChain, BasicAfterChain},
Directions: []Direction{DirectionInput},
},
{
Provider: ProviderFirewalld,
Families: []Family{FamilyIPv4, FamilyIPv6, FamilyInet},
Zone: "public",
Directions: []Direction{DirectionInput},
},
{
Provider: ProviderUFW,
Families: []Family{FamilyIPv4, FamilyIPv6},
Chains: []string{"incoming"},
Directions: []Direction{DirectionInput},
},
}
}
type FirewallRule struct {
UUID string `json:"uuid,omitempty"`
Scope Scope `json:"scope"`
@@ -353,51 +304,8 @@ type Snapshot struct {
}
type Capabilities struct {
Scopes []ScopePattern
Marker bool
AtomicApply bool
TransactionalRollback bool
OwnedChains bool
ExplicitPosition bool
ExplicitPriority bool
NativePort bool
}
func (c Capabilities) SupportsScope(scope Scope) bool {
for _, pattern := range c.Scopes {
if pattern.Matches(scope) {
return true
}
}
return false
}
func containsFamily(values []Family, target Family) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func containsDirection(values []Direction, target Direction) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func containsFold(values []string, target string) bool {
if len(values) == 0 {
return target == ""
}
for _, value := range values {
if strings.EqualFold(value, target) {
return true
}
}
return false
Marker bool
OwnedChains bool
ExplicitPosition bool
ExplicitPriority bool
}
@@ -51,12 +51,8 @@ func (a *Adapter) Capabilities(ctx context.Context) (filter.Capabilities, error)
return filter.Capabilities{}, err
}
return filter.Capabilities{
Scopes: []filter.ScopePattern{{
Provider: filter.ProviderFirewalld, Families: []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6, filter.FamilyInet},
Zone: filter.FirewalldInputZone, Directions: []filter.Direction{filter.DirectionInput},
}},
ExplicitPriority: explicitPriority,
NativePort: true,
}, nil
}
@@ -76,7 +76,7 @@ func (a *Adapter) CheckRule(ctx context.Context, rule filter.FirewallRule) error
return nil
}
if err := a.checker.CheckMultiport(ctx, rule.Scope.Family); err != nil {
return fmt.Errorf("%w: iptables multiport is unavailable for %s: %v", filter.ErrUnsupportedScope, rule.Scope.Family, err)
return fmt.Errorf("inspect iptables multiport for %s: %w", rule.Scope.Family, err)
}
if a.multiportOK == nil {
a.multiportOK = make(map[filter.Family]bool, 2)
@@ -87,12 +87,7 @@ func (a *Adapter) CheckRule(ctx context.Context, rule filter.FirewallRule) error
func (a *Adapter) Capabilities(context.Context) (filter.Capabilities, error) {
return filter.Capabilities{
Scopes: []filter.ScopePattern{{
Provider: filter.ProviderIptables, Families: []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}, Table: "filter",
Chains: native.BasicChains(),
Directions: []filter.Direction{filter.DirectionInput},
}}, Marker: true, OwnedChains: true, ExplicitPosition: true,
AtomicApply: false, TransactionalRollback: false,
Marker: true, OwnedChains: true, ExplicitPosition: true,
}, nil
}
@@ -718,6 +713,11 @@ func (systemBackend) ListChain(ctx context.Context, scope filter.Scope) (string,
var err error
if scope.Family == filter.FamilyIPv6 {
output, err = native.RunIPv6WithStdContext(ctx, scope.Table, "-S", scope.Chain)
if err != nil && !errors.Is(err, filter.ErrFamilyUnavailable) {
if table, readErr := native.RunIPv6WithStdContext(ctx, scope.Table, "-S"); readErr == nil && !containsChainDeclaration(table, scope.Chain) {
return "", fmt.Errorf("%w: iptables %s chain %s is not initialized", filter.ErrProviderUnavailable, scope.Family, scope.Chain)
}
}
} else {
output, err = native.RunWithStdContext(ctx, scope.Table, "-S", scope.Chain)
}
@@ -798,14 +798,14 @@ func runtimeExecutable(logical string) (string, error) {
switch logical {
case "ip6tables":
if !commands.IPv6Available() {
return "", fmt.Errorf("ip6tables command family is unavailable")
return "", fmt.Errorf("%w: ip6tables command family is unavailable", filter.ErrFamilyUnavailable)
}
return commands.IPv6, nil
case "iptables-restore":
return commands.Restore4, nil
case "ip6tables-restore":
if commands.Restore6 == "" {
return "", fmt.Errorf("ip6tables-restore command family is unavailable")
return "", fmt.Errorf("%w: ip6tables-restore command family is unavailable", filter.ErrFamilyUnavailable)
}
return commands.Restore6, nil
}
@@ -29,10 +29,7 @@ func (a *Adapter) Provider() filter.Provider { return filter.ProviderNftables }
func (a *Adapter) Capabilities(context.Context) (filter.Capabilities, error) {
return filter.Capabilities{
Scopes: []filter.ScopePattern{{
Provider: filter.ProviderNftables, Families: []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}, Table: "filter",
Chains: []string{filter.BasicBeforeChain, filter.IptablesInputChain, filter.BasicAfterChain}, Directions: []filter.Direction{filter.DirectionInput},
}}, Marker: true, AtomicApply: true, TransactionalRollback: true, OwnedChains: true, ExplicitPosition: true,
Marker: true, OwnedChains: true, ExplicitPosition: true,
}, nil
}
@@ -541,9 +538,14 @@ func parseRule(scope filter.Scope, raw, handle string, position int) filter.Obse
type systemBackend struct{}
func (systemBackend) ListChain(ctx context.Context, scope filter.Scope) (string, error) {
return cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout(
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second)).RunWithOptionalSudoAndStdout(
"nft", "-n", "-n", "-a", "list", "chain", nftables_helper.TableFamily(scope.Family), nftables_helper.TableName, nativeChainName(scope),
)
if err != nil && scope.Family == filter.FamilyIPv6 &&
(strings.Contains(err.Error(), "Address family not supported") || strings.Contains(err.Error(), "Protocol not supported")) {
return output, fmt.Errorf("%w: %v", filter.ErrFamilyUnavailable, err)
}
return output, err
}
func (systemBackend) Run(ctx context.Context, command filter.NativeCommand) error {
@@ -80,10 +80,7 @@ func (a *Adapter) AppendUnverified(ctx context.Context, rule filter.FirewallRule
func (a *Adapter) Capabilities(context.Context) (filter.Capabilities, error) {
return filter.Capabilities{
Scopes: []filter.ScopePattern{{
Provider: filter.ProviderUFW, Families: []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6},
Chains: []string{filter.UFWInputChain}, Directions: []filter.Direction{filter.DirectionInput},
}},
Marker: true, ExplicitPosition: true,
}, nil
}
@@ -0,0 +1,30 @@
package ufw
import (
"fmt"
"os"
"strings"
"github.com/1Panel-dev/1Panel/agent/utils/ini_conf"
)
func IPv6Enabled() (bool, error) {
return ipv6Enabled("/etc/default/ufw", "/proc/net/if_inet6")
}
func ipv6Enabled(configPath, kernelPath string) (bool, error) {
value, err := ini_conf.GetIniValue(configPath, "", "IPV6")
if err != nil {
return false, fmt.Errorf("load UFW IPv6 configuration: %w", err)
}
if !strings.EqualFold(strings.TrimSpace(value), "yes") {
return false, nil
}
if _, err := os.Stat(kernelPath); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("inspect kernel IPv6 support: %w", err)
}
return true, nil
}
@@ -8,7 +8,6 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall"
@@ -227,24 +226,8 @@ func isRemoteTarget(family, target string) bool {
}
func (l *iptablesNATAdapter) Enable() error {
if err := l.system.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IP forwarding: %w", err)
}
if l.backend.IPv6Available() {
if err := l.system.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IPv6 forwarding: %w", err)
}
}
data, err := l.system.ReadFile("/etc/sysctl.conf")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to read /etc/sysctl.conf: %w", err)
}
content := enableForwardingSysctls(string(data), l.backend.IPv6Available())
if err := l.system.WriteFile("/etc/sysctl.conf", []byte(content), constant.FilePerm); err != nil {
return fmt.Errorf("failed to persist IP forwarding: %w", err)
}
if err := l.system.RunWithOptionalSudo("sysctl", "-p"); err != nil {
return fmt.Errorf("failed to apply IP forwarding: %w", err)
if err := ensureForwardingSysctls(l.system, l.backend.IPv6Available()); err != nil {
return err
}
for _, family := range []string{forwarding.FamilyIPv4, forwarding.FamilyIPv6} {
@@ -450,43 +433,6 @@ func containsExactLine(output, want string) bool {
return false
}
func enableIPv4Forwarding(content string) string {
return enableForwardingSysctls(content, false)
}
func enableForwardingSysctls(content string, withIPv6 bool) string {
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
if withIPv6 {
wanted["net.ipv6.conf.all.forwarding"] = "net.ipv6.conf.all.forwarding = 1"
}
found := make(map[string]bool, len(wanted))
for index, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
if replacement, ok := wanted[key]; ok {
lines[index] = replacement
found[key] = true
}
}
for _, key := range []string{"net.ipv4.ip_forward", "net.ipv6.conf.all.forwarding"} {
if replacement, ok := wanted[key]; ok && !found[key] {
lines = append(lines, replacement)
}
}
if len(lines) > 0 && lines[0] == "" {
lines = lines[1:]
}
return strings.Join(lines, "\n") + "\n"
}
func (l *iptablesNATAdapter) InitStatus() (bool, bool, error) {
ipv4Init, ipv4Bind, err := l.familyInitStatus(forwarding.FamilyIPv4)
if err != nil {
@@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
@@ -56,21 +55,8 @@ func (n *nftablesAdapter) Reconcile(rules []forwarding.Rule) error {
}
func (n *nftablesAdapter) Enable() error {
if err := n.system.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IP forwarding: %w", err)
}
if err := n.system.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IPv6 forwarding: %w", err)
}
data, err := n.system.ReadFile("/etc/sysctl.conf")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to read /etc/sysctl.conf: %w", err)
}
if err := n.system.WriteFile("/etc/sysctl.conf", []byte(enableForwardingSysctls(string(data), true)), constant.FilePerm); err != nil {
return fmt.Errorf("failed to persist IP forwarding: %w", err)
}
if err := n.system.RunWithOptionalSudo("sysctl", "-p"); err != nil {
return fmt.Errorf("failed to apply IP forwarding: %w", err)
if err := ensureForwardingSysctls(n.system, true); err != nil {
return err
}
if err := ensureNftForwardTables(); err != nil {
return fmt.Errorf("initialize nftables forwarding table: %w", err)
@@ -0,0 +1,67 @@
package providers
import (
"errors"
"fmt"
"os"
"strings"
"github.com/1Panel-dev/1Panel/agent/constant"
)
func ensureForwardingSysctls(system forwardingSystem, withIPv6 bool) error {
paths := []string{"/proc/sys/net/ipv4/ip_forward"}
if withIPv6 {
paths = append(paths, "/proc/sys/net/ipv6/conf/all/forwarding")
}
for _, path := range paths {
if err := system.WriteFile(path, []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IP forwarding at %s: %w", path, err)
}
}
data, err := system.ReadFile("/etc/sysctl.conf")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to read /etc/sysctl.conf: %w", err)
}
content := enableForwardingSysctls(string(data), withIPv6)
if err := system.WriteFile("/etc/sysctl.conf", []byte(content), constant.FilePerm); err != nil {
return fmt.Errorf("failed to persist IP forwarding: %w", err)
}
if err := system.RunWithOptionalSudo("sysctl", "-p"); err != nil {
return fmt.Errorf("failed to apply IP forwarding: %w", err)
}
return nil
}
func enableForwardingSysctls(content string, withIPv6 bool) string {
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
if withIPv6 {
wanted["net.ipv6.conf.all.forwarding"] = "net.ipv6.conf.all.forwarding = 1"
}
found := make(map[string]bool, len(wanted))
for index, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
if replacement, ok := wanted[key]; ok {
lines[index] = replacement
found[key] = true
}
}
for _, key := range []string{"net.ipv4.ip_forward", "net.ipv6.conf.all.forwarding"} {
if replacement, ok := wanted[key]; ok && !found[key] {
lines = append(lines, replacement)
}
}
if len(lines) > 0 && lines[0] == "" {
lines = lines[1:]
}
return strings.Join(lines, "\n") + "\n"
}
@@ -9,6 +9,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
)
@@ -92,10 +93,14 @@ func RunIPv6WithStdContext(ctx context.Context, tab string, args ...string) (str
return "", executableErr
}
if !commands.IPv6Available() {
return "", fmt.Errorf("ip6tables command family is unavailable")
return "", fmt.Errorf("%w: ip6tables/ip6tables-restore are not installed", filter.ErrFamilyUnavailable)
}
stdout, err := runTables(ctx, commands.IPv6, tab, true, true, args...)
ignoreExist1 := len(args) == 0 || (args[0] != "-S" && args[0] != "-L" && args[0] != "-nL")
stdout, err := runTables(ctx, commands.IPv6, tab, ignoreExist1, true, args...)
if err != nil {
if strings.Contains(err.Error(), "Address family not supported") || strings.Contains(err.Error(), "Protocol not supported") {
return stdout, fmt.Errorf("%w: %v", filter.ErrFamilyUnavailable, err)
}
global.LOG.Errorf("ip6tables command failed [table=%s, args=%s]: %v", tab, strings.Join(args, " "), err)
return stdout, err
}
@@ -120,13 +125,6 @@ func CheckIPv6ChainExist(tab, chain string) (bool, error) {
return false, nil
}
func RunWithoutIgnore(tab string, args ...string) (string, error) {
stdout, err := runIptables(context.Background(), tab, false, false, args...)
if err != nil {
return stdout, err
}
return stdout, nil
}
func Run(tab string, args ...string) error {
if _, err := RunWithStd(tab, args...); err != nil {
return err
@@ -23,17 +23,26 @@ type FilterRules struct {
}
func ReadFilterRulesByChain(chain string) ([]FilterRules, error) {
return readFilterRulesByChain(chain, RunWithStd)
}
func readFilterRulesByChain(chain string, run func(string, ...string) (string, error)) ([]FilterRules, error) {
var rules []FilterRules
if cmd.CheckIllegal(chain) {
return rules, buserr.New("ErrCmdIllegal")
}
stdout, err := RunWithStd(FilterTab, "-nL", chain)
stdout, err := run(FilterTab, "-nL", chain)
if err != nil {
return rules, fmt.Errorf("load filter fules by chain %s failed, %v", chain, err)
}
lines := strings.Split(stdout, "\n")
for i := 0; i < len(lines); i++ {
fields := strings.Fields(lines[i])
if len(fields) > 2 && strings.Contains(fields[2], ":") {
fields = append(fields, "")
copy(fields[3:], fields[2:])
fields[2] = "--"
}
if len(fields) < 5 {
continue
}
@@ -64,7 +73,7 @@ func LoadFamilyInitStatus(family, tab string) (bool, bool, error) {
case constant.FirewallFamilyIPv4:
return loadInitStatus(tab, RunWithStd, true)
case constant.FirewallFamilyIPv6:
return loadInitStatus(tab, RunIPv6WithStd, false)
return loadInitStatus(tab, RunIPv6WithStd, true)
default:
return false, false, fmt.Errorf("unsupported iptables family %q", family)
}
@@ -191,7 +200,7 @@ func loadPort(position string, portStr []string) string {
}
func loadIP(ipStr string) string {
if ipStr == ANYWHERE || ipStr == "0.0.0.0/0" {
if ipStr == ANYWHERE || ipStr == "0.0.0.0/0" || ipStr == "::/0" {
return ""
}
return ipStr
+50 -15
View File
@@ -6,20 +6,25 @@ import (
"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"
)
func (m *Manager) EnsureIPv6BaseChains() error {
return EnsureIPv6BaseChains(m.panelPort())
ports, err := m.loadRequiredPorts()
if err != nil {
return err
}
return EnsureIPv6BaseChains(m.panelPort(), ports)
}
func RepairIPv6BaseChains(panelPort string) error {
func RepairIPv6BaseChains(panelPort string, ports []firewall.PortWhitelist) error {
initialized, bound, err := LoadFamilyInitStatus(constant.FirewallFamilyIPv6, "base")
if err != nil {
return err
}
return repairIPv6BaseChains(initialized, bound, BindIPv6BaseChains, func() error {
return EnsureIPv6BaseChains(panelPort)
return EnsureIPv6BaseChains(panelPort, ports)
})
}
@@ -33,7 +38,7 @@ func repairIPv6BaseChains(initialized, bound bool, bind, ensure func() error) er
return ensure()
}
func EnsureIPv6BaseChains(panelPort string) error {
func EnsureIPv6BaseChains(panelPort string, ports []firewall.PortWhitelist) error {
commands, err := lifecycle.ResolveIptablesCommands()
if err != nil || !commands.IPv6Available() {
return fmt.Errorf("ip6tables and ip6tables-restore are required")
@@ -41,21 +46,16 @@ func EnsureIPv6BaseChains(panelPort string) error {
if panelPort == "" {
return fmt.Errorf("panel port is required")
}
if err := ensureBaseChainsFamily(true); err != nil {
return err
}
script, err := buildBaseChainsRestoreScript(global.Dir.FirewallDir, panelPort, true)
output, err := RunIPv6WithStd(FilterTab, "-S")
if err != nil {
return err
}
protectedRules := []string{
"-A " + BasicBeforeChain + " " + IoRuleIn,
"-A " + BasicBeforeChain + " " + EstablishedRule,
if err := ensureBaseChainsFamily(true); err != nil {
return err
}
for _, rule := range protectedRules {
if !containsIptablesRule(script, rule) {
script = strings.Replace(script, "COMMIT\n", rule+"\nCOMMIT\n", 1)
}
script, err := buildIPv6BaseInitializationScript(global.Dir.FirewallDir, panelPort, ports, output)
if err != nil {
return err
}
if err := restoreRules(commands.Restore6, script); err != nil {
return fmt.Errorf("batch initialize IPv6 base chains: %w", err)
@@ -90,3 +90,38 @@ func BindIPv6BaseChains() error {
}
return setBaseChainBindings(true, true)
}
func buildIPv6BaseInitializationScript(dir, panelPort string, ports []firewall.PortWhitelist, output string) (string, error) {
for _, chain := range BasicChains() {
if !containsIptablesRule(output, "-N "+chain) {
return buildBaseChainsRestoreScript(dir, panelPort, true, ports...)
}
}
defaults, err := ipv6BaseDefaultRules(panelPort, ports)
if err != nil {
return "", err
}
var script strings.Builder
script.WriteString("*filter\n")
for _, rule := range defaults {
if !containsIptablesRule(output, rule) {
script.WriteString(rule + "\n")
}
}
script.WriteString("COMMIT\n")
return script.String(), nil
}
func ipv6BaseDefaultRules(panelPort string, ports []firewall.PortWhitelist) ([]string, error) {
ports, err := firewall.NormalizeRequiredPorts(append([]firewall.PortWhitelist{{Port: panelPort, Protocol: "tcp"}}, ports...))
if err != nil {
return nil, err
}
rules := []string{"-A " + BasicBeforeChain + " " + IoRuleIn, "-A " + BasicBeforeChain + " " + EstablishedRule}
for _, port := range ports {
if port.Family == "" || port.Family == constant.FirewallFamilyIPv6 {
rules = append(rules, iptablesPortRuleLine("-A", BasicBeforeChain, port.Protocol, port.Port))
}
}
return append(rules, "-A "+BasicAfterChain+" "+DropAllTcp, "-A "+BasicAfterChain+" "+DropAllUdp), nil
}
+76 -36
View File
@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
)
@@ -28,7 +29,7 @@ func (m *Manager) Cleanup() error {
return err
}
if commands, err := lifecycle.ResolveIptablesCommands(); err == nil && commands.IPv6Available() {
if err := cleanupBaseChains(true); err != nil {
if err := cleanupBaseChains(true); err != nil && !errors.Is(err, filter.ErrFamilyUnavailable) {
return err
}
}
@@ -86,7 +87,7 @@ func (m *Manager) disableBase() error {
if err := setBaseChainBindings(false, false); err != nil {
return err
}
if err := UnbindIPv6BaseChains(); err != nil {
if err := UnbindIPv6BaseChains(); err != nil && !errors.Is(err, filter.ErrFamilyUnavailable) {
return err
}
return m.updateSetting("IptablesStatus", constant.StatusDisable)
@@ -235,7 +236,7 @@ func saveBaseChains() error {
return nil
}
func RestoreBaseChains(panelPort string) error {
func RestoreBaseChains(panelPort string, requiredPorts []firewall.PortWhitelist) error {
port, err := strconv.Atoi(panelPort)
if err != nil || port < 1 || port > 65535 {
return fmt.Errorf("invalid panel port %q", panelPort)
@@ -260,7 +261,7 @@ func RestoreBaseChains(panelPort string) error {
if err := ensureBaseChainsFamily(true); err != nil {
return err
}
input, err = buildBaseChainsRestoreScript(global.Dir.FirewallDir, panelPort, true)
input, err = buildBaseChainsRestoreScript(global.Dir.FirewallDir, panelPort, true, requiredPorts...)
if err != nil {
return err
}
@@ -270,7 +271,7 @@ func RestoreBaseChains(panelPort string) error {
return nil
}
func buildBaseChainsRestoreScript(firewallDir, panelPort string, ipv6 bool) (string, error) {
func buildBaseChainsRestoreScript(firewallDir, panelPort string, ipv6 bool, requiredPorts ...firewall.PortWhitelist) (string, error) {
var script strings.Builder
script.WriteString("*filter\n")
for _, chain := range BasicChains() {
@@ -313,6 +314,17 @@ func buildBaseChainsRestoreScript(firewallDir, panelPort string, ipv6 bool) (str
script.WriteString(panelRule)
script.WriteByte('\n')
}
if ipv6 {
defaults, err := ipv6BaseDefaultRules(panelPort, requiredPorts)
if err != nil {
return "", err
}
for _, rule := range defaults {
if !containsIptablesRule(script.String(), rule) {
script.WriteString(rule + "\n")
}
}
}
script.WriteString("COMMIT\n")
return script.String(), nil
}
@@ -322,7 +334,7 @@ func (m *Manager) initPreRules() error {
if err != nil {
return err
}
return applyRequiredFirewallPortWhiteListRules(requiredPorts, false, true)
return applyRequiredFirewallPortWhiteListRules(requiredPorts, false, true, false)
}
func (m *Manager) ensureIPv6BaseChains() error {
@@ -330,7 +342,7 @@ func (m *Manager) ensureIPv6BaseChains() error {
if err != nil || !commands.IPv6Available() {
return nil
}
return EnsureIPv6BaseChains(m.panelPort())
return m.EnsureIPv6BaseChains()
}
func (m *Manager) SyncRequiredPorts(withSave bool) error {
@@ -338,27 +350,60 @@ func (m *Manager) SyncRequiredPorts(withSave bool) error {
if err != nil {
return err
}
return applyRequiredFirewallPortWhiteListRules(requiredPorts, withSave, false)
commands, err := lifecycle.ResolveIptablesCommands()
if err != nil {
return err
}
for _, family := range []string{constant.FirewallFamilyIPv4, constant.FirewallFamilyIPv6} {
ipv6 := family == constant.FirewallFamilyIPv6
if ipv6 && !commands.IPv6Available() {
continue
}
checkChain := CheckChainExist
if ipv6 {
checkChain = CheckIPv6ChainExist
}
initialized, err := checkChain(FilterTab, BasicBeforeChain)
if ipv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
if !initialized {
continue
}
if err := applyRequiredFirewallPortWhiteListRules(requiredPorts, withSave, false, ipv6); err != nil {
return err
}
}
return nil
}
func applyRequiredFirewallPortWhiteListRules(portWhiteList []firewall.PortWhitelist, withSave, includeDefaults bool) error {
portWhiteList, err := validateRequiredFirewallPorts(portWhiteList)
func applyRequiredFirewallPortWhiteListRules(portWhiteList []firewall.PortWhitelist, withSave, includeDefaults, ipv6 bool) error {
portWhiteList, err := firewall.NormalizeRequiredPorts(portWhiteList)
if err != nil {
return err
}
beforeRules, err := ReadFilterRulesByChain(BasicBeforeChain)
run, save := RunWithStd, SaveRulesToFile
beforeFile, afterFile := BasicBeforeFileName, BasicAfterFileName
if ipv6 {
run, save = RunIPv6WithStd, SaveIPv6RulesToFile
beforeFile, afterFile = IPv6FileName(beforeFile), IPv6FileName(afterFile)
}
beforeRules, err := readFilterRulesByChain(BasicBeforeChain, run)
if err != nil {
return err
}
afterRules, err := ReadFilterRulesByChain(BasicAfterChain)
afterRules, err := readFilterRulesByChain(BasicAfterChain, run)
if err != nil {
return err
}
beforeRaw, err := RunWithStd(FilterTab, "-S", BasicBeforeChain)
beforeRaw, err := run(FilterTab, "-S", BasicBeforeChain)
if err != nil {
return err
}
afterRaw, err := RunWithStd(FilterTab, "-S", BasicAfterChain)
afterRaw, err := run(FilterTab, "-S", BasicAfterChain)
if err != nil {
return err
}
@@ -368,34 +413,21 @@ func applyRequiredFirewallPortWhiteListRules(portWhiteList []firewall.PortWhitel
if resolveErr != nil {
return resolveErr
}
if err := restoreRules(commands.Restore4, script); err != nil {
return fmt.Errorf("batch sync required IPv4 firewall ports: %w", err)
restore := commands.Restore4
if ipv6 {
restore = commands.Restore6
}
if err := restoreRules(restore, script); err != nil {
return fmt.Errorf("batch sync required firewall ports with %s: %w", restore, err)
}
}
if !withSave {
return nil
}
if err := SaveRulesToFile(FilterTab, BasicBeforeChain, BasicBeforeFileName); err != nil {
if err := save(FilterTab, BasicBeforeChain, beforeFile); err != nil {
return err
}
return SaveRulesToFile(FilterTab, BasicAfterChain, BasicAfterFileName)
}
func validateRequiredFirewallPorts(ports []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
result := make([]firewall.PortWhitelist, 0, len(ports))
for _, port := range ports {
port.Protocol = strings.ToLower(strings.TrimSpace(port.Protocol))
if port.Protocol != "tcp" && port.Protocol != "udp" {
return nil, fmt.Errorf("unsupported required firewall port protocol %q", port.Protocol)
}
portNumber, err := strconv.Atoi(strings.TrimSpace(port.Port))
if err != nil || portNumber < 1 || portNumber > 65535 {
return nil, fmt.Errorf("invalid required firewall port %q", port.Port)
}
port.Port = strconv.Itoa(portNumber)
result = append(result, port)
}
return firewall.NormalizePortWhitelist(result), nil
return save(FilterTab, BasicAfterChain, afterFile)
}
func buildRequiredPortsRestoreScript(
@@ -474,9 +506,17 @@ func containsIptablesRule(output, rule string) bool {
}
func countIptablesRule(output, rule string) int {
canonical := func(value string) string {
value = strings.TrimSpace(value)
if strings.Contains(value, " -j ACCEPT") {
return strings.Replace(value, " -j ACCEPT", "", 1) + " -j ACCEPT"
}
return value
}
rule = canonical(rule)
count := 0
for _, line := range strings.Split(output, "\n") {
if strings.TrimSpace(line) == rule {
if canonical(line) == rule {
count++
}
}
@@ -162,36 +162,6 @@ func InstalledProviders() []string {
return providers
}
func NewNetfilterClients() ([]Client, error) {
clients := make([]Client, 0, 2)
if which("nft") {
client, err := providers.NewNftables()
if err != nil {
return nil, err
}
clients = append(clients, client)
}
if commands, err := ResolveIptablesCommands(); err == nil {
client, err := providers.NewIptables(commands.IPv4)
if err != nil {
return nil, err
}
clients = append(clients, client)
}
if len(clients) == 0 {
return nil, fmt.Errorf("no supported forwarding backend detected (iptables/iptables-nft/nftables)")
}
return clients, nil
}
func DetectProvider() (string, error) {
runtime, err := DetectRuntime()
if err != nil {
return "", err
}
return runtime.Provider, nil
}
type State struct {
Name string
IsActive bool
@@ -7,14 +7,10 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
type UFW struct {
CmdStr string
}
type UFW struct{}
func NewUFW() (*UFW, error) {
var ufw UFW
ufw.CmdStr = fmt.Sprintf("LANGUAGE=en_US:en %s ufw", cmd.SudoHandleCmd())
return &ufw, nil
return &UFW{}, nil
}
func (f *UFW) Name() string {
+25
View File
@@ -0,0 +1,25 @@
package firewall
import (
"fmt"
"slices"
"strings"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
)
func ReadNftObject(run func(...string) (string, error), args ...string) (string, bool, error) {
output, err := run(args...)
if err == nil {
return output, true, nil
}
message := err.Error()
if slices.Contains(args, "ip6") && (strings.Contains(message, "Address family not supported") || strings.Contains(message, "Protocol not supported")) {
return "", false, fmt.Errorf("%w: %v", filter.ErrFamilyUnavailable, err)
}
if strings.Contains(message, "Error:") && strings.Contains(message, "No such file or directory") &&
!strings.Contains(message, "Operation not permitted") && !strings.Contains(message, "Permission denied") {
return "", false, nil
}
return "", false, err
}
+25 -27
View File
@@ -124,7 +124,7 @@ func (m *Manager) initPreRules() error {
if err != nil {
return err
}
ports, err = validateRequiredPorts(ports)
ports, err = firewall.NormalizeRequiredPorts(ports)
if err != nil {
return err
}
@@ -153,16 +153,37 @@ func (m *Manager) SyncRequiredPorts() error {
if err != nil {
return err
}
ports, err = validateRequiredPorts(ports)
ports, err = firewall.NormalizeRequiredPorts(ports)
if err != nil {
return err
}
commands, err := requiredPortSyncCommands(run, ports)
if err != nil {
return err
}
if err := runBatch(commands...); err != nil {
return err
}
return PersistRuleset(context.Background())
}
func requiredPortSyncCommands(run func(...string) (string, error), ports []firewall.PortWhitelist) ([][]string, error) {
commands := make([][]string, 0)
for _, family := range []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6} {
tableFamily := TableFamily(family)
_, exists, err := firewall.ReadNftObject(run, "list", "table", tableFamily, TableName)
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return nil, err
}
if !exists {
continue
}
stdout, err := run("-n", "-a", "list", "chain", tableFamily, TableName, BasicBeforeChain)
if err != nil {
return err
return nil, err
}
existing := requiredPortRules(stdout)
missing, staleHandles := requiredPortChanges(existing, ports)
@@ -173,30 +194,7 @@ func (m *Manager) SyncRequiredPorts() error {
commands = append(commands, []string{"delete", "rule", tableFamily, TableName, BasicBeforeChain, "handle", handle})
}
}
if err := runBatch(commands...); err != nil {
return err
}
if err := PersistRuleset(context.Background()); err != nil {
return err
}
return nil
}
func validateRequiredPorts(ports []firewall.PortWhitelist) ([]firewall.PortWhitelist, error) {
result := make([]firewall.PortWhitelist, 0, len(ports))
for _, port := range ports {
port.Protocol = strings.ToLower(strings.TrimSpace(port.Protocol))
if port.Protocol != "tcp" && port.Protocol != "udp" {
return nil, fmt.Errorf("unsupported required firewall port protocol %q", port.Protocol)
}
portNumber, err := strconv.Atoi(strings.TrimSpace(port.Port))
if err != nil || portNumber < 1 || portNumber > 65535 {
return nil, fmt.Errorf("invalid required firewall port %q", port.Port)
}
port.Port = strconv.Itoa(portNumber)
result = append(result, port)
}
return firewall.NormalizePortWhitelist(result), nil
return commands, nil
}
type requiredPortRule struct {
@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
)
@@ -18,12 +19,20 @@ const RulesFile = "1panel_filter.nft"
func PersistRuleset(ctx context.Context) error {
var ruleset strings.Builder
run := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second))
for _, family := range []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6} {
stdout, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second)).
RunWithOptionalSudoAndStdout("nft", "list", "table", TableFamily(family), TableName)
stdout, exists, err := firewall.ReadNftObject(func(args ...string) (string, error) {
return run.RunWithOptionalSudoAndStdout("nft", args...)
}, "list", "table", TableFamily(family), TableName)
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
if !exists {
continue
}
ruleset.WriteString(stdout)
if !strings.HasSuffix(stdout, "\n") {
ruleset.WriteByte('\n')
@@ -42,7 +51,14 @@ func Restore() error {
existing := make([]string, 0, 2)
for _, family := range []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6} {
tableFamily := TableFamily(family)
if _, err := run("list", "table", tableFamily, TableName); err == nil {
_, exists, err := firewall.ReadNftObject(run, "list", "table", tableFamily, TableName)
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
if exists {
existing = append(existing, tableFamily)
}
}
@@ -152,13 +152,13 @@ func hasBaseChainBinding(output string) bool {
func loadFamilyInitStatus(family filter.Family) (bool, bool, error) {
for _, chain := range BasicChains() {
if _, err := run("list", "chain", TableFamily(family), TableName, chain); err != nil {
return false, false, nil
if _, exists, err := firewallutil.ReadNftObject(run, "list", "chain", TableFamily(family), TableName, chain); err != nil || !exists {
return false, false, err
}
}
stdout, err := run("list", "chain", TableFamily(family), TableName, InputChain)
if err != nil {
return false, false, nil
stdout, exists, err := firewallutil.ReadNftObject(run, "list", "chain", TableFamily(family), TableName, InputChain)
if err != nil || !exists {
return false, false, err
}
for _, chain := range BasicChains() {
if !strings.Contains(stdout, "jump "+chain) {
+17
View File
@@ -284,3 +284,20 @@ func ExcludePorts(ports, excluded []PortWhitelist) []PortWhitelist {
}
return result
}
func NormalizeRequiredPorts(ports []PortWhitelist) ([]PortWhitelist, error) {
result := make([]PortWhitelist, 0, len(ports))
for _, port := range ports {
port.Protocol = strings.ToLower(strings.TrimSpace(port.Protocol))
if port.Protocol != "tcp" && port.Protocol != "udp" {
return nil, fmt.Errorf("unsupported required firewall port protocol %q", port.Protocol)
}
portNumber, err := strconv.Atoi(strings.TrimSpace(port.Port))
if err != nil || portNumber < 1 || portNumber > 65535 {
return nil, fmt.Errorf("invalid required firewall port %q", port.Port)
}
port.Port = strconv.Itoa(portNumber)
result = append(result, port)
}
return NormalizePortWhitelist(result), nil
}
+10 -5
View File
@@ -19,6 +19,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/core/app/dto"
@@ -31,7 +32,6 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/controller"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/firewall"
"github.com/1Panel-dev/1Panel/core/utils/menutree"
"github.com/1Panel-dev/1Panel/core/utils/passkey"
"github.com/1Panel-dev/1Panel/core/utils/req_helper/proxy_local"
@@ -42,6 +42,8 @@ import (
type SettingService struct{}
var panelPortChangeMu sync.Mutex
type ISettingService interface {
GetSettingInfo() (*dto.SettingInfo, error)
GetSettingBaseInfo() (*dto.SettingBaseInfo, error)
@@ -346,9 +348,9 @@ func (u *SettingService) UpdateProxy(req dto.ProxyUpdate) error {
}
func (u *SettingService) UpdatePort(port uint) error {
if common.ScanPort(int(port)) {
return buserr.WithDetail("ErrPortInUsed", port, nil)
}
panelPortChangeMu.Lock()
defer panelPortChangeMu.Unlock()
oldPort, err := settingRepo.Get(repo.WithByKey("ServerPort"))
if err != nil {
return err
@@ -356,7 +358,10 @@ func (u *SettingService) UpdatePort(port uint) error {
if oldPort.Value == fmt.Sprintf("%v", port) {
return nil
}
if err := firewall.UpdatePort(oldPort.Value, fmt.Sprintf("%v", port)); err != nil {
if common.ScanPort(int(port)) {
return buserr.WithDetail("ErrPortInUsed", port, nil)
}
if err := proxy_local.UpdatePanelPort(oldPort.Value, port); err != nil {
return err
}
+17 -3
View File
@@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/core/utils/cmd"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/req_helper/proxy_local"
upgradeUtil "github.com/1Panel-dev/1Panel/core/utils/upgrade"
"github.com/spf13/cobra"
"golang.org/x/term"
@@ -341,13 +342,26 @@ func port() {
fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortFormat"))
return
}
newPortStr = strconv.Itoa(newPort)
db, err := loadDBConn("core.db")
if err != nil {
fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
return
}
var oldPort setting
if err := db.Where("key = ?", "ServerPort").First(&oldPort).Error; err != nil {
fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()}))
return
}
if oldPort.Value == newPortStr {
return
}
if common.ScanPort(newPort) {
fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortUsed"))
return
}
db, err := loadDBConn("core.db")
if err != nil {
fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()}))
if err := proxy_local.UpdatePanelPort(oldPort.Value, uint(newPort)); err != nil {
fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()}))
return
}
if err := setSettingByKey(db, "ServerPort", newPortStr); err != nil {
+2 -1
View File
@@ -77,7 +77,8 @@ func Proxy() gin.HandlerFunc {
func isInternalOnlyAgentEndpoint(reqPath string) bool {
normalizedPath := path.Clean(reqPath)
return normalizedPath == "/api/v2/xpack/alert/offline/email" ||
normalizedPath == "/api/v2/xpack/alert/offline/webhook"
normalizedPath == "/api/v2/xpack/alert/offline/webhook" ||
normalizedPath == "/api/v2/hosts/firewall/port"
}
func proxyLocalAgent(c *gin.Context) {
-51
View File
@@ -1,51 +0,0 @@
package firewall
import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/core/utils/cmd"
)
func UpdatePort(oldPort, newPort string) error {
firewalld := cmd.Which("firewalld")
if firewalld {
status, _ := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("firewall-cmd", "--state")
isRunning := status == "running\n"
if isRunning {
return firewallUpdatePort(oldPort, newPort)
}
}
ufw := cmd.Which("ufw")
if !ufw {
return nil
}
status, _ := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("ufw", "status")
isRuning := strings.Contains(status, "Status: active")
if isRuning {
return ufwUpdatePort(oldPort, newPort)
}
return nil
}
func firewallUpdatePort(oldPort, newPort string) error {
stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--add-port="+newPort+"/tcp", "--permanent")
if err != nil {
return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout)
}
_, _ = cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--remove-port="+oldPort+"/tcp", "--permanent")
_, _ = cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--reload")
return nil
}
func ufwUpdatePort(oldPort, newPort string) error {
stdout, err := cmd.NewCommandMgr().RunWithStdout("ufw", "allow", newPort)
if err != nil {
return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout)
}
_, _ = cmd.NewCommandMgr().RunWithStdout("ufw", "delete", "allow", oldPort)
return nil
}
@@ -0,0 +1,28 @@
package proxy_local
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
)
func UpdatePanelPort(oldPort string, newPort uint) error {
previousPort, err := strconv.ParseUint(oldPort, 10, 16)
if err != nil || previousPort == 0 {
return fmt.Errorf("invalid old panel port %q", oldPort)
}
if newPort == 0 || newPort > 65535 {
return fmt.Errorf("invalid new panel port %d", newPort)
}
body, err := json.Marshal(struct {
OldPort uint `json:"oldPort"`
NewPort uint `json:"newPort"`
}{OldPort: uint(previousPort), NewPort: newPort})
if err != nil {
return err
}
_, err = NewLocalClient("/api/v2/hosts/firewall/port", http.MethodPost, bytes.NewReader(body), nil)
return err
}
@@ -16,6 +16,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/i18n"
)
@@ -122,7 +123,11 @@ func (c *ReusableClient) RequestWithContext(requestContext context.Context, reqU
return nil, fmt.Errorf("json umarshal resp data failed, err: %v", err)
}
if respJson.Code != http.StatusOK {
return nil, errors.New(strings.ReplaceAll(respJson.Message, i18n.Get("ErrInternalServerKey"), ""))
message := respJson.Message
if global.I18n != nil {
message = strings.ReplaceAll(message, i18n.Get("ErrInternalServerKey"), "")
}
return nil, errors.New(message)
}
return respJson.Data, nil
+5
View File
@@ -61,6 +61,7 @@ export namespace Firewall {
ipv6: BackendFamilyStatus;
}
export interface ForwardRuleSearch extends ReqPage {
all?: boolean;
strategy: string;
info: string;
}
@@ -154,6 +155,7 @@ export namespace Firewall {
ruleKey: string;
origin: RuleOrigin;
protected?: boolean;
expanded?: boolean;
marker?: string;
observedInstanceKey?: string;
}
@@ -165,6 +167,8 @@ export namespace Firewall {
}
export interface InventoryItem {
incompatible?: boolean;
error?: string;
rule: Rule;
observed?: ObservedRule;
desired?: DesiredRule;
@@ -174,6 +178,7 @@ export namespace Firewall {
}
export type ScopeNoticeCode =
| 'family_unavailable'
| 'default_scope_mismatch'
| 'managed_scope_inactive'
| 'unmanaged_active_scopes'
@@ -124,21 +124,24 @@ const onImport = async () => {
}
for (const group of groups.values()) {
const policy = group[0];
try {
await upsertDockerPortGuardPolicies({
endpoints: group.map(({ family, hostIP, hostPort, protocol }) => ({
family,
hostIP,
hostPort,
protocol,
})),
mode: policy.mode,
sources: policy.sources,
description: policy.description,
});
success += group.length;
} catch {
failed += group.length;
for (let offset = 0; offset < group.length; offset += 256) {
const batch = group.slice(offset, offset + 256);
try {
await upsertDockerPortGuardPolicies({
endpoints: batch.map(({ family, hostIP, hostPort, protocol }) => ({
family,
hostIP,
hostPort,
protocol,
})),
mode: policy.mode,
sources: policy.sources,
description: policy.description,
});
success += batch.length;
} catch {
failed += batch.length;
}
}
}
loading.value = false;
@@ -242,25 +242,20 @@ const checkDataFormat = (item: any): boolean => {
const compareRules = (importedRules: any[]) => {
const newRules: any[] = [];
const conflictRules: any[] = [];
const duplicateRules: any[] = [];
const ruleKey = (rule: Firewall.RuleForward | Firewall.RuleInfo) =>
`${rule.family}:${rule.protocol}:${rule.port}:${rule.targetIP}:${rule.targetPort}:${rule.interface || ''}`;
const existingKeys = new Set(currentRules.value.map(ruleKey));
for (const importedRule of importedRules) {
const key = `${importedRule.family}:${importedRule.protocol}:${importedRule.port}:${importedRule.targetIP}:${importedRule.targetPort}:${importedRule.interface || ''}`;
const existingRule = currentRules.value.find((rule) => {
const existingKey = `${rule.family}:${rule.protocol}:${rule.port}:${rule.targetIP}:${rule.targetPort}:${rule.interface || ''}`;
return existingKey === key;
});
if (!existingRule) {
if (!existingKeys.has(ruleKey(importedRule))) {
newRules.push({ ...importedRule, status: 'new' });
} else {
duplicateRules.push({ ...importedRule, status: 'duplicate' });
}
}
displayData.value = [...newRules, ...conflictRules, ...duplicateRules];
displayData.value = [...newRules, ...duplicateRules];
paginationConfig.total = displayData.value.length;
search();
};
@@ -45,12 +45,7 @@
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button
v-permission
v-node-admin
:disabled="paginationConfig.total === 0"
@click="onExport"
>
<el-button v-permission v-node-admin :disabled="loading" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
@@ -154,7 +149,6 @@ import { ElMessageBox } from 'element-plus';
const loading = ref();
const selects = ref<any>([]);
const searchName = ref();
const searchStrategy = ref('');
const isInit = ref(false);
const isBind = ref(false);
@@ -229,7 +223,7 @@ const search = async () => {
return;
}
let params = {
strategy: searchStrategy.value,
strategy: '',
info: searchName.value,
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
@@ -318,12 +312,12 @@ const onImport = () => {
};
const loadAllRules = async (): Promise<Firewall.RuleForward[]> => {
if (paginationConfig.total === 0) return [];
const response = await searchForwardRule({
all: true,
strategy: '',
info: '',
page: 1,
pageSize: Math.max(1, paginationConfig.total),
pageSize: paginationConfig.pageSize,
});
return (response.data.items || []).map((item) => ({
operation: '',
@@ -156,7 +156,7 @@ const normalizeImportedRule = (rule: Firewall.Rule): Firewall.Rule[] => {
...rule,
uuid: undefined,
nativeKind: undefined,
priority: undefined,
priority: provider.value === 'firewalld' && rule.scope.provider === 'firewalld' ? rule.priority : undefined,
orderIndex: undefined,
orderBucket: undefined,
scope: targetScope(family),
@@ -234,30 +234,30 @@ const onImport = async () => {
let success = 0;
let failed = 0;
try {
const plans: Firewall.RuleCheckResult[] = [];
for (let offset = 0; offset < selects.value.length; offset += 256) {
const batch = selects.value.slice(offset, offset + 256);
plans.push(...(await checkFirewallRules({ items: batch.map((rule) => ({ rule })) })).data.items);
}
const items: Firewall.CreateItem[] = [];
for (const plan of plans) {
try {
const item = importedCreateRequest(plan);
if (item) {
items.push(item);
} else {
success++;
const selected = [...selects.value].sort((left, right) =>
JSON.stringify(left.scope).localeCompare(JSON.stringify(right.scope)),
);
for (let offset = 0; offset < selected.length; offset += 256) {
const batch = selected.slice(offset, offset + 256);
const plans = (await checkFirewallRules({ items: batch.map((rule) => ({ rule })) })).data.items;
const items: Firewall.CreateItem[] = [];
for (const plan of plans) {
try {
const item = importedCreateRequest(plan);
if (item) {
items.push(item);
} else {
success++;
}
} catch {
failed++;
}
} catch {
failed++;
}
}
items.sort((left, right) => JSON.stringify(left.rule.scope).localeCompare(JSON.stringify(right.rule.scope)));
for (let offset = 0; offset < items.length; offset += 256) {
const batch = items.slice(offset, offset + 256);
const result = (await createFirewallRules({ items: batch })).data;
success += result.succeeded;
failed += result.failed + result.skipped;
if (items.length > 0) {
const result = (await createFirewallRules({ items })).data;
success += result.succeeded;
failed += result.failed + result.skipped;
}
}
} catch {
failed += selects.value.length - success - failed;
@@ -874,7 +874,7 @@ const toRuleRows = (items: Firewall.InventoryItem[]): RuleRow[] =>
return {
...item,
rowKey:
item.desired?.uuid ||
(item.desired && `${scopeIdentity(item.rule)}:${item.desired.rule.uuid || item.desired.uuid}`) ||
item.observed?.instanceKey ||
item.observed?.marker ||
`${scopeIdentity(item.rule)}:${nativeGroup}:${item.observed?.locator.position ?? index}`,
@@ -933,6 +933,8 @@ const notices = computed<DisplayNotice[]>(() => {
const scopeNoticeText = (notice: Firewall.ScopeNotice) => {
const value = (notice.values || []).join(', ') || '-';
switch (notice.code) {
case 'family_unavailable':
return value;
case 'default_scope_mismatch':
return i18n.global.t('firewall.scopeDefaultMismatch', [value]);
case 'managed_scope_inactive':
@@ -983,6 +985,7 @@ const ruleStateDetail = (row: Firewall.InventoryItem) =>
const ruleStateTooltip = (row: Firewall.InventoryItem) => `${ruleStateTitle(row)}:${ruleStateDetail(row)}`;
const ruleIssueText = (row: Firewall.InventoryItem) => {
if (row.error) return row.error;
if (row.observed?.persistence && row.observed.persistence !== 'converged') {
return i18n.global.t('firewall.plan_runtime_permanent_mismatch');
}
@@ -1078,10 +1081,14 @@ const usageOwnersSummary = (owners: string[]) => {
};
const deleteRulesConfirmMessage = (selected: RuleRow[]) => {
const count = new Set(selected.map((row) => row.desired?.uuid)).size;
const accepted = selected.filter((row) => row.rule.action === 'accept' && Boolean(row.observed));
const risky = accepted.filter((row) => isWildcardDestinationPort(row.rule) || ruleUsageEntries(row).length > 0);
if (selected.length > 1 && risky.length > 0) {
return i18n.global.t('firewall.deleteRiskRulesConfirm', [selected.length, risky.length]);
return i18n.global.t('firewall.deleteRiskRulesConfirm', [
count,
new Set(risky.map((row) => row.desired?.uuid)).size,
]);
}
if (selected.length === 1 && accepted.length === 1) {
const [row] = accepted;
@@ -1096,7 +1103,7 @@ const deleteRulesConfirmMessage = (selected: RuleRow[]) => {
return i18n.global.t('firewall.deleteUsedRuleConfirm', [usageOwnersSummary(owners)]);
}
}
return i18n.global.t('firewall.deleteRuleConfirm', [selected.length]);
return i18n.global.t('firewall.deleteRuleConfirm', [count]);
};
const removeRules = async (selected: RuleRow[]) => {
@@ -1110,7 +1117,7 @@ const removeRules = async (selected: RuleRow[]) => {
return;
}
loading.value = true;
const uuids = selected.flatMap((row) => (row.desired?.uuid ? [row.desired.uuid] : []));
const uuids = [...new Set(selected.flatMap((row) => (row.desired?.uuid ? [row.desired.uuid] : [])))];
try {
let succeeded = 0;
let failed = 0;
@@ -1255,6 +1262,7 @@ const removeRule = (row: RuleRow) => removeRules([row]);
const isEditableManagedRule = (row: Firewall.InventoryItem) =>
Boolean(row.desired?.uuid) &&
!row.desired?.expanded &&
(row.desired?.origin === 'created' || row.desired?.origin === 'adopted') &&
!row.desired?.protected &&
!isIptablesSystemPresetScope(row.rule.scope) &&