@serve.zone/dcrouter 13.22.0 → 13.23.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 (42) hide show
  1. package/dist_serve/bundle.js +2 -2
  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 +42 -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 +41 -0
  18. package/dist_ts/security/classes.security-policy-manager.js +283 -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 +64 -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/package.json +3 -3
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/classes.dcrouter.ts +91 -3
  33. package/ts/db/documents/classes.ip-intelligence.doc.ts +75 -0
  34. package/ts/db/documents/classes.security-block-rule.doc.ts +52 -0
  35. package/ts/db/documents/classes.security-policy-audit.doc.ts +33 -0
  36. package/ts/db/documents/index.ts +3 -0
  37. package/ts/monitoring/classes.metricsmanager.ts +2 -0
  38. package/ts/opsserver/handlers/security.handler.ts +69 -0
  39. package/ts/remoteingress/classes.remoteingress-manager.ts +15 -2
  40. package/ts/security/classes.security-policy-manager.ts +315 -0
  41. package/ts/security/index.ts +7 -1
  42. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -0,0 +1,283 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import { IpIntelligenceDoc, SecurityBlockRuleDoc, SecurityPolicyAuditDoc } from '../db/index.js';
4
+ export class SecurityPolicyManager {
5
+ smartNetwork = new plugins.smartnetwork.SmartNetwork({
6
+ cacheTtl: 24 * 60 * 60 * 1000,
7
+ });
8
+ intelligenceRefreshMs;
9
+ inFlightObservations = new Set();
10
+ onPolicyChanged;
11
+ constructor(options = {}) {
12
+ this.intelligenceRefreshMs = options.intelligenceRefreshMs ?? 24 * 60 * 60 * 1000;
13
+ this.onPolicyChanged = options.onPolicyChanged;
14
+ }
15
+ async start() {
16
+ logger.log('info', 'SecurityPolicyManager started');
17
+ }
18
+ async stop() {
19
+ await this.smartNetwork.stop();
20
+ }
21
+ async observeIps(ips) {
22
+ const uniqueIps = [...new Set(ips.map((ip) => this.normalizeIp(ip)).filter(Boolean))];
23
+ await Promise.allSettled(uniqueIps.map((ip) => this.observeIp(ip)));
24
+ }
25
+ async observeIp(ipAddress) {
26
+ const ip = this.normalizeIp(ipAddress);
27
+ if (!ip || !this.isPublicIp(ip) || this.inFlightObservations.has(ip)) {
28
+ return;
29
+ }
30
+ this.inFlightObservations.add(ip);
31
+ try {
32
+ const now = Date.now();
33
+ let doc = await IpIntelligenceDoc.findByIp(ip);
34
+ if (doc && now - doc.updatedAt < this.intelligenceRefreshMs) {
35
+ if (now - doc.lastSeenAt > 60_000) {
36
+ doc.lastSeenAt = now;
37
+ doc.seenCount = (doc.seenCount || 0) + 1;
38
+ await doc.save();
39
+ }
40
+ return;
41
+ }
42
+ const intelligence = await this.smartNetwork.getIpIntelligence(ip);
43
+ if (!doc) {
44
+ doc = new IpIntelligenceDoc();
45
+ doc.ipAddress = ip;
46
+ doc.firstSeenAt = now;
47
+ }
48
+ Object.assign(doc, intelligence);
49
+ doc.lastSeenAt = now;
50
+ doc.updatedAt = now;
51
+ doc.seenCount = (doc.seenCount || 0) + 1;
52
+ await doc.save();
53
+ if (await this.matchesAnyReactiveRule(doc)) {
54
+ await this.notifyPolicyChanged();
55
+ }
56
+ }
57
+ catch (err) {
58
+ logger.log('warn', `Failed to enrich IP ${ip}: ${err.message}`);
59
+ }
60
+ finally {
61
+ this.inFlightObservations.delete(ip);
62
+ }
63
+ }
64
+ async listBlockRules() {
65
+ return (await SecurityBlockRuleDoc.findAll()).map((doc) => this.ruleFromDoc(doc));
66
+ }
67
+ async listIpIntelligence() {
68
+ return (await IpIntelligenceDoc.findAll()).map((doc) => ({
69
+ ipAddress: doc.ipAddress,
70
+ asn: doc.asn,
71
+ asnOrg: doc.asnOrg,
72
+ registrantOrg: doc.registrantOrg,
73
+ registrantCountry: doc.registrantCountry,
74
+ networkRange: doc.networkRange,
75
+ abuseContact: doc.abuseContact,
76
+ country: doc.country,
77
+ countryCode: doc.countryCode,
78
+ city: doc.city,
79
+ latitude: doc.latitude,
80
+ longitude: doc.longitude,
81
+ accuracyRadius: doc.accuracyRadius,
82
+ timezone: doc.timezone,
83
+ firstSeenAt: doc.firstSeenAt,
84
+ lastSeenAt: doc.lastSeenAt,
85
+ updatedAt: doc.updatedAt,
86
+ seenCount: doc.seenCount,
87
+ }));
88
+ }
89
+ async createBlockRule(input, actor = 'system') {
90
+ const now = Date.now();
91
+ const doc = new SecurityBlockRuleDoc();
92
+ doc.id = plugins.uuid.v4();
93
+ doc.type = input.type;
94
+ doc.value = input.value.trim();
95
+ doc.matchMode = input.matchMode;
96
+ doc.reason = input.reason;
97
+ doc.enabled = input.enabled ?? true;
98
+ doc.createdAt = now;
99
+ doc.updatedAt = now;
100
+ doc.createdBy = actor;
101
+ await doc.save();
102
+ await this.writeAudit('createBlockRule', actor, { rule: this.ruleFromDoc(doc) });
103
+ await this.notifyPolicyChanged();
104
+ return this.ruleFromDoc(doc);
105
+ }
106
+ async updateBlockRule(id, patch, actor = 'system') {
107
+ const doc = await SecurityBlockRuleDoc.findById(id);
108
+ if (!doc) {
109
+ return null;
110
+ }
111
+ if (patch.value !== undefined)
112
+ doc.value = patch.value.trim();
113
+ if (patch.matchMode !== undefined)
114
+ doc.matchMode = patch.matchMode;
115
+ if (patch.reason !== undefined)
116
+ doc.reason = patch.reason;
117
+ if (patch.enabled !== undefined)
118
+ doc.enabled = patch.enabled;
119
+ doc.updatedAt = Date.now();
120
+ await doc.save();
121
+ await this.writeAudit('updateBlockRule', actor, { id, patch });
122
+ await this.notifyPolicyChanged();
123
+ return this.ruleFromDoc(doc);
124
+ }
125
+ async deleteBlockRule(id, actor = 'system') {
126
+ const doc = await SecurityBlockRuleDoc.findById(id);
127
+ if (!doc) {
128
+ return false;
129
+ }
130
+ await doc.delete();
131
+ await this.writeAudit('deleteBlockRule', actor, { id });
132
+ await this.notifyPolicyChanged();
133
+ return true;
134
+ }
135
+ async compilePolicy() {
136
+ const rules = await SecurityBlockRuleDoc.findEnabled();
137
+ const intelligenceDocs = await IpIntelligenceDoc.findAll();
138
+ const blockedIps = new Set();
139
+ const blockedCidrs = new Set();
140
+ for (const rule of rules) {
141
+ const normalizedValue = rule.value.trim();
142
+ if (!normalizedValue)
143
+ continue;
144
+ if (rule.type === 'ip') {
145
+ const ip = this.normalizeIp(normalizedValue);
146
+ if (ip && plugins.net.isIP(ip))
147
+ blockedIps.add(ip);
148
+ continue;
149
+ }
150
+ if (rule.type === 'cidr') {
151
+ const cidr = this.normalizeCidr(normalizedValue);
152
+ if (cidr)
153
+ blockedCidrs.add(cidr);
154
+ continue;
155
+ }
156
+ for (const doc of intelligenceDocs) {
157
+ if (!this.ruleMatchesIntelligence(rule, doc))
158
+ continue;
159
+ const cidr = this.normalizeCidr(doc.networkRange || '');
160
+ if (cidr) {
161
+ blockedCidrs.add(cidr);
162
+ }
163
+ else if (this.normalizeIp(doc.ipAddress)) {
164
+ blockedIps.add(this.normalizeIp(doc.ipAddress));
165
+ }
166
+ }
167
+ }
168
+ return {
169
+ blockedIps: [...blockedIps].sort(),
170
+ blockedCidrs: [...blockedCidrs].sort(),
171
+ };
172
+ }
173
+ async compileSmartProxyPolicy() {
174
+ return await this.compilePolicy();
175
+ }
176
+ async compileRemoteIngressFirewall() {
177
+ const policy = await this.compilePolicy();
178
+ const blockedIps = [
179
+ ...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
180
+ ...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
181
+ ];
182
+ return blockedIps.length > 0 ? { blockedIps } : undefined;
183
+ }
184
+ async matchesAnyReactiveRule(doc) {
185
+ const rules = await SecurityBlockRuleDoc.findEnabled();
186
+ return rules.some((rule) => rule.type === 'asn' || rule.type === 'organization'
187
+ ? this.ruleMatchesIntelligence(rule, doc)
188
+ : false);
189
+ }
190
+ ruleMatchesIntelligence(rule, doc) {
191
+ const value = rule.value.trim().toLowerCase();
192
+ if (!value)
193
+ return false;
194
+ if (rule.type === 'asn') {
195
+ return String(doc.asn ?? '') === value.replace(/^as/i, '');
196
+ }
197
+ if (rule.type === 'organization') {
198
+ const candidates = [doc.asnOrg, doc.registrantOrg]
199
+ .filter(Boolean)
200
+ .map((candidate) => candidate.toLowerCase());
201
+ if (rule.matchMode === 'exact') {
202
+ return candidates.some((candidate) => candidate === value);
203
+ }
204
+ return candidates.some((candidate) => candidate.includes(value));
205
+ }
206
+ return false;
207
+ }
208
+ normalizeIp(ipAddress) {
209
+ const ip = ipAddress.trim();
210
+ if (ip.startsWith('::ffff:')) {
211
+ return ip.slice('::ffff:'.length);
212
+ }
213
+ return plugins.net.isIP(ip) ? ip : undefined;
214
+ }
215
+ normalizeCidr(value) {
216
+ const [rawIp, rawPrefix] = value.trim().split('/');
217
+ if (!rawIp || !rawPrefix)
218
+ return undefined;
219
+ const ip = this.normalizeIp(rawIp);
220
+ if (!ip)
221
+ return undefined;
222
+ const prefix = Number(rawPrefix);
223
+ const maxPrefix = plugins.net.isIP(ip) === 4 ? 32 : 128;
224
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > maxPrefix)
225
+ return undefined;
226
+ return `${ip}/${prefix}`;
227
+ }
228
+ isPublicIp(ip) {
229
+ const family = plugins.net.isIP(ip);
230
+ if (family === 4) {
231
+ const parts = ip.split('.').map((part) => Number(part));
232
+ const [a, b] = parts;
233
+ if (a === 10 || a === 127 || a === 0 || a >= 224)
234
+ return false;
235
+ if (a === 100 && b >= 64 && b <= 127)
236
+ return false;
237
+ if (a === 169 && b === 254)
238
+ return false;
239
+ if (a === 172 && b >= 16 && b <= 31)
240
+ return false;
241
+ if (a === 192 && b === 168)
242
+ return false;
243
+ return true;
244
+ }
245
+ if (family === 6) {
246
+ const lower = ip.toLowerCase();
247
+ if (lower === '::1' || lower === '::')
248
+ return false;
249
+ if (lower.startsWith('fe80:') || lower.startsWith('fc') || lower.startsWith('fd'))
250
+ return false;
251
+ return true;
252
+ }
253
+ return false;
254
+ }
255
+ ruleFromDoc(doc) {
256
+ return {
257
+ id: doc.id,
258
+ type: doc.type,
259
+ value: doc.value,
260
+ matchMode: doc.matchMode,
261
+ enabled: doc.enabled,
262
+ reason: doc.reason,
263
+ createdAt: doc.createdAt,
264
+ updatedAt: doc.updatedAt,
265
+ createdBy: doc.createdBy,
266
+ };
267
+ }
268
+ async writeAudit(action, actor, details) {
269
+ const doc = new SecurityPolicyAuditDoc();
270
+ doc.id = plugins.uuid.v4();
271
+ doc.action = action;
272
+ doc.actor = actor;
273
+ doc.details = details;
274
+ doc.createdAt = Date.now();
275
+ await doc.save();
276
+ }
277
+ async notifyPolicyChanged() {
278
+ if (this.onPolicyChanged) {
279
+ await this.onPolicyChanged();
280
+ }
281
+ }
282
+ }
283
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zZWN1cml0eS1wb2xpY3ktbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3NlY3VyaXR5L2NsYXNzZXMuc2VjdXJpdHktcG9saWN5LW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsb0JBQW9CLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQWtCakcsTUFBTSxPQUFPLHFCQUFxQjtJQUNmLFlBQVksR0FBRyxJQUFJLE9BQU8sQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDO1FBQ3BFLFFBQVEsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJO0tBQzlCLENBQUMsQ0FBQztJQUNjLHFCQUFxQixDQUFTO0lBQzlCLG9CQUFvQixHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFDekMsZUFBZSxDQUE4QjtJQUU5RCxZQUFZLFVBQXlDLEVBQUU7UUFDckQsSUFBSSxDQUFDLHFCQUFxQixHQUFHLE9BQU8sQ0FBQyxxQkFBcUIsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7UUFDbEYsSUFBSSxDQUFDLGVBQWUsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDO0lBQ2pELENBQUM7SUFFTSxLQUFLLENBQUMsS0FBSztRQUNoQixNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSwrQkFBK0IsQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFFTSxLQUFLLENBQUMsSUFBSTtRQUNmLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNqQyxDQUFDO0lBRU0sS0FBSyxDQUFDLFVBQVUsQ0FBQyxHQUFhO1FBQ25DLE1BQU0sU0FBUyxH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
@@ -1,3 +1,4 @@
1
1
  export { SecurityLogger, SecurityLogLevel, SecurityEventType, type ISecurityEvent } from './classes.securitylogger.js';
