fix(remoteingressedge): reset nftables state on startup and restart before reapplying hub firewall config
This commit is contained in:
@@ -57,6 +57,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
private restartAttempts = 0;
|
||||
private statusInterval: ReturnType<typeof setInterval> | undefined;
|
||||
private nft: InstanceType<typeof plugins.smartnftables.SmartNftables> | null = null;
|
||||
private pendingFirewallConfig: IFirewallConfig | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -114,7 +115,9 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
});
|
||||
this.bridge.on('management:firewallConfigUpdated', (data: { firewallConfig: IFirewallConfig }) => {
|
||||
console.log(`[RemoteIngressEdge] Firewall config updated from hub`);
|
||||
this.applyFirewallConfig(data.firewallConfig);
|
||||
void this.applyFirewallConfig(data.firewallConfig).catch((err) => {
|
||||
console.error(`[RemoteIngressEdge] Failed to apply firewall config: ${err}`);
|
||||
});
|
||||
this.emit('firewallConfigUpdated', data);
|
||||
});
|
||||
}
|
||||
@@ -122,14 +125,22 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
/**
|
||||
* Initialize the nftables manager. Fails gracefully if not running as root.
|
||||
*/
|
||||
private async initNft(): Promise<void> {
|
||||
private async initNft(options: { reset?: boolean } = {}): Promise<void> {
|
||||
try {
|
||||
this.nft = new plugins.smartnftables.SmartNftables({
|
||||
tableName: 'remoteingress',
|
||||
dryRun: false,
|
||||
});
|
||||
if (options.reset) {
|
||||
await (this.nft as any).cleanup({ force: true });
|
||||
}
|
||||
await this.nft.initialize();
|
||||
console.log('[RemoteIngressEdge] SmartNftables initialized');
|
||||
if (this.pendingFirewallConfig) {
|
||||
const pending = this.pendingFirewallConfig;
|
||||
this.pendingFirewallConfig = null;
|
||||
await this.applyFirewallConfig(pending);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[RemoteIngressEdge] Failed to initialize nftables (not root?): ${err}`);
|
||||
this.nft = null;
|
||||
@@ -142,19 +153,22 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
*/
|
||||
private async applyFirewallConfig(config: IFirewallConfig): Promise<void> {
|
||||
if (!this.nft) {
|
||||
this.pendingFirewallConfig = config;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Full cleanup and reinitialize to replace all rules atomically
|
||||
await this.nft.cleanup();
|
||||
await (this.nft as any).cleanup({ force: true });
|
||||
await this.nft.initialize();
|
||||
|
||||
// Apply blocked IPs
|
||||
if (config.blockedIps && config.blockedIps.length > 0) {
|
||||
for (const ip of config.blockedIps) {
|
||||
await this.nft.firewall.blockIP(ip);
|
||||
}
|
||||
await (this.nft.firewall as any).blockIPSet('hub-blocklist', {
|
||||
setName: 'blocked_ipv4',
|
||||
ips: config.blockedIps,
|
||||
comment: 'RemoteIngress hub blocklist',
|
||||
});
|
||||
console.log(`[RemoteIngressEdge] Blocked ${config.blockedIps.length} IPs`);
|
||||
}
|
||||
|
||||
@@ -213,6 +227,10 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
this.savedConfig = edgeConfig;
|
||||
this.stopping = false;
|
||||
|
||||
// Clear any stale nftables state left by a prior process before the edge
|
||||
// can accept hub config or bind public listener ports.
|
||||
await this.initNft({ reset: true });
|
||||
|
||||
const spawned = await this.bridge.spawn();
|
||||
if (!spawned) {
|
||||
throw new Error('Failed to spawn remoteingress-bin');
|
||||
@@ -242,9 +260,6 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
this.restartAttempts = 0;
|
||||
this.restartBackoffMs = 1000;
|
||||
|
||||
// Initialize nftables (graceful degradation if not root)
|
||||
await this.initNft();
|
||||
|
||||
// Start periodic status logging
|
||||
this.statusInterval = setInterval(async () => {
|
||||
try {
|
||||
@@ -272,7 +287,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
// Clean up nftables rules before stopping
|
||||
if (this.nft) {
|
||||
try {
|
||||
await this.nft.cleanup();
|
||||
await (this.nft as any).cleanup({ force: true });
|
||||
} catch (err) {
|
||||
console.warn(`[RemoteIngressEdge] nftables cleanup error: ${err}`);
|
||||
}
|
||||
@@ -289,6 +304,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
this.started = false;
|
||||
}
|
||||
this.savedConfig = null;
|
||||
this.pendingFirewallConfig = null;
|
||||
// Remove all listeners to prevent memory buildup
|
||||
this.bridge.removeAllListeners();
|
||||
this.removeAllListeners();
|
||||
@@ -344,6 +360,10 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
this.restartAttempts++;
|
||||
|
||||
try {
|
||||
// Drop stale kernel rules before reconnecting. The hub will send the
|
||||
// current full firewall snapshot during handshake/config refresh.
|
||||
await this.initNft({ reset: true });
|
||||
|
||||
const spawned = await this.bridge.spawn();
|
||||
if (!spawned) {
|
||||
console.error('[RemoteIngressEdge] Failed to respawn binary');
|
||||
@@ -366,9 +386,6 @@ export class RemoteIngressEdge extends EventEmitter {
|
||||
this.restartAttempts = 0;
|
||||
this.restartBackoffMs = 1000;
|
||||
|
||||
// Re-initialize nftables (hub will re-push config via handshake)
|
||||
await this.initNft();
|
||||
|
||||
// Restart periodic status logging
|
||||
this.statusInterval = setInterval(async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user