Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions agent/app/api/v2/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ func (b *BaseApi) OperateFirewallBackend(c *gin.Context) {
return
}
if err := firewallSettingService.Operate(c.Request.Context(), request); err != nil {
if errors.Is(err, service.ErrFirewallBackendCleanupRequired) {
helper.ErrorWithBusinessCode(
c,
http.StatusConflict,
"FW_BACKEND_CLEANUP_REQUIRED",
"ErrInvalidParams",
err,
)
return
}
helper.InternalServer(c, err)
return
}
Expand Down Expand Up @@ -583,6 +593,14 @@ func (b *BaseApi) UpsertDockerPortGuardPolicies(c *gin.Context) {
}

func handleDockerPortGuardError(c *gin.Context, err error) {
if errors.Is(err, service.ErrDockerIptablesChainUnavailable) {
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_IPTABLES_CHAIN_UNAVAILABLE", "ErrDockerIptablesChainUnavailable", err)
return
}
if errors.Is(err, service.ErrDockerNftablesChainUnavailable) {
helper.ErrorWithBusinessCode(c, http.StatusServiceUnavailable, "FW_DOCKER_NFTABLES_CHAIN_UNAVAILABLE", "ErrDockerNftablesChainUnavailable", err)
return
}
if errors.Is(err, service.ErrDockerGuardInvalid) {
helper.ErrorWithBusinessCode(c, http.StatusBadRequest, "FW_DOCKER_GUARD_INVALID", "ErrInvalidParams", err)
return
Expand Down
4 changes: 4 additions & 0 deletions agent/app/model/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ func (rule FirewallRule) RulesForProvider(provider filter.Provider) ([]filter.Fi
Interface: rule.Interface, ConnectionStates: connectionStates,
Action: filter.Action(rule.Action), Description: rule.Description,
}
if provider != filter.ProviderUFW && strings.EqualFold(strings.TrimSpace(base.Protocol), "all") &&
strings.TrimSpace(base.SourcePort) == "" && strings.TrimSpace(base.DestinationPort) != "" {
base.Protocol = "tcp/udp"
}
if provider == filter.ProviderFirewalld {
base.Priority = rule.Priority
}
Expand Down
66 changes: 50 additions & 16 deletions agent/app/service/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e
if request.Operate != string(firewall.BaseOperationInit) && request.Operate != string(firewall.BaseOperationBind) {
return nil
}
ctx := context.Background()
if err := s.restoreStoredFirewallRules(ctx, filter.Provider(provider)); err != nil {
return err
}
configured, err := loadConfiguredFirewallPortWhiteList()
if err != nil {
return err
Expand All @@ -178,7 +182,7 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e
return err
}
ports := excludeFirewallPorts(configured, required)
return s.SyncSystemPorts(context.Background(), nil, systemPorts(ports))
return s.SyncSystemPorts(ctx, nil, systemPorts(ports))
}

func (s *FirewallService) Reset(ctx context.Context, request dto.FirewallRuleReset) (dto.FirewallRuleResetResponse, error) {
Expand Down Expand Up @@ -963,7 +967,7 @@ func (s *FirewallService) Delete(
continue
}
for _, item := range group.items {
if err := s.deleteRule(ctx, item.stored.UUID); err != nil {
if err := s.deleteRule(ctx, item.stored.UUID, false); err != nil {
result.Failed++
result.Errors = append(result.Errors, dto.FirewallRuleDeleteFailure{
Index: item.index, UUID: item.stored.UUID, Error: err.Error(),
Expand Down Expand Up @@ -993,6 +997,9 @@ func (s *FirewallService) prepareDelete(
}
return preparedFirewallRuleDelete{}, err
}
if isProtectedSystemFirewallRule(stored) {
return preparedFirewallRuleDelete{}, filter.ErrProtectedRule
}
if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted {
return preparedFirewallRuleDelete{}, fmt.Errorf("%w: only created or adopted rules can be deleted", filter.ErrInvalidRule)
}
Expand Down Expand Up @@ -1196,7 +1203,7 @@ func (s *FirewallService) createRule(
return nil
}

func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string) error {
func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string, allowProtected bool) error {
if ruleUUID == "" {
return fmt.Errorf("%w: rule UUID is required", repo.ErrFirewallPersistenceInvalid)
}
Expand Down Expand Up @@ -1230,6 +1237,9 @@ func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string) error
return cause
}
for _, desired := range desiredRules {
if allowProtected {
desired.Protected = false
}
runtime, runtimeErr := s.resolveRuntime(ctx, desired.Rule.Scope.Provider)
if runtimeErr != nil {
return rollback(runtimeErr)
Expand All @@ -1238,6 +1248,11 @@ func (s *FirewallService) deleteRule(ctx context.Context, ruleUUID string) error
if observeErr != nil {
return rollback(observeErr)
}
if allowProtected {
for index := range snapshot.Rules {
snapshot.Rules[index].Protected = false
}
}
observed, managedErr := filter.ManagedObserved(snapshot, desired)
if managedErr != nil {
if errors.Is(managedErr, filter.ErrRuleStale) {
Expand Down Expand Up @@ -1476,6 +1491,10 @@ func (s *FirewallService) loadManagedMutation(
if err != nil {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil, err
}
if isProtectedSystemFirewallRule(stored) {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil,
filter.ErrProtectedRule
}
if stored.Origin != constant.FirewallRuleOriginCreated && stored.Origin != constant.FirewallRuleOriginAdopted {
return model.FirewallRule{}, filter.DesiredRule{}, filter.Snapshot{}, filter.ObservedRule{}, nil,
fmt.Errorf("%w: only created or adopted rules can be changed", filter.ErrInvalidRule)
Expand Down Expand Up @@ -1728,20 +1747,19 @@ func (s *FirewallService) deleteSystemPort(ctx context.Context, port dto.Firewal
continue
}
for _, rule := range stored {
result, err := s.Delete(ctx, dto.FirewallRuleDelete{UUIDs: []string{rule.UUID}})
if err != nil {
if err := s.deleteProtectedSystemPortRule(ctx, rule.UUID); err != nil {
return err
}
if result.Failed > 0 {
if len(result.Errors) > 0 {
return errors.New(result.Errors[0].Error)
}
return errors.New("delete firewall rule failed")
}
}
}
}

func (s *FirewallService) deleteProtectedSystemPortRule(ctx context.Context, ruleUUID string) error {
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
return s.deleteRule(ctx, ruleUUID, true)
}

func (s *FirewallService) systemPortRecords(ctx context.Context, port dto.FirewallSystemPort) ([]model.FirewallRule, error) {
records := make([]model.FirewallRule, 0)
sourceIDs := []string{constant.FirewallSystemAcceptedPortSourcePrefix + systemPortKey(port)}
Expand All @@ -1767,6 +1785,14 @@ func (s *FirewallService) systemPortRecords(ctx context.Context, port dto.Firewa
return records, nil
}

func isProtectedSystemFirewallRule(rule model.FirewallRule) bool {
ownerPrefix := model.FirewallRuleOwner(
constant.FirewallRuleSourceSecurity,
constant.FirewallSystemAcceptedPortSourcePrefix,
)
return strings.HasPrefix(rule.Owner, ownerPrefix)
}

func (s *FirewallService) adoptExternalSystemPort(ctx context.Context, port dto.FirewallSystemPort) (bool, error) {
provider, err := s.selectedProvider(ctx)
if err != nil {
Expand Down Expand Up @@ -2069,6 +2095,7 @@ func (s *FirewallService) desiredFirewallRulesForScope(
}
for _, rule := range compiled {
if rule.Rule.Scope.Key() == scope.Key() {
rule.Protected = isProtectedSystemFirewallRule(record)
desired = append(desired, rule)
}
}
Expand Down Expand Up @@ -2458,15 +2485,22 @@ func supportsManagedFilterChains(provider string) bool {
}

func (s *FirewallService) addPortsBeforeStart(client lifecycle.Client) error {
if client.Name() == constant.FirewallProviderIptables || client.Name() == constant.FirewallProviderNftables {
isInit, _, err := loadDirectFirewallInitStatus(client.Name())
ctx := context.Background()
provider := filter.Provider(client.Name())
if provider == filter.ProviderIptables || provider == filter.ProviderNftables {
isInit, _, err := loadDirectFirewallInitStatus(string(provider))
if err != nil {
return err
}
if !isInit {
return nil
}
if client.Name() == constant.FirewallProviderIptables {
}
if err := s.restoreStoredFirewallRules(ctx, provider); err != nil {
return err
}
if provider == filter.ProviderIptables || provider == filter.ProviderNftables {
if provider == filter.ProviderIptables {
if err := newIptablesHelperManager().SyncRequiredPorts(true); err != nil {
return err
}
Expand All @@ -2481,13 +2515,13 @@ func (s *FirewallService) addPortsBeforeStart(client lifecycle.Client) error {
if err != nil {
return err
}
return s.SyncSystemPorts(context.Background(), nil, systemPorts(excludeFirewallPorts(configured, required)))
return s.SyncSystemPorts(ctx, nil, systemPorts(excludeFirewallPorts(configured, required)))
}
portWhitelist, err := loadFirewallPortWhiteList()
if err != nil {
return err
}
return s.SyncSystemPorts(context.Background(), nil, systemPorts(portWhitelist))
return s.SyncSystemPorts(ctx, nil, systemPorts(portWhitelist))
}

func syncManagedAcceptedPorts(previous, current []firewall.PortWhitelist) error {
Expand Down
12 changes: 7 additions & 5 deletions agent/app/service/firewall_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ type DockerPortGuardService struct {
}

var (
dockerPortGuardServiceMu sync.Mutex
dockerPortGuardSyncMu sync.RWMutex
dockerPortGuardSyncErr error
ErrDockerGuardInvalid = docker_guard.ErrInvalidPolicy
ErrDockerUnavailable = docker.ErrUnavailable
dockerPortGuardServiceMu sync.Mutex
dockerPortGuardSyncMu sync.RWMutex
dockerPortGuardSyncErr error
ErrDockerGuardInvalid = docker_guard.ErrInvalidPolicy
ErrDockerUnavailable = docker.ErrUnavailable
ErrDockerIptablesChainUnavailable = docker_guard.ErrDockerIptablesChainUnavailable
ErrDockerNftablesChainUnavailable = docker_guard.ErrDockerNftablesChainUnavailable
)

type IDockerPortGuardService interface {
Expand Down
17 changes: 14 additions & 3 deletions agent/app/service/firewall_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ type IFirewallSettingService interface {

type FirewallSettingService struct{}

var ErrFirewallBackendCleanupRequired = errors.New("firewall backend cleanup required")

func firewallBackendCleanupRequired(current, target string) error {
return fmt.Errorf(
"%w: current backend %s still contains 1Panel runtime rules; clean it up before switching to %s",
ErrFirewallBackendCleanupRequired,
current,
target,
)
}

func NewIFirewallSettingService() IFirewallSettingService {
return &FirewallSettingService{}
}
Expand Down Expand Up @@ -213,7 +224,7 @@ func (s *FirewallSettingService) operateDocker(ctx context.Context, request dto.
return err
}
if current != request.Backend && initialized {
return fmt.Errorf("clean up the current Docker firewall backend %s before switching to %s", current, request.Backend)
return firewallBackendCleanupRequired(current, request.Backend)
}
}
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, request.Backend); err != nil {
Expand Down Expand Up @@ -267,7 +278,7 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
return err
}
if initialized {
return fmt.Errorf("clean up the current system firewall backend %s before switching to %s", previous, request.Backend)
return firewallBackendCleanupRequired(previous, request.Backend)
}
}
if err := settingRepo.UpdateOrCreate(constant.FirewallSystemBackendKey, request.Backend); err != nil {
Expand Down Expand Up @@ -374,7 +385,7 @@ func (s *FirewallSettingService) operateForwarding(request dto.FirewallBackendOp
return err
}
if current != request.Backend && initialized {
return fmt.Errorf("clean up the current forwarding backend %s before switching to %s", current, request.Backend)
return firewallBackendCleanupRequired(current, request.Backend)
}
}
if err := settingRepo.UpdateOrCreate(constant.FirewallForwardingBackendKey, request.Backend); err != nil {
Expand Down
42 changes: 37 additions & 5 deletions agent/app/service/firewall_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,27 @@ func (s *FirewallService) syncRules(
return s.executeFirewallSystemSyncPlan(ctx, clientIP, plan), nil
}

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,
})
if err != nil {
return fmt.Errorf("restore database firewall rules: %w", err)
}
if result.Failed == 0 {
return nil
}
messages := firewallRuleSyncFailureMessages(result.Errors)
if len(messages) == 0 {
return fmt.Errorf("restore database firewall rules: %d rules failed", result.Failed)
}
return fmt.Errorf("restore database firewall rules: %s", strings.Join(messages, "; "))
}

func (s *FirewallService) loadFirewallRuleSyncPlan(
ctx context.Context,
clientIP string,
Expand Down Expand Up @@ -164,10 +185,17 @@ func (s *FirewallService) loadFirewallRuleSyncPlan(
seenSnapshots[scopeKey] = struct{}{}
scopeEntries := append([]*firewallRuleSyncEntry(nil), entriesByScope[scopeKey]...)
desired := make([]filter.DesiredRule, 0, len(scopeEntries))
byMarker := make(map[string]*firewallRuleSyncEntry, len(scopeEntries))
byRuleUUID := make(map[string]*firewallRuleSyncEntry, len(scopeEntries))
for _, entry := range scopeEntries {
desired = append(desired, entry.desired)
byMarker[entry.desired.Marker] = entry
ruleUUID := strings.TrimSpace(entry.desired.Rule.UUID)
if ruleUUID == "" {
return firewallSystemSyncPlan{}, fmt.Errorf("%w: compiled database rule has no runtime UUID", filter.ErrInvalidRule)
}
if _, exists := byRuleUUID[ruleUUID]; exists {
return firewallSystemSyncPlan{}, fmt.Errorf("%w: duplicate compiled runtime rule UUID %q", filter.ErrInvalidRule, ruleUUID)
}
byRuleUUID[ruleUUID] = entry
}
inventory, mergeErr := filter.MergeInventory(filter.InventoryMergeInput{
Observed: snapshot.Rules, Desired: desired,
Expand All @@ -180,7 +208,7 @@ func (s *FirewallService) loadFirewallRuleSyncPlan(
if inventoryItem.Desired == nil {
continue
}
entry, exists := byMarker[inventoryItem.Desired.Marker]
entry, exists := byRuleUUID[strings.TrimSpace(inventoryItem.Desired.Rule.UUID)]
if !exists {
continue
}
Expand Down Expand Up @@ -1170,8 +1198,12 @@ func firewallRuleSyncChange(
}
change.Operation = filter.ChangeCreate
if position := firewallRuleSyncInsertionPosition(snapshot, entries, entry); position != nil {
after.OrderIndex = position
change.After = &after
if entry.rule.Scope.Provider == filter.ProviderUFW && *position > maxObservedFirewallPosition(snapshot) {
change.Append = true
} else {
after.OrderIndex = position
change.After = &after
}
} else {
change.Append = entry.rule.Scope.Provider == filter.ProviderUFW
}
Expand Down
2 changes: 2 additions & 0 deletions agent/i18n/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ ErrCustomApps: 'Installed apps detected; uninstall first'
ErrCustomRuntimes: 'Installed runtimes detected; delete first'
ErrAppVersionDeprecated: "The {{ .name }} application is not compatible with the current 1Panel version, skipped"
ErrDockerFailed: 'Docker is abnormal; check service status'
ErrDockerIptablesChainUnavailable: 'The Docker DOCKER-USER chain was not found. Restart Docker and try again'
ErrDockerNftablesChainUnavailable: 'The Docker nftables IPv4 firewall chain was not found. Verify that the current Docker version supports the nftables firewall backend, restart Docker, and try again'
ErrDockerComposeCmdNotFound: 'Docker Compose not found on host'
UseExistImage: 'Image exists; using existing image'
UpgradePrepare: 'Prepare application upgrade'
Expand Down
2 changes: 2 additions & 0 deletions agent/i18n/lang/es-ES.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ ErrCustomApps: 'Hay una aplicación instalada, desinstálela primero'
ErrCustomRuntimes: 'Hay un entorno de ejecución instalado, elimínelo primero'
ErrAppVersionDeprecated: 'La aplicación {{ .name }} no es compatible con la versión actual de 1Panel, omitida'
ErrDockerFailed: 'El estado de Docker es anómalo, revise el servicio'
ErrDockerIptablesChainUnavailable: 'No se encontró la cadena DOCKER-USER de Docker. Reinicie Docker y vuelva a intentarlo'
ErrDockerNftablesChainUnavailable: 'No se encontró la cadena de firewall IPv4 de nftables de Docker. Compruebe que la versión actual de Docker admita el backend de firewall nftables, reinicie Docker y vuelva a intentarlo'
ErrDockerComposeCmdNotFound: 'El comando Docker Compose no existe instálelo primero en el host'
UseExistImage: 'La imagen ya existe, usando imagen existente'
UpgradePrepare: 'Preparar actualización de la aplicación'
Expand Down
2 changes: 2 additions & 0 deletions agent/i18n/lang/fa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ ErrCustomApps: 'برنامه‌های نصب شده شناسایی شدند؛ ا
ErrCustomRuntimes: 'محیط‌های اجرایی نصب شده شناسایی شدند؛ ابتدا حذف کنید'
ErrAppVersionDeprecated: "برنامه {{ .name }} با نسخه فعلی 1Panel سازگار نیست، رد شد"
ErrDockerFailed: 'Docker غیرعادی است؛ وضعیت سرویس را بررسی کنید'
ErrDockerIptablesChainUnavailable: 'زنجیره DOCKER-USER داکر یافت نشد. داکر را راه‌اندازی مجدد کنید و دوباره تلاش کنید'
ErrDockerNftablesChainUnavailable: 'زنجیره فایروال IPv4 مربوط به nftables داکر یافت نشد. بررسی کنید نسخه فعلی داکر از بک‌اند فایروال nftables پشتیبانی کند، سپس داکر را راه‌اندازی مجدد کرده و دوباره تلاش کنید'
ErrDockerComposeCmdNotFound: 'Docker Compose روی میزبان یافت نشد'
UseExistImage: 'تصویر وجود دارد؛ استفاده از تصویر موجود'
UpgradePrepare: 'آماده‌سازی ارتقاء برنامه'
Expand Down
2 changes: 2 additions & 0 deletions agent/i18n/lang/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ ErrCustomApps: 'インストールされているアプリケーションがあ
ErrCustomRuntimes: 'ランタイム環境がインストールされています。まずそれを削除してください'
ErrAppVersionDeprecated: '{{ .name }} アプリケーションは現在の 1Panel バージョンと互換性がありません、スキップしました'
ErrDockerFailed: 'Docker の状態が異常です。サービス状態を確認してください'
ErrDockerIptablesChainUnavailable: 'Docker の DOCKER-USER チェーンが見つかりません。Docker を再起動してから再試行してください'
ErrDockerNftablesChainUnavailable: 'Docker の nftables IPv4 ファイアウォールチェーンが見つかりません。現在の Docker バージョンが nftables ファイアウォールバックエンドをサポートしていることを確認し、Docker を再起動してから再試行してください'
ErrDockerComposeCmdNotFound: 'Docker Compose コマンドは存在しません。ホストマシンにこのコマンドを先にインストールしてください'
UseExistImage: 'イメージは既に存在します。既存のイメージを使用します'
UpgradePrepare: 'アプリケーションのアップグレードを準備'
Expand Down
2 changes: 2 additions & 0 deletions agent/i18n/lang/ko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ ErrCustomApps: '설치된 애플리케이션이 있습니다. 먼저 제거해
ErrCustomRuntimes: '설치된 런타임 환경이 있습니다. 먼저 삭제해 주세요'
ErrAppVersionDeprecated: '{{ .name }} 응용 프로그램은 현재 1Panel 버전과 호환되지 않아 건너뛰었습니다'
ErrDockerFailed: 'Docker의 상태가 비정상입니다. 서비스 상태를 확인하세요'
ErrDockerIptablesChainUnavailable: 'Docker DOCKER-USER 체인을 찾을 수 없습니다. Docker를 다시 시작한 후 재시도하세요'
ErrDockerNftablesChainUnavailable: 'Docker nftables IPv4 방화벽 체인을 찾을 수 없습니다. 현재 Docker 버전이 nftables 방화벽 백엔드를 지원하는지 확인하고 Docker를 다시 시작한 후 재시도하세요'
ErrDockerComposeCmdNotFound: 'Docker Compose 명령이 없습니다. 호스트 머신에 먼저 이 명령을 설치하세요'
UseExistImage: '이미지가 이미 존재하여 기존 이미지를 사용합니다'
UpgradePrepare: '애플리케이션 업그레이드 준비'
Expand Down
Loading
Loading