2
2
  export { IPReputationChecker, ReputationThreshold, IPType, type IReputationResult, type IIPReputationOptions } from './classes.ipreputationchecker.js';
3
3
  export { ContentScanner, ThreatCategory, type IScanResult, type IContentScannerOptions } from './classes.contentscanner.js';
4
+ export { SecurityPolicyManager, type ISecurityPolicyManagerOptions, type IRemoteIngressFirewallSnapshot, } from './classes.security-policy-manager.js';
@@ -1,4 +1,5 @@
1
1
  export { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
2
2
  export { IPReputationChecker, ReputationThreshold, IPType } from './classes.ipreputationchecker.js';
3
3
  export { ContentScanner, ThreatCategory } from './classes.contentscanner.js';
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDIn0=
4
+ export { SecurityPolicyManager, } from './classes.security-policy-manager.js';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDO0FBRXJDLE9BQU8sRUFDTCxxQkFBcUIsR0FHdEIsTUFBTSxzQ0FBc0MsQ0FBQyJ9
@@ -9,3 +9,4 @@ export * from './domain.js';
9
9
  export * from './dns-record.js';
10
10
  export * from './acme-config.js';
11
11
  export * from './email-domain.js';
12
+ export * from './security-policy.js';
@@ -9,4 +9,5 @@ export * from './domain.js';
9
9
  export * from './dns-record.js';
10
10
  export * from './acme-config.js';
11
11
  export * from './email-domain.js';
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19pbnRlcmZhY2VzL2RhdGEvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxtQkFBbUIsQ0FBQyJ9
12
+ export * from './security-policy.js';
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19pbnRlcmZhY2VzL2RhdGEvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLHNCQUFzQixDQUFDIn0=
@@ -0,0 +1,32 @@
1
+ import type { IIpIntelligenceResult } from '@push.rocks/smartnetwork';
2
+ export type TSecurityBlockRuleType = 'ip' | 'cidr' | 'asn' | 'organization';
3
+ export type TSecurityBlockRuleMatchMode = 'exact' | 'contains';
4
+ export interface IIpIntelligenceRecord extends IIpIntelligenceResult {
5
+ ipAddress: string;
6
+ firstSeenAt: number;
7
+ lastSeenAt: number;
8
+ updatedAt: number;
9
+ seenCount: number;
10
+ }
11
+ export interface ISecurityBlockRule {
12
+ id: string;
13
+ type: TSecurityBlockRuleType;
14
+ value: string;
15
+ matchMode?: TSecurityBlockRuleMatchMode;
16
+ enabled: boolean;
17
+ reason?: string;
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ createdBy: string;
21
+ }
22
+ export interface ISecurityCompiledPolicy {
23
+ blockedIps: string[];
24
+ blockedCidrs: string[];
25
+ }
26
+ export interface ISecurityPolicyAuditEvent {
27
+ id: string;
28
+ action: string;
29
+ actor: string;
30
+ details: Record<string, unknown>;
31
+ createdAt: number;
32
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJpdHktcG9saWN5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfaW50ZXJmYWNlcy9kYXRhL3NlY3VyaXR5LXBvbGljeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -19,3 +19,4 @@ export * from './domains.js';
19
19
  export * from './dns-records.js';
20
20
  export * from './acme-config.js';
21
21
  export * from './email-domains.js';
22
+ export * from './security-policy.js';
@@ -19,4 +19,5 @@ export * from './domains.js';
19
19
  export * from './dns-records.js';
20
20
  export * from './acme-config.js';
21
21
  export * from './email-domains.js';
22
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19pbnRlcmZhY2VzL3JlcXVlc3RzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsb0JBQW9CLENBQUMifQ==
22
+ export * from './security-policy.js';
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19pbnRlcmZhY2VzL3JlcXVlc3RzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxzQkFBc0IsQ0FBQyJ9
@@ -0,0 +1,64 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type * as authInterfaces from '../data/auth.js';
3
+ import type { IIpIntelligenceRecord, ISecurityBlockRule, TSecurityBlockRuleMatchMode, TSecurityBlockRuleType } from '../data/security-policy.js';
4
+ export interface IReq_ListSecurityBlockRules extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_ListSecurityBlockRules> {
5
+ method: 'listSecurityBlockRules';
6
+ request: {
7
+ identity: authInterfaces.IIdentity;
8
+ };
9
+ response: {
10
+ rules: ISecurityBlockRule[];
11
+ };
12
+ }
13
+ export interface IReq_CreateSecurityBlockRule extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_CreateSecurityBlockRule> {
14
+ method: 'createSecurityBlockRule';
15
+ request: {
16
+ identity: authInterfaces.IIdentity;
17
+ type: TSecurityBlockRuleType;
18
+ value: string;
19
+ matchMode?: TSecurityBlockRuleMatchMode;
20
+ reason?: string;
21
+ enabled?: boolean;
22
+ };
23
+ response: {
24
+ success: boolean;
25
+ rule?: ISecurityBlockRule;
26
+ message?: string;
27
+ };
28
+ }
29
+ export interface IReq_UpdateSecurityBlockRule extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_UpdateSecurityBlockRule> {
30
+ method: 'updateSecurityBlockRule';
31
+ request: {
32
+ identity: authInterfaces.IIdentity;
33
+ id: string;
34
+ value?: string;
35
+ matchMode?: TSecurityBlockRuleMatchMode;
36
+ reason?: string;
37
+ enabled?: boolean;
38
+ };
39
+ response: {
40
+ success: boolean;
41
+ rule?: ISecurityBlockRule;
42
+ message?: string;
43
+ };
44
+ }
45
+ export interface IReq_DeleteSecurityBlockRule extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_DeleteSecurityBlockRule> {
46
+ method: 'deleteSecurityBlockRule';
47
+ request: {
48
+ identity: authInterfaces.IIdentity;
49
+ id: string;
50
+ };
51
+ response: {
52
+ success: boolean;
53
+ message?: string;
54
+ };
55
+ }
56
+ export interface IReq_ListIpIntelligence extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_ListIpIntelligence> {
57
+ method: 'listIpIntelligence';
58
+ request: {
59
+ identity: authInterfaces.IIdentity;
60
+ };
61
+ response: {
62
+ records: IIpIntelligenceRecord[];
63
+ };
64
+ }
@@ -0,0 +1,2 @@
1
+ import * as plugins from '../plugins.js';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJpdHktcG9saWN5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfaW50ZXJmYWNlcy9yZXF1ZXN0cy9zZWN1cml0eS1wb2xpY3kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUMifQ==
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.22.0',
6
+ version: '13.23.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxTQUFTO0lBQ2xCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "13.22.0",
4
+ "version": "13.23.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -54,7 +54,7 @@
54
54
  "@push.rocks/smartnetwork": "^4.6.0",
55
55
  "@push.rocks/smartpath": "^6.0.0",
56
56
  "@push.rocks/smartpromise": "^4.2.3",
57
- "@push.rocks/smartproxy": "^27.8.2",
57
+ "@push.rocks/smartproxy": "^27.9.0",
58
58
  "@push.rocks/smartradius": "^1.1.1",
59
59
  "@push.rocks/smartrequest": "^5.0.1",
60
60
  "@push.rocks/smartrx": "^3.0.10",
@@ -64,7 +64,7 @@
64
64
  "@push.rocks/taskbuffer": "^8.0.2",
65
65
  "@serve.zone/catalog": "^2.12.4",
66
66
  "@serve.zone/interfaces": "^5.4.3",
67
- "@serve.zone/remoteingress": "^4.17.0",
67
+ "@serve.zone/remoteingress": "^4.17.1",
68
68
  "@tsclass/tsclass": "^9.5.0",
69
69
  "@types/qrcode": "^1.5.6",
70
70
  "lru-cache": "^11.3.5",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.22.0',
6
+ version: '13.23.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -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