@serve.zone/remoteingress 4.14.2 → 4.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.smartconfig.json CHANGED
@@ -11,26 +11,26 @@
11
11
  "githost": "code.foss.global",
12
12
  "gitscope": "serve.zone",
13
13
  "gitrepo": "remoteingress",
14
- "description": "Provides a service for creating private tunnels and reaching private clusters from the outside, facilitating secure remote access as part of the @serve.zone stack.",
14
+ "description": "Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.",
15
15
  "npmPackagename": "@serve.zone/remoteingress",
16
16
  "license": "MIT",
17
17
  "projectDomain": "serve.zone",
18
18
  "keywords": [
19
19
  "remote access",
20
- "private tunnels",
21
- "network security",
22
- "TLS encryption",
23
- "connector",
20
+ "ingress tunnel",
21
+ "network edge",
22
+ "PROXY protocol",
23
+ "multiplexed tunnel",
24
+ "TCP proxy",
25
+ "TLS tunnel",
26
+ "QUIC transport",
27
+ "UDP tunneling",
24
28
  "serve.zone stack",
25
- "private clusters access",
26
- "public access management",
27
- "TypeScript application",
28
- "node.js package",
29
- "secure communications",
30
- "TLS/SSL certificates",
31
- "development tools",
32
- "software development",
33
- "private network integration"
29
+ "TypeScript",
30
+ "Rust",
31
+ "SmartProxy",
32
+ "DcRouter",
33
+ "flow control"
34
34
  ]
35
35
  },
