@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.
- package/dist_serve/bundle.js +952 -792
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +4 -0
- package/dist_ts/classes.dcrouter.js +69 -2
- package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +25 -0
- package/dist_ts/db/documents/classes.ip-intelligence.doc.js +175 -0
- package/dist_ts/db/documents/classes.security-block-rule.doc.d.ts +17 -0
- package/dist_ts/db/documents/classes.security-block-rule.doc.js +124 -0
- package/dist_ts/db/documents/classes.security-policy-audit.doc.d.ts +11 -0
- package/dist_ts/db/documents/classes.security-policy-audit.doc.js +95 -0
- package/dist_ts/db/documents/index.d.ts +3 -0
- package/dist_ts/db/documents/index.js +4 -1
- package/dist_ts/monitoring/classes.metricsmanager.js +2 -1
- package/dist_ts/opsserver/handlers/security.handler.js +63 -1
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +10 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +9 -1
- package/dist_ts/security/classes.security-policy-manager.d.ts +46 -0
- package/dist_ts/security/classes.security-policy-manager.js +304 -0
- package/dist_ts/security/index.d.ts +1 -0
- package/dist_ts/security/index.js +2 -1
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/security-policy.d.ts +32 -0
- package/dist_ts_interfaces/data/security-policy.js +2 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/security-policy.d.ts +95 -0
- package/dist_ts_interfaces/requests/security-policy.js +2 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +28 -0
- package/dist_ts_web/appstate.js +171 -4
- package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +9 -0
- package/dist_ts_web/elements/network/ops-view-network-activity.js +210 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +12 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +407 -52
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +91 -3
- package/ts/db/documents/classes.ip-intelligence.doc.ts +75 -0
- package/ts/db/documents/classes.security-block-rule.doc.ts +52 -0
- package/ts/db/documents/classes.security-policy-audit.doc.ts +33 -0
- package/ts/db/documents/index.ts +3 -0
- package/ts/monitoring/classes.metricsmanager.ts +2 -0
- package/ts/opsserver/handlers/security.handler.ts +107 -0
- package/ts/remoteingress/classes.remoteingress-manager.ts +15 -2
- package/ts/security/classes.security-policy-manager.ts +340 -0
- package/ts/security/index.ts +7 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +236 -3
- package/ts_web/elements/network/ops-view-network-activity.ts +219 -2
- package/ts_web/elements/security/ops-view-security-blocked.ts +414 -51
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -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
|
+
}
|
package/ts/db/documents/index.ts
CHANGED
|
@@ -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
|
}
|