@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.
- package/dist_serve/bundle.js +2 -2
- 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 +42 -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 +41 -0
- package/dist_ts/security/classes.security-policy-manager.js +283 -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 +64 -0
- package/dist_ts_interfaces/requests/security-policy.js +2 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- 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 +69 -0
- package/ts/remoteingress/classes.remoteingress-manager.ts +15 -2
- package/ts/security/classes.security-policy-manager.ts +315 -0
- package/ts/security/index.ts +7 -1
- 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
|
-
|
|
4
|
+
export { SecurityPolicyManager, } from './classes.security-policy-manager.js';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDO0FBRXJDLE9BQU8sRUFDTCxxQkFBcUIsR0FHdEIsTUFBTSxzQ0FBc0MsQ0FBQyJ9
|
|
@@ -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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/ts/00_commitinfo_data.ts
CHANGED
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
|