36
36
  "release": {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.14.2',
6
+ version: '4.15.0',
7
7
  description: 'Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLCtKQUErSjtDQUM3SyxDQUFBIn0=
@@ -15,7 +15,17 @@ export declare class RemoteIngressEdge extends EventEmitter {
15
15
  private restartBackoffMs;
16
16
  private restartAttempts;
17
17
  private statusInterval;
18
+ private nft;
18
19
  constructor();
20
+ /**
21
+ * Initialize the nftables manager. Fails gracefully if not running as root.
22
+ */
23
+ private initNft;
24
+ /**
25
+ * Apply firewall configuration received from the hub.
26
+ * Performs a full replacement: cleans up existing rules, then applies the new config.
27
+ */
28
+ private applyFirewallConfig;
19
29
  /**
20
30
  * Start the edge — spawns the Rust binary and connects to the hub.
21
31
  * Accepts either a connection token or an explicit IEdgeConfig.
@@ -11,6 +11,7 @@ export class RemoteIngressEdge extends EventEmitter {
11
11
  this.savedConfig = null;
12
12
  this.restartBackoffMs = 1000;
13
13
  this.restartAttempts = 0;
14
+ this.nft = null;
14
15
  /**
15
16
  * Handle unexpected Rust binary crash — auto-restart with backoff.
16
17
  */
@@ -53,6 +54,8 @@ export class RemoteIngressEdge extends EventEmitter {
53
54
  this.started = true;
54
55
  this.restartAttempts = 0;
55
56
  this.restartBackoffMs = 1000;
57
+ // Re-initialize nftables (hub will re-push config via handshake)
58
+ await this.initNft();
56
59
  // Restart periodic status logging
57
60
  this.statusInterval = setInterval(async () => {
58
61
  try {
@@ -119,6 +122,79 @@ export class RemoteIngressEdge extends EventEmitter {
119
122
  console.log(`[RemoteIngressEdge] Ports updated by hub: ${data.listenPorts.join(', ')}`);
120
123
  this.emit('portsUpdated', data);
121
124
  });
125
+ this.bridge.on('management:firewallConfigUpdated', (data) => {
126
+ console.log(`[RemoteIngressEdge] Firewall config updated from hub`);
127
+ this.applyFirewallConfig(data.firewallConfig);
128
+ this.emit('firewallConfigUpdated', data);
129
+ });
130
+ }
131
+ /**
132
+ * Initialize the nftables manager. Fails gracefully if not running as root.
133
+ */
134
+ async initNft() {
135
+ try {
136
+ this.nft = new plugins.smartnftables.SmartNftables({
137
+ tableName: 'remoteingress',
138
+ dryRun: false,
139
+ });
140
+ await this.nft.initialize();
141
+ console.log('[RemoteIngressEdge] SmartNftables initialized');
142
+ }
143
+ catch (err) {
144
+ console.warn(`[RemoteIngressEdge] Failed to initialize nftables (not root?): ${err}`);
145
+ this.nft = null;
146
+ }
147
+ }
148
+ /**
149
+ * Apply firewall configuration received from the hub.
150
+ * Performs a full replacement: cleans up existing rules, then applies the new config.
151
+ */
152
+ async applyFirewallConfig(config) {
153
+ if (!this.nft) {
154
+ return;
155
+ }
156
+ try {
157
+ // Full cleanup and reinitialize to replace all rules atomically
158
+ await this.nft.cleanup();
159
+ await this.nft.initialize();
160
+ // Apply blocked IPs
161
+ if (config.blockedIps && config.blockedIps.length > 0) {
162
+ for (const ip of config.blockedIps) {
163
+ await this.nft.firewall.blockIP(ip);
164
+ }
165
+ console.log(`[RemoteIngressEdge] Blocked ${config.blockedIps.length} IPs`);
166
+ }
167
+ // Apply rate limits
168
+ if (config.rateLimits && config.rateLimits.length > 0) {
169
+ for (const rl of config.rateLimits) {
170
+ await this.nft.rateLimit.addRateLimit(rl.id, {
171
+ port: rl.port,
172
+ protocol: rl.protocol,
173
+ rate: rl.rate,
174
+ burst: rl.burst,
175
+ perSourceIP: rl.perSourceIP,
176
+ });
177
+ }
178
+ console.log(`[RemoteIngressEdge] Applied ${config.rateLimits.length} rate limits`);
179
+ }
180
+ // Apply firewall rules
181
+ if (config.rules && config.rules.length > 0) {
182
+ for (const rule of config.rules) {
183
+ await this.nft.firewall.addRule(rule.id, {
184
+ direction: rule.direction,
185
+ action: rule.action,
186
+ sourceIP: rule.sourceIP,
187
+ destPort: rule.destPort,
188
+ protocol: rule.protocol,
189
+ comment: rule.comment,
190
+ });
191
+ }
192
+ console.log(`[RemoteIngressEdge] Applied ${config.rules.length} firewall rules`);
193
+ }
194
+ }
195
+ catch (err) {
196
+ console.error(`[RemoteIngressEdge] Failed to apply firewall config: ${err}`);
197
+ }
122
198
  }
123
199
  /**
124
200
  * Start the edge — spawns the Rust binary and connects to the hub.
@@ -158,6 +234,8 @@ export class RemoteIngressEdge extends EventEmitter {
158
234
  this.started = true;
159
235
  this.restartAttempts = 0;
160
236
  this.restartBackoffMs = 1000;
237
+ // Initialize nftables (graceful degradation if not root)
238
+ await this.initNft();
161
239
  // Start periodic status logging
162
240
  this.statusInterval = setInterval(async () => {
163
241
  try {
@@ -180,6 +258,16 @@ export class RemoteIngressEdge extends EventEmitter {
180
258
  clearInterval(this.statusInterval);
181
259
  this.statusInterval = undefined;
182
260
  }
261
+ // Clean up nftables rules before stopping
262
+ if (this.nft) {
263
+ try {
264
+ await this.nft.cleanup();
265
+ }
266
+ catch (err) {
267
+ console.warn(`[RemoteIngressEdge] nftables cleanup error: ${err}`);
268
+ }
269
+ this.nft = null;
270
+ }
183
271
  if (this.started) {
184
272
  try {
185
273
  await this.bridge.sendCommand('stopEdge', {});
@@ -206,4 +294,4 @@ export class RemoteIngressEdge extends EventEmitter {
206
294
  return this.bridge.running;
207
295
  }
208
296
  }
209
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQTRDM0QsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLENBQUM7QUFFdEMsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFTakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQVJGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixnQkFBVyxHQUF1QixJQUFJLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBMEo1Qjs7V0FFRztRQUNLLHdCQUFtQixHQUFHLEtBQUssRUFBRSxJQUFtQixFQUFFLE1BQXFCLEVBQUUsRUFBRTtZQUNqRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN4RCxPQUFPO1lBQ1QsQ0FBQztZQUVELE9BQU8sQ0FBQyxLQUFLLENBQ1gsaURBQWlELElBQUksWUFBWSxNQUFNLEtBQUs7Z0JBQzVFLFdBQVcsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FDOUQsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1lBRXJCLG1EQUFtRDtZQUNuRCxJQUFJLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFD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
297
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQTZDM0QsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLENBQUM7QUFFdEMsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFVakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQVRGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixnQkFBVyxHQUF1QixJQUFJLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBRXBCLFFBQUcsR0FBb0UsSUFBSSxDQUFDO1FBa1BwRjs7V0FFRztRQUNLLHdCQUFtQixHQUFHLEtBQUssRUFBRSxJQUFtQixFQUFFLE1BQXFCLEVBQUUsRUFBRTtZQUNqRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN4RCxPQUFPO1lBQ1QsQ0FBQztZQUVELE9BQU8sQ0FBQyxLQUFLLENBQ1gsaURBQWlELElBQUksWUFBWSxNQUFNLEtBQUs7Z0JBQzVFLFdBQVcsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FDOUQsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1lBRXJCLG1EQUFtRDtZQUNuRCxJQUFJLElB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
@@ -1,4 +1,26 @@
1
1
  import { EventEmitter } from 'events';
2
+ export interface IFirewallRateLimit {
3
+ id: string;
4
+ port: number;
5
+ protocol?: 'tcp' | 'udp';
6
+ rate: string;
7
+ burst?: number;
8
+ perSourceIP?: boolean;
9
+ }
10
+ export interface IFirewallRule {
11
+ id: string;
12
+ direction: 'input' | 'output' | 'forward';
13
+ action: 'accept' | 'drop' | 'reject';
14
+ sourceIP?: string;
15
+ destPort?: number;
16
+ protocol?: 'tcp' | 'udp';
17
+ comment?: string;
18
+ }
19
+ export interface IFirewallConfig {
20
+ blockedIps?: string[];
21
+ rateLimits?: IFirewallRateLimit[];
22
+ rules?: IFirewallRule[];
23
+ }
2
24
  export interface IHubConfig {
3
25
  tunnelPort?: number;
4
26
  targetHost?: string;
@@ -13,6 +35,7 @@ type TAllowedEdge = {
13
35
  listenPorts?: number[];
14
36
  listenPortsUdp?: number[];
15
37
  stunIntervalSecs?: number;
38
+ firewallConfig?: IFirewallConfig;
16
39
  };
17
40
  export declare class RemoteIngressHub extends EventEmitter {
18
41
  private bridge;
@@ -165,4 +165,4 @@ export class RemoteIngressHub extends EventEmitter {
165
165
  return this.bridge.running;
166
166
  }
167
167
  }
168
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFxRHRDLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0sc0JBQXNCLEdBQUcsTUFBTSxDQUFDO0FBRXRDLE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxZQUFZO0lBU2hEO1FBQ0UsS0FBSyxFQUFFLENBQUM7UUFSRixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ2hCLGFBQVEsR0FBRyxLQUFLLENBQUM7UUFDakIsZ0JBQVcsR0FBc0IsSUFBSSxDQUFDO1FBQ3RDLGVBQVUsR0FBbUIsRUFBRSxDQUFDO1FBQ2hDLHFCQUFnQixHQUFHLElBQUksQ0FBQztRQUN4QixvQkFBZSxHQUFHLENBQUMsQ0FBQztRQTJINUI7O1dBRUc7UUFDSyx3QkFBbUIsR0FBRyxLQUFLLEVBQUUsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDakYsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDeEQsT0FBTztZQUNULENBQUM7WUFFRCxPQUFPLENBQUMsS0FBSyxDQUNYLGdEQUFnRCxJQUFJLFlBQVksTUFBTSxLQUFLO2dCQUMzRSxXQUFXLElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxJQUFJLG9CQUFvQixFQUFFLENBQzlELENBQUM7WUFFRixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUVyQixJQUFJLElBQUksQ0FBQyxlQUFlLElBQUksb0JBQW9CLEVBQUUsQ0FBQztnQkFDakQsT0FBTyxDQUFDLEtBQUssQ0FB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
168
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUE4RXRDLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0sc0JBQXNCLEdBQUcsTUFBTSxDQUFDO0FBRXRDLE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxZQUFZO0lBU2hEO1FBQ0UsS0FBSyxFQUFFLENBQUM7UUFSRixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ2hCLGFBQVEsR0FBRyxLQUFLLENBQUM7UUFDakIsZ0JBQVcsR0FBc0IsSUFBSSxDQUFDO1FBQ3RDLGVBQVUsR0FBbUIsRUFBRSxDQUFDO1FBQ2hDLHFCQUFnQixHQUFHLElBQUksQ0FBQztRQUN4QixvQkFBZSxHQUFHLENBQUMsQ0FBQztRQTJINUI7O1dBRUc7UUFDSyx3QkFBbUIsR0FBRyxLQUFLLEVBQUUsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDakYsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDeEQsT0FBTztZQUNULENBQUM7WUFFRCxPQUFPLENBQUMsS0FBSyxDQUNYLGdEQUFnRCxJQUFJLFlBQVksTUFBTSxLQUFLO2dCQUMzRSxXQUFXLElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxJQUFJLG9CQUFvQixFQUFFLENBQzlELENBQUM7WUFFRixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUVyQixJQUFJLElBQUksQ0FBQyxlQUFlLElBQUksb0JBQW9CLEVBQUUsQ0FBQztnQkFDakQsT0FBTyxDQUFDLEtBQUssQ0FB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
@@ -1,4 +1,5 @@
1
1
  import * as path from 'path';
2
2
  export { path };
3
+ import * as smartnftables from '@push.rocks/smartnftables';
3
4
  import * as smartrust from '@push.rocks/smartrust';
4
- export { smartrust };
5
+ export { smartnftables, smartrust };
@@ -2,6 +2,7 @@
2
2
  import * as path from 'path';
3
3
  export { path };
4
4
  // @push.rocks scope
5
+ import * as smartnftables from '@push.rocks/smartnftables';
5
6
  import * as smartrust from '@push.rocks/smartrust';
6
- export { smartrust };
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUVoQixvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsU0FBUyxFQUFFLENBQUMifQ==
7
+ export { smartnftables, smartrust };
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUVoQixvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLGFBQWEsTUFBTSwyQkFBMkIsQ0FBQztBQUMzRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxhQUFhLEVBQUUsU0FBUyxFQUFFLENBQUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/remoteingress",
3
- "version": "4.14.2",
3
+ "version": "4.15.0",
4
4
  "private": false,
5
5
  "description": "Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.",
6
6
  "main": "dist_ts/index.js",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@push.rocks/qenv": "^6.1.3",
27
+ "@push.rocks/smartnftables": "^1.0.1",
27
28
  "@push.rocks/smartrust": "^1.3.2"
28
29
  },
29
30
  "repository": {
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @serve.zone/remoteingress
2
2
 
3
- Edge ingress tunnel for DcRouter — tunnels **TCP and UDP** traffic from the network edge to a private DcRouter/SmartProxy cluster over encrypted TLS or QUIC connections, preserving the original client IP via PROXY protocol.
3
+ Edge ingress tunnel for DcRouter — tunnels **TCP and UDP** traffic from the network edge to a private DcRouter/SmartProxy cluster over encrypted TLS or QUIC connections, preserving the original client IP via PROXY protocol. Includes **hub-controlled nftables firewall** for IP blocking, rate limiting, and custom firewall rules applied directly at the edge.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -12,49 +12,55 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
12
12
  pnpm install @serve.zone/remoteingress
13
13
  ```
14
14
 
15
- ## 🏗️ Architecture
15
+ ## Architecture
16
16
 
17
17
  `@serve.zone/remoteingress` uses a **Hub/Edge** topology with a high-performance Rust core and a TypeScript API surface:
18
18
 
19
19
  ```
20
- ┌─────────────────────┐ TLS or QUIC Tunnel ┌─────────────────────┐
21
- │ Network Edge │ ◄══════════════════════════► │ Private Cluster │
22
- │ TCP+TLS: frame mux │
23
- RemoteIngressEdge │ QUIC: native streams RemoteIngressHub
24
- │ UDP: QUIC datagrams │
25
- Accepts TCP & UDP Forwards to
26
- on hub-assigned SmartProxy on
27
- ports local ports
28
- └─────────────────────┘ └─────────────────────┘
29
-
30
- TCP + UDP from end users ▼
31
- Internet DcRouter / SmartProxy
20
+ TLS or QUIC Tunnel
21
+ ┌─────────────────────┐ ◄══════════════════════════► ┌─────────────────────┐
22
+ Network Edge │ TCP+TLS: frame mux │ Private Cluster
23
+ │ QUIC: native streams
24
+ RemoteIngressEdge │ UDP: QUIC datagrams │ RemoteIngressHub
25
+
26
+ Accepts TCP & UDP Forwards to
27
+ on hub-assigned SmartProxy on
28
+ │ ports │ │ local ports │
29
+ │ │ │
30
+ 🔥 nftables rules │ ◄── firewall config pushed ── │ Configures edge │
31
+ │ applied locally │ via FRAME_CONFIG │ firewalls remotely │
32
+ └─────────────────────┘ └─────────────────────┘
33
+ ▲ │
34
+ │ TCP + UDP from end users ▼
35
+ Internet DcRouter / SmartProxy
32
36
  ```
33
37
 
34
38
  | Component | Role |
35
39
  |-----------|------|
36
- | **RemoteIngressEdge** | Deployed at the network edge (VPS, cloud instance). Listens on TCP and UDP ports assigned by the hub, accepts connections/datagrams, and tunnels them to the hub. Ports are hot-reloadable at runtime. |
37
- | **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams/datagrams, and forwards each to SmartProxy with PROXY protocol headers so the real client IP is preserved. |
40
+ | **RemoteIngressEdge** | Deployed at the network edge (VPS, cloud instance). Runs as root. Listens on TCP and UDP ports assigned by the hub, accepts connections/datagrams, and tunnels them to the hub. Applies nftables firewall rules pushed by the hub for IP blocking and rate limiting. Ports and firewall config are hot-reloadable at runtime. |
41
+ | **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams/datagrams, and forwards each to SmartProxy with PROXY protocol headers so the real client IP is preserved. Pushes firewall configuration to edges. |
38
42
  | **Rust Binary** (`remoteingress-bin`) | The performance-critical networking core. Managed via `@push.rocks/smartrust` RustBridge IPC — you never interact with it directly. Cross-compiled for `linux/amd64` and `linux/arm64`. |
39
43
 
40
- ### Key Features
41
-
42
- - 🔒 **Dual transport** — choose between TCP+TLS (frame-multiplexed) or QUIC (native stream multiplexing, zero head-of-line blocking)
43
- - 🌐 **TCP + UDP tunneling** — tunnel any TCP connection or UDP datagram through the same edge/hub pair
44
- - 📋 **PROXY protocol v1 & v2** — SmartProxy sees the real client IP for both TCP (v1 text) and UDP (v2 binary)
45
- - 🔀 **Multiplexed streams** — thousands of concurrent TCP connections over a single tunnel
46
- - **QUIC datagrams** — UDP traffic forwarded via QUIC unreliable datagrams for lowest possible latency
47
- - 🔑 **Shared-secret authentication** — edges must present valid credentials to connect
48
- - 🎫 **Connection tokens** — encode all connection details into a single opaque base64url string
49
- - 📡 **STUN-based public IP discovery** — edges automatically discover their public IP via Cloudflare STUN
50
- - 🔄 **Auto-reconnect** with exponential backoff if the tunnel drops
51
- - 🎛️ **Dynamic port configuration** the hub assigns TCP and UDP listen ports per edge, hot-reloadable at runtime
52
- - 📣 **Event-driven**both Hub and Edge extend `EventEmitter` for real-time monitoring
53
- - 🎚️ **3-tier QoS** — control frames, normal data, and sustained (elephant flow) traffic each get their own priority queue
54
- - 📊 **Adaptive flow control** per-stream windows scale with active stream count to prevent memory overuse
55
- - 🕒 **UDP session management** — automatic session tracking with 60s idle timeout and cleanup
56
-
57
- ## 🚀 Usage
44
+ ### Key Features
45
+
46
+ - **Dual transport** — choose between TCP+TLS (frame-multiplexed) or QUIC (native stream multiplexing, zero head-of-line blocking)
47
+ - **TCP + UDP tunneling** — tunnel any TCP connection or UDP datagram through the same edge/hub pair
48
+ - **PROXY protocol v1 & v2** — SmartProxy sees the real client IP for both TCP (v1 text) and UDP (v2 binary)
49
+ - **Hub-controlled firewall** — push nftables rules (IP blocking, rate limiting, custom rules) from the hub to edges via `@push.rocks/smartnftables`
50
+ - **Multiplexed streams** — thousands of concurrent TCP connections over a single tunnel
51
+ - **QUIC datagrams** — UDP traffic forwarded via QUIC unreliable datagrams for lowest possible latency
52
+ - **Shared-secret authentication** — edges must present valid credentials to connect
53
+ - **Connection tokens** — encode all connection details into a single opaque base64url string
54
+ - **STUN-based public IP discovery** edges automatically discover their public IP via Cloudflare STUN
55
+ - **Auto-reconnect** with exponential backoff if the tunnel drops
56
+ - **Dynamic port configuration** — the hub assigns TCP and UDP listen ports per edge, hot-reloadable at runtime
57
+ - **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring
58
+ - **3-tier QoS** control frames, normal data, and sustained (elephant flow) traffic each get their own priority queue
59
+ - **Adaptive flow control** — per-stream windows scale with active stream count to prevent memory overuse
60
+ - **UDP session management** — automatic session tracking with 60s idle timeout and cleanup
61
+ - **Crash recovery** — automatic restart with exponential backoff if the Rust binary crashes unexpectedly
62
+
63
+ ## Usage
58
64
 
59
65
  Both classes are imported from the package and communicate with the Rust binary under the hood.
60
66
 
@@ -77,7 +83,7 @@ await hub.start({
77
83
  targetHost: '127.0.0.1', // SmartProxy host to forward traffic to
78
84
  });
79
85
 
80
- // Register allowed edges with TCP and UDP listen ports
86
+ // Register allowed edges with TCP and UDP listen ports + firewall config
81
87
  await hub.updateAllowedEdges([
82
88
  {
83
89
  id: 'edge-nyc-01',
@@ -85,6 +91,15 @@ await hub.updateAllowedEdges([
85
91
  listenPorts: [80, 443], // TCP ports the edge should listen on
86
92
  listenPortsUdp: [53, 51820], // UDP ports (e.g., DNS, WireGuard)
87
93
  stunIntervalSecs: 300,
94
+ firewallConfig: {
95
+ blockedIps: ['192.168.1.100', '10.0.0.0/8'],
96
+ rateLimits: [
97
+ { id: 'http-rate', port: 80, protocol: 'tcp', rate: '100/second', perSourceIP: true },
98
+ ],
99
+ rules: [
100
+ { id: 'allow-ssh', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/24', destPort: 22, protocol: 'tcp' },
101
+ ],
102
+ },
88
103
  },
89
104
  {
90
105
  id: 'edge-fra-02',
@@ -93,13 +108,19 @@ await hub.updateAllowedEdges([
93
108
  },
94
109
  ]);
95
110
 
96
- // Dynamically update ports — changes are pushed instantly to connected edges
111
+ // Dynamically update ports and firewall — changes are pushed instantly to connected edges
97
112
  await hub.updateAllowedEdges([
98
113
  {
99
114
  id: 'edge-nyc-01',
100
115
  secret: 'supersecrettoken1',
101
116
  listenPorts: [80, 443, 8443], // added TCP port 8443
102
117
  listenPortsUdp: [53], // removed WireGuard UDP port
118
+ firewallConfig: {
119
+ blockedIps: ['192.168.1.100', '10.0.0.0/8', '203.0.113.50'], // added new blocked IP
120
+ rateLimits: [
121
+ { id: 'http-rate', port: 80, protocol: 'tcp', rate: '200/second', perSourceIP: true },
122
+ ],
123
+ },
103
124
  },
104
125
  ]);
105
126
 
@@ -112,7 +133,7 @@ await hub.stop();
112
133
 
113
134
  ### Setting Up the Edge (Network Edge Side)
114
135
 
115
- The edge can connect via **TCP+TLS** (default) or **QUIC** transport.
136
+ The edge can connect via **TCP+TLS** (default) or **QUIC** transport. Edges run as **root** so they can bind to privileged ports and apply nftables firewall rules.
116
137
 
117
138
  #### Option A: Connection Token (Recommended)
118
139
 
@@ -125,6 +146,7 @@ edge.on('tunnelConnected', () => console.log('Tunnel established'));
125
146
  edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
126
147
  edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
127
148
  edge.on('portsAssigned', ({ listenPorts }) => console.log(`TCP ports: ${listenPorts}`));
149
+ edge.on('firewallConfigUpdated', () => console.log('Firewall rules applied'));
128
150
 
129
151
  await edge.start({
130
152
  token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...',
@@ -160,7 +182,7 @@ await edge.stop();
160
182
  | `'quic'` | QUIC with native stream multiplexing. Eliminates head-of-line blocking. Uses QUIC datagrams for UDP traffic. |
161
183
  | `'quicWithFallback'` | Tries QUIC first (5s timeout), falls back to TCP+TLS if UDP is blocked by the network. |
162
184
 
163
- ### 🎫 Connection Tokens
185
+ ### Connection Tokens
164
186
 
165
187
  Encode all connection details into a single opaque string for easy distribution:
166
188
 
@@ -183,7 +205,78 @@ const data = decodeConnectionToken(token);
183
205
 
184
206
  Tokens are base64url-encoded — safe for environment variables, CLI arguments, and config files.
185
207
 
186
- ## 📖 API Reference
208
+ ## 🔥 Hub-Controlled Firewall
209
+
210
+ Edges run as root and use `@push.rocks/smartnftables` to apply nftables rules pushed from the hub. This gives you centralized control over network-level security at every edge node.
211
+
212
+ ### How It Works
213
+
214
+ 1. The hub includes `firewallConfig` when calling `updateAllowedEdges()`
215
+ 2. The config flows through the Rust binary as an opaque JSON blob via `FRAME_CONFIG`
216
+ 3. The edge TypeScript layer receives it and applies the rules using `SmartNftables`
217
+ 4. On each config update, all previous rules are replaced atomically (full replacement, not incremental)
218
+
219
+ ### Firewall Config Structure
220
+
221
+ ```typescript
222
+ interface IFirewallConfig {
223
+ blockedIps?: string[]; // IPs or CIDRs to block (e.g., '1.2.3.4', '10.0.0.0/8')
224
+ rateLimits?: IFirewallRateLimit[];
225
+ rules?: IFirewallRule[];
226
+ }
227
+
228
+ interface IFirewallRateLimit {
229
+ id: string; // unique identifier for this rate limit
230
+ port: number; // port to rate-limit
231
+ protocol?: 'tcp' | 'udp'; // default: both
232
+ rate: string; // e.g., '100/second', '1000/minute'
233
+ burst?: number; // burst allowance
234
+ perSourceIP?: boolean; // per-client rate limiting (recommended)
235
+ }
236
+
237
+ interface IFirewallRule {
238
+ id: string; // unique identifier for this rule
239
+ direction: 'input' | 'output' | 'forward';
240
+ action: 'accept' | 'drop' | 'reject';
241
+ sourceIP?: string; // source IP or CIDR
242
+ destPort?: number; // destination port
243
+ protocol?: 'tcp' | 'udp';
244
+ comment?: string;
245
+ }
246
+ ```
247
+
248
+ ### Example: Rate Limiting + IP Blocking
249
+
250
+ ```typescript
251
+ await hub.updateAllowedEdges([
252
+ {
253
+ id: 'edge-nyc-01',
254
+ secret: 'secret',
255
+ listenPorts: [80, 443],
256
+ firewallConfig: {
257
+ // Block known bad actors
258
+ blockedIps: ['198.51.100.0/24', '203.0.113.50'],
259
+
260
+ // Rate limit HTTP traffic per source IP
261
+ rateLimits: [
262
+ { id: 'http', port: 80, protocol: 'tcp', rate: '100/second', burst: 50, perSourceIP: true },
263
+ { id: 'https', port: 443, protocol: 'tcp', rate: '200/second', burst: 100, perSourceIP: true },
264
+ ],
265
+
266
+ // Allow monitoring from trusted subnet
267
+ rules: [
268
+ { id: 'monitoring', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/24', destPort: 9090, protocol: 'tcp', comment: 'Prometheus scraping' },
269
+ ],
270
+ },
271
+ },
272
+ ]);
273
+ ```
274
+
275
+ ### Graceful Degradation
276
+
277
+ If the edge isn't running as root or nftables is unavailable, the SmartNftables initialization logs a warning and continues operating normally — the tunnel works fine, just without kernel-level firewall rules.
278
+
279
+ ## API Reference
187
280
 
188
281
  ### `RemoteIngressHub`
189
282
 
@@ -191,22 +284,22 @@ Tokens are base64url-encoded — safe for environment variables, CLI arguments,
191
284
  |-------------------|-------------|
192
285
  | `start(config?)` | Start the hub. Config: `{ tunnelPort?, targetHost?, tls?: { certPem?, keyPem? } }`. Listens on both TCP and UDP (QUIC) on the tunnel port. |
193
286
  | `stop()` | Graceful shutdown. |
194
- | `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs? }`. Port changes are pushed to connected edges in real time. |
287
+ | `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs?, firewallConfig? }`. Port and firewall changes are pushed to connected edges in real time. |
195
288
  | `getStatus()` | Returns `{ running, tunnelPort, connectedEdges: [...] }`. |
196
289
  | `running` | `boolean` — whether the Rust binary is alive. |
197
290
 
198
- **Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`
291
+ **Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`, `crashRecovered`, `crashRecoveryFailed`
199
292
 
200
293
  ### `RemoteIngressEdge`
201
294
 
202
295
  | Method / Property | Description |
203
296
  |-------------------|-------------|
204
- | `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, transportMode? }`. |
205
- | `stop()` | Graceful shutdown. |
297
+ | `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, bindAddress?, transportMode? }`. |
298
+ | `stop()` | Graceful shutdown. Cleans up all nftables rules. |
206
299
  | `getStatus()` | Returns `{ running, connected, publicIp, activeStreams, listenPorts }`. |
207
300
  | `running` | `boolean` — whether the Rust binary is alive. |
208
301
 
209
- **Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`
302
+ **Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`, `firewallConfigUpdated`, `crashRecovered`, `crashRecoveryFailed`
210
303
 
211
304
  ### Token Utilities
212
305
 
@@ -244,7 +337,7 @@ interface IConnectionTokenData {
244
337
  }
245
338
  ```
246
339
 
247
- ## 🔌 Wire Protocol
340
+ ## Wire Protocol
248
341
 
249
342
  ### TCP+TLS Transport (Frame Protocol)
250
343
 
@@ -261,7 +354,7 @@ The tunnel uses a custom binary frame protocol over a single TLS connection:
261
354
  | `CLOSE` | `0x03` | Edge → Hub | Client closed connection |
262
355
  | `DATA_BACK` | `0x04` | Hub → Edge | Response data (download) |
263
356
  | `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream closed connection |
264
- | `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON payload) |
357
+ | `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON: ports + firewall config) |
265
358
  | `PING` | `0x07` | Hub → Edge | Heartbeat probe (every 15s) |
266
359
  | `PONG` | `0x08` | Edge → Hub | Heartbeat response |
267
360
  | `WINDOW_UPDATE` | `0x09` | Edge → Hub | Flow control: edge consumed N bytes |
@@ -284,19 +377,20 @@ When using QUIC, the frame protocol is replaced by native QUIC primitives:
284
377
  1. Edge opens a TLS or QUIC connection to the hub
285
378
  2. Edge sends: `EDGE <edgeId> <secret>\n`
286
379
  3. Hub verifies credentials (constant-time comparison) and responds with JSON:
287
- `{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300}\n`
380
+ `{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300,"firewallConfig":{...}}\n`
288
381
  4. Edge starts TCP and UDP listeners on the assigned ports
289
- 5. Data flows TCP frames/QUIC streams for TCP traffic, UDP frames/QUIC datagrams for UDP traffic
382
+ 5. Edge applies firewall config via nftables (if present and running as root)
383
+ 6. Data flows — TCP frames/QUIC streams for TCP traffic, UDP frames/QUIC datagrams for UDP traffic
290
384
 
291
- ## 🎚️ QoS & Flow Control
385
+ ## QoS & Flow Control
292
386
 
293
387
  ### Priority Tiers (TCP+TLS Transport)
294
388
 
295
389
  | Tier | Frames | Behavior |
296
390
  |------|--------|----------|
297
- | 🔴 **Control** | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. |
298
- | 🟡 **Data** | DATA/DATA_BACK from normal streams, UDP frames | Drained when control queue is empty. |
299
- | 🟢 **Sustained** | DATA/DATA_BACK from elephant flows | Lowest priority with guaranteed **1 MB/s** drain rate. |
391
+ | **Control** | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. |
392
+ | **Data** | DATA/DATA_BACK from normal streams, UDP frames | Drained when control queue is empty. |
393
+ | **Sustained** | DATA/DATA_BACK from elephant flows | Lowest priority with guaranteed **1 MB/s** drain rate. |
300
394
 
301
395
  ### Sustained Stream Classification
302
396
 
@@ -318,17 +412,17 @@ Each TCP stream has a send window from a shared **200 MB budget**:
318
412
 
319
413
  UDP traffic uses no flow control — datagrams are fire-and-forget, matching UDP semantics.
320
414
 
321
- ## 💡 Example Scenarios
415
+ ## Example Scenarios
322
416
 
323
- ### 1. Expose a Private Cluster to the Internet
417
+ ### 1. 🌐 Expose a Private Cluster to the Internet
324
418
 
325
419
  Deploy an Edge on a public VPS, point DNS to its IP. The Edge tunnels all TCP and UDP traffic to the Hub running inside your private cluster. No public ports needed on the cluster.
326
420
 
327
- ### 2. Multi-Region Edge Ingress
421
+ ### 2. 🗺️ Multi-Region Edge Ingress
328
422
 
329
423
  Run Edges in NYC, Frankfurt, and Tokyo — all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. PROXY protocol ensures the Hub sees real client IPs regardless of which Edge they entered through.
330
424
 
331
- ### 3. UDP Forwarding (DNS, Gaming, VoIP)
425
+ ### 3. 📡 UDP Forwarding (DNS, Gaming, VoIP)
332
426
 
333
427
  Configure UDP listen ports alongside TCP ports. DNS queries, game server traffic, or VoIP packets are tunneled through the same edge/hub connection and forwarded to SmartProxy with a PROXY v2 binary header preserving the client's real IP.
334
428
 
@@ -343,7 +437,7 @@ await hub.updateAllowedEdges([
343
437
  ]);
344
438
  ```
345
439
 
346
- ### 4. QUIC Transport for Low-Latency
440
+ ### 4. 🚀 QUIC Transport for Low-Latency
347
441
 
348
442
  Use QUIC transport to eliminate head-of-line blocking — a lost packet on one stream doesn't stall others. QUIC also enables 0-RTT reconnection and connection migration.
349
443
 
@@ -357,11 +451,13 @@ await edge.start({
357
451
  });
358
452
  ```
359
453
 
360
- ### 5. Token-Based Edge Provisioning
454
+ ### 5. 🔑 Token-Based Edge Provisioning
361
455
 
362
456
  Generate connection tokens on the hub side and distribute them to edge operators:
363
457
 
364
458
  ```typescript
459
+ import { encodeConnectionToken, RemoteIngressEdge } from '@serve.zone/remoteingress';
460
+
365
461
  const token = encodeConnectionToken({
366
462
  hubHost: 'hub.prod.example.com',
367
463
  hubPort: 8443,
@@ -374,9 +470,33 @@ const edge = new RemoteIngressEdge();
374
470
  await edge.start({ token });
375
471
  ```
376
472
 
473
+ ### 6. 🛡️ Centralized Firewall Management
474
+
475
+ Push firewall rules from the hub to all your edge nodes. Block bad actors, rate-limit abusive traffic, and whitelist trusted subnets — all from a single control plane:
476
+
477
+ ```typescript
478
+ await hub.updateAllowedEdges([
479
+ {
480
+ id: 'edge-nyc-01',
481
+ secret: 'secret',
482
+ listenPorts: [80, 443],
483
+ firewallConfig: {
484
+ blockedIps: ['198.51.100.0/24'],
485
+ rateLimits: [
486
+ { id: 'https', port: 443, protocol: 'tcp', rate: '500/second', perSourceIP: true, burst: 100 },
487
+ ],
488
+ rules: [
489
+ { id: 'allow-monitoring', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/8', destPort: 9090, protocol: 'tcp' },
490
+ ],
491
+ },
492
+ },
493
+ ]);
494
+ // Firewall rules are applied at the edge via nftables within seconds
495
+ ```
496
+
377
497
  ## License and Legal Information
378
498
 
379
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.
499
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
380
500
 
381
501
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
382
502
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.14.2',
6
+ version: '4.15.0',
7
7
  description: 'Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.'
8
8
  }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { EventEmitter } from 'events';
3
3
  import { decodeConnectionToken } from './classes.token.js';
4
+ import type { IFirewallConfig } from './classes.remoteingresshub.js';
4
5
 
5
6
  // Command map for the edge side of remoteingress-bin
6
7
  type TEdgeCommands = {
@@ -55,6 +56,7 @@ export class RemoteIngressEdge extends EventEmitter {
55
56
  private restartBackoffMs = 1000;
56
57
  private restartAttempts = 0;
57
58
  private statusInterval: ReturnType<typeof setInterval> | undefined;
59
+ private nft: InstanceType<typeof plugins.smartnftables.SmartNftables> | null = null;
58
60
 
59
61
  constructor() {
60
62
  super();
@@ -110,6 +112,83 @@ export class RemoteIngressEdge extends EventEmitter {
110
112
  console.log(`[RemoteIngressEdge] Ports updated by hub: ${data.listenPorts.join(', ')}`);
111
113
  this.emit('portsUpdated', data);
112
114
  });
115
+ this.bridge.on('management:firewallConfigUpdated', (data: { firewallConfig: IFirewallConfig }) => {
116
+ console.log(`[RemoteIngressEdge] Firewall config updated from hub`);
117
+ this.applyFirewallConfig(data.firewallConfig);
118
+ this.emit('firewallConfigUpdated', data);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Initialize the nftables manager. Fails gracefully if not running as root.
124
+ */
125
+ private async initNft(): Promise<void> {
126
+ try {
127
+ this.nft = new plugins.smartnftables.SmartNftables({
128
+ tableName: 'remoteingress',
129
+ dryRun: false,
130
+ });
131
+ await this.nft.initialize();
132
+ console.log('[RemoteIngressEdge] SmartNftables initialized');
133
+ } catch (err) {
134
+ console.warn(`[RemoteIngressEdge] Failed to initialize nftables (not root?): ${err}`);
135
+ this.nft = null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Apply firewall configuration received from the hub.
141
+ * Performs a full replacement: cleans up existing rules, then applies the new config.
142
+ */
143
+ private async applyFirewallConfig(config: IFirewallConfig): Promise<void> {
144
+ if (!this.nft) {
145
+ return;
146
+ }
147
+
148
+ try {
149
+ // Full cleanup and reinitialize to replace all rules atomically
150
+ await this.nft.cleanup();
151
+ await this.nft.initialize();
152
+
153
+ // Apply blocked IPs
154
+ if (config.blockedIps && config.blockedIps.length > 0) {
155
+ for (const ip of config.blockedIps) {
156
+ await this.nft.firewall.blockIP(ip);
157
+ }
158
+ console.log(`[RemoteIngressEdge] Blocked ${config.blockedIps.length} IPs`);
159
+ }
160
+
161
+ // Apply rate limits
162
+ if (config.rateLimits && config.rateLimits.length > 0) {
163
+ for (const rl of config.rateLimits) {
164
+ await this.nft.rateLimit.addRateLimit(rl.id, {
165
+ port: rl.port,
166
+ protocol: rl.protocol,
167
+ rate: rl.rate,
168
+ burst: rl.burst,
169
+ perSourceIP: rl.perSourceIP,
170
+ });
171
+ }
172
+ console.log(`[RemoteIngressEdge] Applied ${config.rateLimits.length} rate limits`);
173
+ }
174
+
175
+ // Apply firewall rules
176
+ if (config.rules && config.rules.length > 0) {
177
+ for (const rule of config.rules) {
178
+ await this.nft.firewall.addRule(rule.id, {
179
+ direction: rule.direction,
180
+ action: rule.action,
181
+ sourceIP: rule.sourceIP,
182
+ destPort: rule.destPort,
183
+ protocol: rule.protocol,
184
+ comment: rule.comment,
185
+ });
186
+ }
187
+ console.log(`[RemoteIngressEdge] Applied ${config.rules.length} firewall rules`);
188
+ }
189
+ } catch (err) {
190
+ console.error(`[RemoteIngressEdge] Failed to apply firewall config: ${err}`);
191
+ }
113
192
  }
114
193
 
115
194
  /**
@@ -156,6 +235,9 @@ export class RemoteIngressEdge extends EventEmitter {
156
235
  this.restartAttempts = 0;
157
236
  this.restartBackoffMs = 1000;
158
237
 
238
+ // Initialize nftables (graceful degradation if not root)
239
+ await this.initNft();
240
+
159
241
  // Start periodic status logging
160
242
  this.statusInterval = setInterval(async () => {
161
243
  try {
@@ -180,6 +262,15 @@ export class RemoteIngressEdge extends EventEmitter {
180
262
  clearInterval(this.statusInterval);
181
263
  this.statusInterval = undefined;
182
264
  }
265
+ // Clean up nftables rules before stopping
266
+ if (this.nft) {
267
+ try {
268
+ await this.nft.cleanup();
269
+ } catch (err) {
270
+ console.warn(`[RemoteIngressEdge] nftables cleanup error: ${err}`);
271
+ }
272
+ this.nft = null;
273
+ }
183
274
  if (this.started) {
184
275
  try {
185
276
  await this.bridge.sendCommand('stopEdge', {} as Record<string, never>);
@@ -261,6 +352,9 @@ export class RemoteIngressEdge extends EventEmitter {
261
352
  this.restartAttempts = 0;
262
353
  this.restartBackoffMs = 1000;
263
354
 
355
+ // Re-initialize nftables (hub will re-push config via handshake)
356
+ await this.initNft();
357
+
264
358
  // Restart periodic status logging
265
359
  this.statusInterval = setInterval(async () => {
266
360
  try {
@@ -22,7 +22,7 @@ type THubCommands = {
22
22
  };
23
23
  updateAllowedEdges: {
24
24
  params: {
25
- edges: Array<{ id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number }>;
25
+ edges: Array<{ id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig }>;
26
26
  };
27
27
  result: { updated: boolean };
28
28
  };
@@ -41,6 +41,31 @@ type THubCommands = {
41
41
  };
42
42
  };
43
43
 
44
+ export interface IFirewallRateLimit {
45
+ id: string;
46
+ port: number;
47
+ protocol?: 'tcp' | 'udp';
48
+ rate: string;
49
+ burst?: number;
50
+ perSourceIP?: boolean;
51
+ }
52
+
53
+ export interface IFirewallRule {
54
+ id: string;
55
+ direction: 'input' | 'output' | 'forward';
56
+ action: 'accept' | 'drop' | 'reject';
57
+ sourceIP?: string;
58
+ destPort?: number;
59
+ protocol?: 'tcp' | 'udp';
60
+ comment?: string;
61
+ }
62
+
63
+ export interface IFirewallConfig {
64
+ blockedIps?: string[];
65
+ rateLimits?: IFirewallRateLimit[];
66
+ rules?: IFirewallRule[];
67
+ }
68
+
44
69
  export interface IHubConfig {
45
70
  tunnelPort?: number;
46
71
  targetHost?: string;
@@ -50,7 +75,7 @@ export interface IHubConfig {
50
75
  };
51
76
  }
52
77
 
53
- type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number };
78
+ type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig };
54
79
 
55
80
  const MAX_RESTART_ATTEMPTS = 10;
56
81
  const MAX_RESTART_BACKOFF_MS = 30_000;
package/ts/plugins.ts CHANGED
@@ -3,5 +3,6 @@ import * as path from 'path';
3
3
  export { path };
4
4
 
5
5
  // @push.rocks scope
6
+ import * as smartnftables from '@push.rocks/smartnftables';
6
7
  import * as smartrust from '@push.rocks/smartrust';
7
- export { smartrust };
8
+ export { smartnftables, smartrust };