@serve.zone/dcrouter 13.22.0 → 13.24.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.
Files changed (51) hide show
  1. package/dist_serve/bundle.js +952 -792
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +4 -0
  4. package/dist_ts/classes.dcrouter.js +69 -2
  5. package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +25 -0
  6. package/dist_ts/db/documents/classes.ip-intelligence.doc.js +175 -0
  7. package/dist_ts/db/documents/classes.security-block-rule.doc.d.ts +17 -0
  8. package/dist_ts/db/documents/classes.security-block-rule.doc.js +124 -0
  9. package/dist_ts/db/documents/classes.security-policy-audit.doc.d.ts +11 -0
  10. package/dist_ts/db/documents/classes.security-policy-audit.doc.js +95 -0
  11. package/dist_ts/db/documents/index.d.ts +3 -0
  12. package/dist_ts/db/documents/index.js +4 -1
  13. package/dist_ts/monitoring/classes.metricsmanager.js +2 -1
  14. package/dist_ts/opsserver/handlers/security.handler.js +63 -1
  15. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +10 -0
  16. package/dist_ts/remoteingress/classes.remoteingress-manager.js +9 -1
  17. package/dist_ts/security/classes.security-policy-manager.d.ts +46 -0
  18. package/dist_ts/security/classes.security-policy-manager.js +304 -0
  19. package/dist_ts/security/index.d.ts +1 -0
  20. package/dist_ts/security/index.js +2 -1
  21. package/dist_ts_interfaces/data/index.d.ts +1 -0
  22. package/dist_ts_interfaces/data/index.js +2 -1
  23. package/dist_ts_interfaces/data/security-policy.d.ts +32 -0
  24. package/dist_ts_interfaces/data/security-policy.js +2 -0
  25. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  26. package/dist_ts_interfaces/requests/index.js +2 -1
  27. package/dist_ts_interfaces/requests/security-policy.d.ts +95 -0
  28. package/dist_ts_interfaces/requests/security-policy.js +2 -0
  29. package/dist_ts_web/00_commitinfo_data.js +1 -1
  30. package/dist_ts_web/appstate.d.ts +28 -0
  31. package/dist_ts_web/appstate.js +171 -4
  32. package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +9 -0
  33. package/dist_ts_web/elements/network/ops-view-network-activity.js +210 -3
  34. package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +12 -3
  35. package/dist_ts_web/elements/security/ops-view-security-blocked.js +407 -52
  36. package/package.json +3 -3
  37. package/ts/00_commitinfo_data.ts +1 -1
  38. package/ts/classes.dcrouter.ts +91 -3
  39. package/ts/db/documents/classes.ip-intelligence.doc.ts +75 -0
  40. package/ts/db/documents/classes.security-block-rule.doc.ts +52 -0
  41. package/ts/db/documents/classes.security-policy-audit.doc.ts +33 -0
  42. package/ts/db/documents/index.ts +3 -0
  43. package/ts/monitoring/classes.metricsmanager.ts +2 -0
  44. package/ts/opsserver/handlers/security.handler.ts +107 -0
  45. package/ts/remoteingress/classes.remoteingress-manager.ts +15 -2
  46. package/ts/security/classes.security-policy-manager.ts +340 -0
  47. package/ts/security/index.ts +7 -1
  48. package/ts_web/00_commitinfo_data.ts +1 -1
  49. package/ts_web/appstate.ts +236 -3
  50. package/ts_web/elements/network/ops-view-network-activity.ts +219 -2
  51. package/ts_web/elements/security/ops-view-security-blocked.ts +414 -51
@@ -27,12 +27,13 @@ import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
27
27
  import { VpnManager, type IVpnManagerConfig } from './vpn/index.js';
28
28
  import { RouteConfigManager, ApiTokenManager, ReferenceResolver, DbSeeder, TargetProfileManager } from './config/index.js';
29
29
  import type { TIpAllowEntry } from './config/classes.route-config-manager.js';
30
- import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js';
30
+ import { SecurityLogger, ContentScanner, IPReputationChecker, SecurityPolicyManager } from './security/index.js';
31
31
  import { type IHttp3Config, augmentRoutesWithHttp3 } from './http3/index.js';
32
32
  import { DnsManager } from './dns/manager.dns.js';
