mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
fix: restore firewall-dependent rules after reset (#13674)
This commit is contained in:
@@ -103,7 +103,8 @@ type FirewallRuleResetResponse struct {
|
||||
}
|
||||
|
||||
type FirewallRuleReset struct {
|
||||
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||
Provider filter.Provider `json:"provider,omitempty" validate:"omitempty,oneof=firewalld ufw iptables nftables"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type FirewallRuleCheckResult struct {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"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"
|
||||
filterruntime "github.com/1Panel-dev/1Panel/agent/utils/firewall/filter/runtime"
|
||||
@@ -37,7 +38,10 @@ type FirewallService struct {
|
||||
iptablesHelper *iptables_helper.Manager
|
||||
cleanupBackend func(string) error
|
||||
cleanupInactiveBackend func(string) error
|
||||
resetBackend func(string) error
|
||||
resetBackend func(string, bool) error
|
||||
dockerActive func() (bool, error)
|
||||
restoreForwarding func(context.Context) error
|
||||
restoreDockerGuard func(context.Context) error
|
||||
baseClient func() (lifecycle.Client, error)
|
||||
}
|
||||
|
||||
@@ -82,7 +86,14 @@ func newFirewallService() *FirewallService {
|
||||
cleanupBackend: cleanupSystemBackend,
|
||||
cleanupInactiveBackend: cleanupInactiveSystemBackend,
|
||||
resetBackend: resetServiceFirewallBackend,
|
||||
baseClient: selectedSystemFirewallClient,
|
||||
dockerActive: func() (bool, error) {
|
||||
return controller.CheckActive("docker")
|
||||
},
|
||||
restoreForwarding: func(ctx context.Context) error {
|
||||
return newForwardingService().Restore(ctx)
|
||||
},
|
||||
restoreDockerGuard: ReconcileDockerPortGuard,
|
||||
baseClient: selectedSystemFirewallClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +297,38 @@ func (s *FirewallService) Reset(ctx context.Context, request dto.FirewallRuleRes
|
||||
if reset == nil {
|
||||
reset = resetServiceFirewallBackend
|
||||
}
|
||||
if err := reset(string(provider)); err != nil {
|
||||
return dto.FirewallRuleResetResponse{}, err
|
||||
restartDocker := false
|
||||
if provider == filter.ProviderFirewalld && request.WithDockerRestart {
|
||||
dockerActive := s.dockerActive
|
||||
if dockerActive == nil {
|
||||
dockerActive = func() (bool, error) { return controller.CheckActive("docker") }
|
||||
}
|
||||
active, err := dockerActive()
|
||||
if err != nil {
|
||||
return dto.FirewallRuleResetResponse{}, fmt.Errorf("check Docker status before resetting firewalld: %w", err)
|
||||
}
|
||||
restartDocker = active
|
||||
}
|
||||
resetErr := reset(string(provider), restartDocker)
|
||||
if resetErr != nil {
|
||||
var dockerRestartErr *lifecycle.DockerRestartError
|
||||
if provider != filter.ProviderFirewalld || !errors.As(resetErr, &dockerRestartErr) {
|
||||
return dto.FirewallRuleResetResponse{}, resetErr
|
||||
}
|
||||
}
|
||||
if provider == filter.ProviderFirewalld {
|
||||
restoreForwarding := s.restoreForwarding
|
||||
if restoreForwarding == nil {
|
||||
restoreForwarding = func(ctx context.Context) error { return newForwardingService().Restore(ctx) }
|
||||
}
|
||||
restoreDockerGuard := s.restoreDockerGuard
|
||||
if restoreDockerGuard == nil {
|
||||
restoreDockerGuard = ReconcileDockerPortGuard
|
||||
}
|
||||
restoreErr := restoreFirewalldDependents(ctx, restartDocker, restoreForwarding, restoreDockerGuard)
|
||||
if err := errors.Join(resetErr, restoreErr); err != nil {
|
||||
return dto.FirewallRuleResetResponse{}, err
|
||||
}
|
||||
}
|
||||
return dto.FirewallRuleResetResponse{Removed: len(stored), Disabled: true}, nil
|
||||
}
|
||||
@@ -296,23 +337,56 @@ func isDirectFirewallProvider(provider filter.Provider) bool {
|
||||
return provider == filter.ProviderIptables || provider == filter.ProviderNftables
|
||||
}
|
||||
|
||||
func resetServiceFirewallBackend(provider string) error {
|
||||
func resetServiceFirewallBackend(provider string, withDockerRestart bool) error {
|
||||
client, err := lifecycle.NewClientFor(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resetServiceFirewallClient(client, withDockerRestart, func(
|
||||
client lifecycle.Client,
|
||||
restartDocker bool,
|
||||
prepareStop func() error,
|
||||
) error {
|
||||
return lifecycle.NewOperator(client).StopWithPrepare(restartDocker, prepareStop)
|
||||
})
|
||||
}
|
||||
|
||||
func resetServiceFirewallClient(
|
||||
client lifecycle.Client,
|
||||
withDockerRestart bool,
|
||||
stop func(lifecycle.Client, bool, func() error) error,
|
||||
) error {
|
||||
resetter, ok := client.(lifecycle.Resetter)
|
||||
if !ok {
|
||||
return fmt.Errorf("firewall provider %s does not support reset", provider)
|
||||
return fmt.Errorf("firewall provider %s does not support reset", client.Name())
|
||||
}
|
||||
if provider == constant.FirewallProviderFirewalld {
|
||||
if err := lifecycle.NewOperator(client).Operate(lifecycle.OperationStop, false, nil); err != nil {
|
||||
if resetBeforeStop, ok := client.(lifecycle.PreStopResetter); ok {
|
||||
if err := stop(client, withDockerRestart, resetBeforeStop.ResetBeforeStop); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return resetter.Reset()
|
||||
}
|
||||
|
||||
func restoreFirewalldDependents(
|
||||
ctx context.Context,
|
||||
restartDocker bool,
|
||||
restoreForwarding func(context.Context) error,
|
||||
restoreDockerGuard func(context.Context) error,
|
||||
) error {
|
||||
var errs []error
|
||||
if err := restoreForwarding(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("restore port forwarding after resetting firewalld: %w", err))
|
||||
}
|
||||
if restartDocker {
|
||||
if err := restoreDockerGuard(ctx); err != nil {
|
||||
errs = append(errs, fmt.Errorf("restore Docker port guard after resetting firewalld: %w", err))
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (s *FirewallService) deleteFirewallRuleRecords(
|
||||
ctx context.Context,
|
||||
stored []model.FirewallRule,
|
||||
|
||||
@@ -102,6 +102,13 @@ type Resetter interface {
|
||||
Reset() error
|
||||
}
|
||||
|
||||
// PreStopResetter prepares a service-backed firewall reset without stopping
|
||||
// the service. The lifecycle operator can then stop the service and perform
|
||||
// dependent recovery, such as rebuilding Docker firewall rules, in order.
|
||||
type PreStopResetter interface {
|
||||
ResetBeforeStop() error
|
||||
}
|
||||
|
||||
func NewClient() (Client, error) {
|
||||
runtime, err := DetectRuntime()
|
||||
if err != nil {
|
||||
|
||||
@@ -22,17 +22,25 @@ type Operator struct {
|
||||
client Client
|
||||
}
|
||||
|
||||
// DockerRestartError reports that the requested firewall operation completed,
|
||||
// but rebuilding Docker's firewall rules failed.
|
||||
type DockerRestartError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DockerRestartError) Error() string {
|
||||
return fmt.Sprintf("failed to restart Docker: %v", e.Err)
|
||||
}
|
||||
|
||||
func (e *DockerRestartError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func NewOperator(client Client) *Operator {
|
||||
return &Operator{client: client}
|
||||
}
|
||||
|
||||
func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareStart func(Client) error) error {
|
||||
if o.client.Name() == ProviderFirewalld && operation == OperationStop {
|
||||
if err := rememberFail2BanBeforeFirewallStop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch operation {
|
||||
case OperationStart:
|
||||
if err := o.client.Start(); err != nil {
|
||||
@@ -44,9 +52,7 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
|
||||
}
|
||||
}
|
||||
case OperationStop:
|
||||
if err := o.client.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.StopWithPrepare(withDockerRestart, nil)
|
||||
case OperationRestart:
|
||||
if err := o.client.Restart(); err != nil {
|
||||
return err
|
||||
@@ -62,7 +68,7 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
|
||||
|
||||
if withDockerRestart {
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %v", err)
|
||||
return &DockerRestartError{Err: err}
|
||||
}
|
||||
}
|
||||
if o.client.Name() == ProviderFirewalld && operation == OperationStart {
|
||||
@@ -71,6 +77,30 @@ func (o *Operator) Operate(operation Operation, withDockerRestart bool, prepareS
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopWithPrepare records dependent service state, runs preparation, stops the
|
||||
// firewall, and optionally restarts Docker in that order.
|
||||
func (o *Operator) StopWithPrepare(withDockerRestart bool, prepareStop func() error) error {
|
||||
if o.client.Name() == ProviderFirewalld {
|
||||
if err := rememberFail2BanBeforeFirewallStop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if prepareStop != nil {
|
||||
if err := prepareStop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := o.client.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if withDockerRestart {
|
||||
if err := controller.HandleRestart("docker"); err != nil {
|
||||
return &DockerRestartError{Err: err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rememberFail2BanBeforeFirewallStop() error {
|
||||
exists, err := controller.CheckExist("fail2ban.service")
|
||||
if err != nil {
|
||||
|
||||
@@ -76,16 +76,16 @@ func (f *Firewalld) Stop() error {
|
||||
}
|
||||
|
||||
func (f *Firewalld) Reset() error {
|
||||
running, err := f.Status()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reset firewalld to defaults failed: %w", err)
|
||||
if err := f.ResetBeforeStop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if running {
|
||||
if err := f.Stop(); err != nil {
|
||||
return fmt.Errorf("reset firewalld to defaults failed: %w", err)
|
||||
}
|
||||
if err := f.Stop(); err != nil {
|
||||
return fmt.Errorf("stop firewalld after reset failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Firewalld) ResetBeforeStop() error {
|
||||
backupDir := fmt.Sprintf("%s.1panel-backup-%d", firewalldConfigDir, time.Now().UnixNano())
|
||||
rollback, err := replaceFirewalldConfig(
|
||||
firewalldConfigDir,
|
||||
|
||||
@@ -201,6 +201,7 @@ export namespace Firewall {
|
||||
|
||||
export interface ResetRequest {
|
||||
provider?: Provider;
|
||||
withDockerRestart?: boolean;
|
||||
}
|
||||
|
||||
export type CheckDecision = 'ready' | 'confirmation_required' | 'blocked' | 'no_change';
|
||||
|
||||
@@ -13,7 +13,7 @@ export const searchForwardRule = (request: Firewall.ForwardRuleSearch) =>
|
||||
http.post<ResPage<Firewall.RuleInfo>>('/hosts/firewall/forward/search', request, TimeoutEnum.T_40S);
|
||||
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean) =>
|
||||
http.post('/hosts/firewall/operate', { operation, withDockerRestart }, TimeoutEnum.T_60S);
|
||||
http.post('/hosts/firewall/operate', { operation, withDockerRestart }, TimeoutEnum.T_10M);
|
||||
|
||||
export const operateForwardRule = (request: { rules: Firewall.RuleForward[]; forceDelete?: boolean }) =>
|
||||
http.post('/hosts/firewall/forward/operate', request, TimeoutEnum.T_40S);
|
||||
|
||||
@@ -368,7 +368,12 @@
|
||||
<RuleImport ref="ruleImportRef" @search="search" />
|
||||
<RuleSync ref="ruleSyncRef" @search="search" />
|
||||
<ProcessDetail ref="processDetailRef" />
|
||||
<ConfirmDialog ref="resetConfirmRef" @confirm="submitResetRules" />
|
||||
<ConfirmDialog ref="resetConfirmRef" @confirm="prepareResetRules" />
|
||||
<DockerRestart
|
||||
ref="dockerRestartRef"
|
||||
v-model:withDockerRestart="withDockerRestart"
|
||||
@submit="submitResetRules"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -398,6 +403,8 @@ import FireRouter from '@/views/host/firewall/index.vue';
|
||||
import FireStatus from '@/views/host/firewall/status/index.vue';
|
||||
import ProcessDetail from '@/views/host/process/process/detail/index.vue';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import DockerRestart from '@/components/docker-proxy/docker-restart.vue';
|
||||
import { loadDockerStatus } from '@/api/modules/container';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { Expand, Filter, Lock, WarningFilled } from '@element-plus/icons-vue';
|
||||
@@ -461,6 +468,8 @@ const ruleImportRef = ref<InstanceType<typeof RuleImport>>();
|
||||
const ruleSyncRef = ref<InstanceType<typeof RuleSync>>();
|
||||
const processDetailRef = ref<InstanceType<typeof ProcessDetail>>();
|
||||
const resetConfirmRef = ref<InstanceType<typeof ConfirmDialog>>();
|
||||
const dockerRestartRef = ref<InstanceType<typeof DockerRestart>>();
|
||||
const withDockerRestart = ref(false);
|
||||
const loading = ref(false);
|
||||
const resetting = ref(false);
|
||||
const syncOpening = ref(false);
|
||||
@@ -1111,6 +1120,7 @@ const removeRules = async (selected: RuleRow[]) => {
|
||||
const removeSelectedRules = () => removeRules(selects.value.filter((row) => isDeletableManagedRule(row)));
|
||||
|
||||
const resetRules = () => {
|
||||
withDockerRestart.value = false;
|
||||
const message = i18n.global.t(
|
||||
isDirectBackend.value ? 'firewall.resetDirectRulesHelper' : 'firewall.resetWhitelistRulesHelper',
|
||||
[provider.value],
|
||||
@@ -1122,11 +1132,25 @@ const resetRules = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const prepareResetRules = async () => {
|
||||
if (provider.value === 'firewalld') {
|
||||
const status = await loadDockerStatus();
|
||||
if (status.data.isActive) {
|
||||
dockerRestartRef.value?.acceptParams({ title: i18n.global.t('firewall.dockerRestart') });
|
||||
return;
|
||||
}
|
||||
}
|
||||
await submitResetRules();
|
||||
};
|
||||
|
||||
const submitResetRules = async () => {
|
||||
resetting.value = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
await resetFirewallRules({ provider: provider.value as Firewall.Provider });
|
||||
await resetFirewallRules({
|
||||
provider: provider.value as Firewall.Provider,
|
||||
withDockerRestart: provider.value === 'firewalld' && withDockerRestart.value,
|
||||
});
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
} finally {
|
||||
resetting.value = false;
|
||||
|
||||
@@ -333,7 +333,7 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
|
||||
const loadDocker = async () => {
|
||||
const res = await loadDockerStatus();
|
||||
dockerStatus.value = res.data.isExist;
|
||||
dockerStatus.value = res.data.isActive;
|
||||
};
|
||||
|
||||
const onInit = async () => {
|
||||
|
||||
Reference in New Issue
Block a user