mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-10 21:21:13 +08:00
fix(firewall): harden port switching and rule synchronization (#13731)
This commit is contained in:
1 parent
81b72d9b7d
commit
da5682a600
46 files changed
+979
-681
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
Reference in new issue
Block a user