33
33
  import { AcmeConfigManager } from './acme/manager.acme-config.js';
34
34
  import { EmailDomainManager, SmartMtaStorageManager, buildEmailDnsRecords } from './email/index.js';
35
35
  import type { IRoute } from '../ts_interfaces/data/route-management.js';
36
+ import type { ISecurityCompiledPolicy } from '../ts_interfaces/data/security-policy.js';
36
37
 
37
38
  export interface IDcRouterOptions {
38
39
  /** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
@@ -284,6 +285,7 @@ export class DcRouter {
284
285
  // ACME configuration (DB-backed singleton, replaces tls.contactEmail)
285
286
  public acmeConfigManager?: AcmeConfigManager;
286
287
  public emailDomainManager?: EmailDomainManager;
288
+ public securityPolicyManager?: SecurityPolicyManager;
287
289
 
288
290
  // Auto-discovered public IP (populated by generateAuthoritativeRecords)
289
291
  public detectedPublicIp: string | null = null;
@@ -471,12 +473,36 @@ export class DcRouter {
471
473
  );
472
474
  }
473
475
 
476
+ // SecurityPolicyManager: optional, depends on DcRouterDb — owns IP intelligence
477
+ // and compiles the global block policy for SmartProxy and remote ingress edges.
478
+ if (this.options.dbConfig?.enabled !== false) {
479
+ this.serviceManager.addService(
480
+ new plugins.taskbuffer.Service('SecurityPolicyManager')
481
+ .optional()
482
+ .dependsOn('DcRouterDb')
483
+ .withStart(async () => {
484
+ this.securityPolicyManager = new SecurityPolicyManager({
485
+ onPolicyChanged: () => this.applySecurityPolicy(),
486
+ });
487
+ await this.securityPolicyManager.start();
488
+ })
489
+ .withStop(async () => {
490
+ if (this.securityPolicyManager) {
491
+ await this.securityPolicyManager.stop();
492
+ this.securityPolicyManager = undefined;
493
+ }
494
+ })
495
+ .withRetry({ maxRetries: 1, baseDelayMs: 500 }),
496
+ );
497
+ }
498
+
474
499
  // SmartProxy: critical, depends on DcRouterDb + DnsManager + AcmeConfigManager (if enabled)
475
500
  const smartProxyDeps: string[] = [];
476
501
  if (this.options.dbConfig?.enabled !== false) {
477
502
  smartProxyDeps.push('DcRouterDb');
478
503
  smartProxyDeps.push('DnsManager');
479
504
  smartProxyDeps.push('AcmeConfigManager');
505
+ smartProxyDeps.push('SecurityPolicyManager');
480
506
  }
481
507
  this.serviceManager.addService(
482
508
  new plugins.taskbuffer.Service('SmartProxy')
@@ -971,6 +997,12 @@ export class DcRouter {
971
997
  logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration');
972
998
  }
973
999
 
1000
+ const compiledSecurityPolicy = await this.securityPolicyManager?.compileSmartProxyPolicy();
1001
+ const mergedSecurityPolicy = this.mergeSecurityPolicies(
1002
+ (this.options.smartProxyConfig as any)?.securityPolicy,
1003
+ compiledSecurityPolicy,
1004
+ );
1005
+
974
1006
  // If we have routes or need a basic SmartProxy instance, create it
975
1007
  if (routes.length > 0 || this.options.smartProxyConfig) {
976
1008
  logger.log('info', 'Setting up SmartProxy with combined configuration');
@@ -1002,6 +1034,7 @@ export class DcRouter {
1002
1034
  // --- always set by dcrouter (after spread) ---
1003
1035
  routes,
1004
1036
  acme: acmeConfig,
1037
+ ...(mergedSecurityPolicy ? { securityPolicy: mergedSecurityPolicy } as any : {}),
1005
1038
  certStore: {
1006
1039
  loadAll: async () => {
1007
1040
  const docs = await ProxyCertDoc.findAll();
@@ -1244,8 +1277,60 @@ export class DcRouter {
1244
1277
  logger.log('info', `SmartProxy started with ${routes.length} routes`);
1245
1278
  }
1246
1279
  }
1247
-
1248
-
1280
+
1281
+ public async applySecurityPolicy(): Promise<void> {
1282
+ if (!this.securityPolicyManager) {
1283
+ return;
1284
+ }
1285
+
1286
+ const compiledSmartProxyPolicy = await this.securityPolicyManager.compileSmartProxyPolicy();
1287
+ const mergedSecurityPolicy = this.mergeSecurityPolicies(
1288
+ (this.options.smartProxyConfig as any)?.securityPolicy,
1289
+ compiledSmartProxyPolicy,
1290
+ );
1291
+
1292
+ if (this.smartProxy && mergedSecurityPolicy) {
1293
+ const smartProxyWithPolicyApi = this.smartProxy as any;
1294
+ if (typeof smartProxyWithPolicyApi.updateSecurityPolicy === 'function') {
1295
+ await smartProxyWithPolicyApi.updateSecurityPolicy(mergedSecurityPolicy);
1296
+ }
1297
+ }
1298
+
1299
+ const firewallConfig = await this.securityPolicyManager.compileRemoteIngressFirewall();
1300
+ if (this.remoteIngressManager) {
1301
+ (this.remoteIngressManager as any).setFirewallConfig?.(firewallConfig);
1302
+ }
1303
+ if (this.tunnelManager) {
1304
+ await this.tunnelManager.syncAllowedEdges();
1305
+ }
1306
+ }
1307
+
1308
+ private mergeSecurityPolicies(
1309
+ ...policies: Array<Partial<ISecurityCompiledPolicy> | undefined>
1310
+ ): ISecurityCompiledPolicy | undefined {
1311
+ const blockedIps = new Set<string>();
1312
+ const blockedCidrs = new Set<string>();
1313
+
1314
+ for (const policy of policies) {
1315
+ for (const ip of policy?.blockedIps || []) {
1316
+ if (ip) blockedIps.add(ip);
1317
+ }
1318
+ for (const cidr of policy?.blockedCidrs || []) {
1319
+ if (cidr) blockedCidrs.add(cidr);
1320
+ }
1321
+ }
1322
+
1323
+ if (blockedIps.size === 0 && blockedCidrs.size === 0) {
1324
+ return undefined;
1325
+ }
1326
+
1327
+ return {
1328
+ blockedIps: [...blockedIps].sort(),
1329
+ blockedCidrs: [...blockedCidrs].sort(),
1330
+ };
1331
+ }
1332
+
1333
+
1249
1334
 
1250
1335
  /**
1251
1336
  * Generate SmartProxy routes for email configuration
@@ -2232,6 +2317,9 @@ export class DcRouter {
2232
2317
  // Initialize the edge registration manager
2233
2318
  this.remoteIngressManager = new RemoteIngressManager();
2234
2319
  await this.remoteIngressManager.initialize();
2320
+ this.remoteIngressManager.setFirewallConfig(
2321
+ await this.securityPolicyManager?.compileRemoteIngressFirewall(),
2322
+ );
2235
2323
 
2236
2324
  // Pass current bootstrap routes so the manager can derive edge ports initially.
2237
2325
  // Once RouteConfigManager applies the full DB set, the onRoutesApplied callback
@@ -0,0 +1,75 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+ import type { IIpIntelligenceRecord } from '../../../ts_interfaces/data/security-policy.js';
4
+
5
+ const getDb = () => DcRouterDb.getInstance().getDb();
6
+
7
+ @plugins.smartdata.Collection(() => getDb())
8
+ export class IpIntelligenceDoc extends plugins.smartdata.SmartDataDbDoc<IpIntelligenceDoc, IpIntelligenceDoc> implements IIpIntelligenceRecord {
9
+ @plugins.smartdata.unI()
10
+ @plugins.smartdata.svDb()
11
+ public ipAddress!: string;
12
+
13
+ @plugins.smartdata.svDb()
14
+ public asn: number | null = null;
15
+
16
+ @plugins.smartdata.svDb()
17
+ public asnOrg: string | null = null;
18
+
19
+ @plugins.smartdata.svDb()
20
+ public registrantOrg: string | null = null;
21
+
22
+ @plugins.smartdata.svDb()
23
+ public registrantCountry: string | null = null;
24
+
25
+ @plugins.smartdata.svDb()
26
+ public networkRange: string | null = null;
27
+
28
+ @plugins.smartdata.svDb()
29
+ public abuseContact: string | null = null;
30
+
31
+ @plugins.smartdata.svDb()
32
+ public country: string | null = null;
33
+
34
+ @plugins.smartdata.svDb()
35
+ public countryCode: string | null = null;
36
+
37
+ @plugins.smartdata.svDb()
38
+ public city: string | null = null;
39
+
40
+ @plugins.smartdata.svDb()
41
+ public latitude: number | null = null;
42
+
43
+ @plugins.smartdata.svDb()
44
+ public longitude: number | null = null;
45
+
46
+ @plugins.smartdata.svDb()
47
+ public accuracyRadius: number | null = null;
48
+
49
+ @plugins.smartdata.svDb()
50
+ public timezone: string | null = null;
51
+
52
+ @plugins.smartdata.svDb()
53
+ public firstSeenAt: number = Date.now();
54
+
55
+ @plugins.smartdata.svDb()
56
+ public lastSeenAt: number = Date.now();
57
+
58
+ @plugins.smartdata.svDb()
59
+ public updatedAt: number = Date.now();
60
+
61
+ @plugins.smartdata.svDb()
62
+ public seenCount: number = 0;
63
+
64
+ constructor() {
65
+ super();
66
+ }
67
+
68
+ public static async findByIp(ipAddress: string): Promise<IpIntelligenceDoc | null> {
69
+ return await IpIntelligenceDoc.getInstance({ ipAddress });
70
+ }
71
+
72
+ public static async findAll(): Promise<IpIntelligenceDoc[]> {
73
+ return await IpIntelligenceDoc.getInstances({});
74
+ }
75
+ }
@@ -0,0 +1,52 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+ import type { ISecurityBlockRule, TSecurityBlockRuleMatchMode, TSecurityBlockRuleType } from '../../../ts_interfaces/data/security-policy.js';
4
+
5
+ const getDb = () => DcRouterDb.getInstance().getDb();
6
+
7
+ @plugins.smartdata.Collection(() => getDb())
8
+ export class SecurityBlockRuleDoc extends plugins.smartdata.SmartDataDbDoc<SecurityBlockRuleDoc, SecurityBlockRuleDoc> implements ISecurityBlockRule {
9
+ @plugins.smartdata.unI()
10
+ @plugins.smartdata.svDb()
11
+ public id!: string;
12
+
13
+ @plugins.smartdata.svDb()
14
+ public type!: TSecurityBlockRuleType;
15
+
16
+ @plugins.smartdata.svDb()
17
+ public value!: string;
18
+
19
+ @plugins.smartdata.svDb()
20
+ public matchMode?: TSecurityBlockRuleMatchMode;
21
+
22
+ @plugins.smartdata.svDb()
23
+ public enabled: boolean = true;
24
+
25
+ @plugins.smartdata.svDb()
26
+ public reason?: string;
27
+
28
+ @plugins.smartdata.svDb()
29
+ public createdAt: number = Date.now();
30
+
31
+ @plugins.smartdata.svDb()
32
+ public updatedAt: number = Date.now();
33
+
34
+ @plugins.smartdata.svDb()
35
+ public createdBy: string = 'system';
36
+
37
+ constructor() {
38
+ super();
39
+ }
40
+
41
+ public static async findById(id: string): Promise<SecurityBlockRuleDoc | null> {
42
+ return await SecurityBlockRuleDoc.getInstance({ id });
43
+ }
44
+
45
+ public static async findAll(): Promise<SecurityBlockRuleDoc[]> {
46
+ return await SecurityBlockRuleDoc.getInstances({});
47
+ }
48
+
49
+ public static async findEnabled(): Promise<SecurityBlockRuleDoc[]> {
50
+ return await SecurityBlockRuleDoc.getInstances({ enabled: true });
51
+ }
52
+ }
@@ -0,0 +1,33 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+ import type { ISecurityPolicyAuditEvent } from '../../../ts_interfaces/data/security-policy.js';
4
+
5
+ const getDb = () => DcRouterDb.getInstance().getDb();
6
+
7
+ @plugins.smartdata.Collection(() => getDb())
8
+ export class SecurityPolicyAuditDoc extends plugins.smartdata.SmartDataDbDoc<SecurityPolicyAuditDoc, SecurityPolicyAuditDoc> implements ISecurityPolicyAuditEvent {
9
+ @plugins.smartdata.unI()
10
+ @plugins.smartdata.svDb()
11
+ public id!: string;
12
+
13
+ @plugins.smartdata.svDb()
14
+ public action!: string;
15
+
16
+ @plugins.smartdata.svDb()
17
+ public actor!: string;
18
+
19
+ @plugins.smartdata.svDb()
20
+ public details!: Record<string, unknown>;
21
+
22
+ @plugins.smartdata.svDb()
23
+ public createdAt: number = Date.now();
24
+
25
+ constructor() {
26
+ super();
27
+ }
28
+
29
+ public static async findRecent(limit = 100): Promise<SecurityPolicyAuditDoc[]> {
30
+ const docs = await SecurityPolicyAuditDoc.getInstances({});
31
+ return docs.sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
32
+ }
33
+ }
@@ -1,6 +1,9 @@
1
1
  // Cached/TTL document classes
2
2
  export * from './classes.cached.email.js';
3
3
  export * from './classes.cached.ip.reputation.js';
4
+ export * from './classes.ip-intelligence.doc.js';
5
+ export * from './classes.security-block-rule.doc.js';
6
+ export * from './classes.security-policy-audit.doc.js';
4
7
 
5
8
  // Config document classes
6
9
  export * from './classes.route.doc.js';
@@ -725,6 +725,8 @@ export class MetricsManager {
725
725
  .slice(0, 10)
726
726
  .map(([ip, data]) => ({ ip, count: data.count, bwIn: data.bwIn, bwOut: data.bwOut }));
727
727
 
728
+ void this.dcRouter.securityPolicyManager?.observeIps([...allIPData.keys()]);
729
+
728
730
  // Build domain activity using per-IP domain request counts from Rust engine
729
731
  const connectionsByRoute = proxyMetrics.connections.byRoute();
730
732
  const throughputByRoute = proxyMetrics.throughput.byRoute();
@@ -157,6 +157,113 @@ export class SecurityHandler {
157
157
  }
158
158
  )
159
159
  );
160
+
161
+ router.addTypedHandler(
162
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListSecurityBlockRules>(
163
+ 'listSecurityBlockRules',
164
+ async () => {
165
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
166
+ return { rules: manager ? await manager.listBlockRules() : [] };
167
+ },
168
+ ),
169
+ );
170
+
171
+ router.addTypedHandler(
172
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListIpIntelligence>(
173
+ 'listIpIntelligence',
174
+ async () => {
175
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
176
+ return { records: manager ? await manager.listIpIntelligence() : [] };
177
+ },
178
+ ),
179
+ );
180
+
181
+ router.addTypedHandler(
182
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCompiledSecurityPolicy>(
183
+ 'getCompiledSecurityPolicy',
184
+ async () => {
185
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
186
+ return {
187
+ policy: manager
188
+ ? await manager.compilePolicy()
189
+ : { blockedIps: [], blockedCidrs: [] },
190
+ };
191
+ },
192
+ ),
193
+ );
194
+
195
+ router.addTypedHandler(
196
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListSecurityPolicyAudit>(
197
+ 'listSecurityPolicyAudit',
198
+ async (dataArg) => {
199
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
200
+ return { events: manager ? await manager.listAuditEvents(dataArg.limit || 100) : [] };
201
+ },
202
+ ),
203
+ );
204
+
205
+ const adminRouter = this.opsServerRef.adminRouter;
206
+
207
+ adminRouter.addTypedHandler(
208
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateSecurityBlockRule>(
209
+ 'createSecurityBlockRule',
210
+ async (dataArg) => {
211
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
212
+ if (!manager) return { success: false, message: 'Security policy manager not initialized' };
213
+ const rule = await manager.createBlockRule({
214
+ type: dataArg.type,
215
+ value: dataArg.value,
216
+ matchMode: dataArg.matchMode,
217
+ reason: dataArg.reason,
218
+ enabled: dataArg.enabled,
219
+ }, dataArg.identity.userId);
220
+ return { success: true, rule };
221
+ },
222
+ ),
223
+ );
224
+
225
+ adminRouter.addTypedHandler(
226
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSecurityBlockRule>(
227
+ 'updateSecurityBlockRule',
228
+ async (dataArg) => {
229
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
230
+ if (!manager) return { success: false, message: 'Security policy manager not initialized' };
231
+ const rule = await manager.updateBlockRule(dataArg.id, {
232
+ value: dataArg.value,
233
+ matchMode: dataArg.matchMode,
234
+ reason: dataArg.reason,
235
+ enabled: dataArg.enabled,
236
+ }, dataArg.identity.userId);
237
+ return rule ? { success: true, rule } : { success: false, message: 'Rule not found' };
238
+ },
239
+ ),
240
+ );
241
+
242
+ adminRouter.addTypedHandler(
243
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteSecurityBlockRule>(
244
+ 'deleteSecurityBlockRule',
245
+ async (dataArg) => {
246
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
247
+ if (!manager) return { success: false, message: 'Security policy manager not initialized' };
248
+ const success = await manager.deleteBlockRule(dataArg.id, dataArg.identity.userId);
249
+ return { success, message: success ? undefined : 'Rule not found' };
250
+ },
251
+ ),
252
+ );
253
+
254
+ adminRouter.addTypedHandler(
255
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RefreshIpIntelligence>(
256
+ 'refreshIpIntelligence',
257
+ async (dataArg) => {
258
+ const manager = this.opsServerRef.dcRouterRef.securityPolicyManager;
259
+ if (!manager) return { success: false, message: 'Security policy manager not initialized' };
260
+ const record = await manager.refreshIpIntelligence(dataArg.ipAddress);
261
+ return record
262
+ ? { success: true, record }
263
+ : { success: false, message: 'IP address is invalid or not public' };
264
+ },
265
+ ),
266
+ );
160
267
  }
161
268
 
162
269
  private async collectSecurityMetrics(): Promise<{
@@ -2,6 +2,10 @@ import * as plugins from '../plugins.js';
2
2
  import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
3
3
  import { RemoteIngressEdgeDoc } from '../db/index.js';
4
4
 
5
+ interface IRemoteIngressFirewallConfig {
6
+ blockedIps?: string[];
7
+ }
8
+
5
9
  /**
6
10
  * Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
7
11
  */
@@ -31,6 +35,7 @@ function extractPorts(portRange: number | Array<number | { from: number; to: num
31
35
  export class RemoteIngressManager {
32
36
  private edges: Map<string, IRemoteIngress> = new Map();
33
37
  private routes: IDcRouterRouteConfig[] = [];
38
+ private firewallConfig?: IRemoteIngressFirewallConfig;
34
39
 
35
40
  constructor() {
36
41
  }
@@ -69,6 +74,13 @@ export class RemoteIngressManager {
69
74
  this.routes = routes;
70
75
  }
71
76
 
77
+ /**
78
+ * Set the full desired firewall snapshot pushed to all edges.
79
+ */
80
+ public setFirewallConfig(firewallConfig?: IRemoteIngressFirewallConfig): void {
81
+ this.firewallConfig = firewallConfig;
82
+ }
83
+
72
84
  /**
73
85
  * Derive listen ports for an edge from routes tagged with remoteIngress.enabled.
74
86
  * When a route specifies edgeFilter, only edges whose id or tags match get that route's ports.
@@ -305,8 +317,8 @@ export class RemoteIngressManager {
305
317
  * Get the list of allowed edges (enabled only) for the Rust hub.
306
318
  * Includes listenPortsUdp when routes with transport 'udp' or 'all' are present.
307
319
  */
308
- public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[] }> {
309
- const result: Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[] }> = [];
320
+ public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig }> {
321
+ const result: Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig }> = [];
310
322
  for (const edge of this.edges.values()) {
311
323
  if (edge.enabled) {
312
324
  const listenPortsUdp = this.getEffectiveListenPortsUdp(edge);
@@ -315,6 +327,7 @@ export class RemoteIngressManager {
315
327
  secret: edge.secret,
316
328
  listenPorts: this.getEffectiveListenPorts(edge),
317
329
  ...(listenPortsUdp.length > 0 ? { listenPortsUdp } : {}),
330
+ ...(this.firewallConfig ? { firewallConfig: this.firewallConfig } : {}),
318
331
  });
319
332
  }
320
333
  }