@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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import { IpIntelligenceDoc, SecurityBlockRuleDoc, SecurityPolicyAuditDoc } from '../db/index.js';
|
|
4
|
+
import type {
|
|
5
|
+
IIpIntelligenceRecord,
|
|
6
|
+
ISecurityBlockRule,
|
|
7
|
+
ISecurityCompiledPolicy,
|
|
8
|
+
ISecurityPolicyAuditEvent,
|
|
9
|
+
TSecurityBlockRuleMatchMode,
|
|
10
|
+
TSecurityBlockRuleType,
|
|
11
|
+
} from '../../ts_interfaces/data/security-policy.js';
|
|
12
|
+
|
|
13
|
+
export interface ISecurityPolicyManagerOptions {
|
|
14
|
+
intelligenceRefreshMs?: number;
|
|
15
|
+
onPolicyChanged?: () => void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IRemoteIngressFirewallSnapshot {
|
|
19
|
+
blockedIps?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SecurityPolicyManager {
|
|
23
|
+
private readonly smartNetwork = new plugins.smartnetwork.SmartNetwork({
|
|
24
|
+
cacheTtl: 24 * 60 * 60 * 1000,
|
|
25
|
+
});
|
|
26
|
+
private readonly intelligenceRefreshMs: number;
|
|
27
|
+
private readonly inFlightObservations = new Set<string>();
|
|
28
|
+
private readonly onPolicyChanged?: () => void | Promise<void>;
|
|
29
|
+
|
|
30
|
+
constructor(options: ISecurityPolicyManagerOptions = {}) {
|
|
31
|
+
this.intelligenceRefreshMs = options.intelligenceRefreshMs ?? 24 * 60 * 60 * 1000;
|
|
32
|
+
this.onPolicyChanged = options.onPolicyChanged;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async start(): Promise<void> {
|
|
36
|
+
logger.log('info', 'SecurityPolicyManager started');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async stop(): Promise<void> {
|
|
40
|
+
await this.smartNetwork.stop();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async observeIps(ips: string[]): Promise<void> {
|
|
44
|
+
const uniqueIps = [...new Set(ips.map((ip) => this.normalizeIp(ip)).filter(Boolean) as string[])];
|
|
45
|
+
await Promise.allSettled(uniqueIps.map((ip) => this.observeIp(ip)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async observeIp(ipAddress: string, options: { force?: boolean } = {}): Promise<void> {
|
|
49
|
+
const ip = this.normalizeIp(ipAddress);
|
|
50
|
+
if (!ip || !this.isPublicIp(ip) || this.inFlightObservations.has(ip)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.inFlightObservations.add(ip);
|
|
55
|
+
try {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
let doc = await IpIntelligenceDoc.findByIp(ip);
|
|
58
|
+
if (doc && !options.force && now - doc.updatedAt < this.intelligenceRefreshMs) {
|
|
59
|
+
if (now - doc.lastSeenAt > 60_000) {
|
|
60
|
+
doc.lastSeenAt = now;
|
|
61
|
+
doc.seenCount = (doc.seenCount || 0) + 1;
|
|
62
|
+
await doc.save();
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const intelligence = await this.smartNetwork.getIpIntelligence(ip);
|
|
68
|
+
if (!doc) {
|
|
69
|
+
doc = new IpIntelligenceDoc();
|
|
70
|
+
doc.ipAddress = ip;
|
|
71
|
+
doc.firstSeenAt = now;
|
|
72
|
+
}
|
|
73
|
+
Object.assign(doc, intelligence);
|
|
74
|
+
doc.lastSeenAt = now;
|
|
75
|
+
doc.updatedAt = now;
|
|
76
|
+
doc.seenCount = (doc.seenCount || 0) + 1;
|
|
77
|
+
await doc.save();
|
|
78
|
+
|
|
79
|
+
if (await this.matchesAnyReactiveRule(doc)) {
|
|
80
|
+
await this.notifyPolicyChanged();
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.log('warn', `Failed to enrich IP ${ip}: ${(err as Error).message}`);
|
|
84
|
+
} finally {
|
|
85
|
+
this.inFlightObservations.delete(ip);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async listBlockRules(): Promise<ISecurityBlockRule[]> {
|
|
90
|
+
return (await SecurityBlockRuleDoc.findAll()).map((doc) => this.ruleFromDoc(doc));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async listIpIntelligence(): Promise<IIpIntelligenceRecord[]> {
|
|
94
|
+
return (await IpIntelligenceDoc.findAll()).map((doc) => this.intelligenceFromDoc(doc));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async refreshIpIntelligence(ipAddress: string): Promise<IIpIntelligenceRecord | null> {
|
|
98
|
+
const ip = this.normalizeIp(ipAddress);
|
|
99
|
+
if (!ip || !this.isPublicIp(ip)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
await this.observeIp(ip, { force: true });
|
|
103
|
+
const doc = await IpIntelligenceDoc.findByIp(ip);
|
|
104
|
+
return doc ? this.intelligenceFromDoc(doc) : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public async listAuditEvents(limit = 100): Promise<ISecurityPolicyAuditEvent[]> {
|
|
108
|
+
return (await SecurityPolicyAuditDoc.findRecent(limit)).map((doc) => ({
|
|
109
|
+
id: doc.id,
|
|
110
|
+
action: doc.action,
|
|
111
|
+
actor: doc.actor,
|
|
112
|
+
details: doc.details,
|
|
113
|
+
createdAt: doc.createdAt,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private intelligenceFromDoc(doc: IpIntelligenceDoc): IIpIntelligenceRecord {
|
|
118
|
+
return {
|
|
119
|
+
ipAddress: doc.ipAddress,
|
|
120
|
+
asn: doc.asn,
|
|
121
|
+
asnOrg: doc.asnOrg,
|
|
122
|
+
registrantOrg: doc.registrantOrg,
|
|
123
|
+
registrantCountry: doc.registrantCountry,
|
|
124
|
+
networkRange: doc.networkRange,
|
|
125
|
+
abuseContact: doc.abuseContact,
|
|
126
|
+
country: doc.country,
|
|
127
|
+
countryCode: doc.countryCode,
|
|
128
|
+
city: doc.city,
|
|
129
|
+
latitude: doc.latitude,
|
|
130
|
+
longitude: doc.longitude,
|
|
131
|
+
accuracyRadius: doc.accuracyRadius,
|
|
132
|
+
timezone: doc.timezone,
|
|
133
|
+
firstSeenAt: doc.firstSeenAt,
|
|
134
|
+
lastSeenAt: doc.lastSeenAt,
|
|
135
|
+
updatedAt: doc.updatedAt,
|
|
136
|
+
seenCount: doc.seenCount,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public async createBlockRule(input: {
|
|
141
|
+
type: TSecurityBlockRuleType;
|
|
142
|
+
value: string;
|
|
143
|
+
matchMode?: TSecurityBlockRuleMatchMode;
|
|
144
|
+
reason?: string;
|
|
145
|
+
enabled?: boolean;
|
|
146
|
+
}, actor = 'system'): Promise<ISecurityBlockRule> {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const doc = new SecurityBlockRuleDoc();
|
|
149
|
+
doc.id = plugins.uuid.v4();
|
|
150
|
+
doc.type = input.type;
|
|
151
|
+
doc.value = input.value.trim();
|
|
152
|
+
doc.matchMode = input.matchMode;
|
|
153
|
+
doc.reason = input.reason;
|
|
154
|
+
doc.enabled = input.enabled ?? true;
|
|
155
|
+
doc.createdAt = now;
|
|
156
|
+
doc.updatedAt = now;
|
|
157
|
+
doc.createdBy = actor;
|
|
158
|
+
await doc.save();
|
|
159
|
+
await this.writeAudit('createBlockRule', actor, { rule: this.ruleFromDoc(doc) });
|
|
160
|
+
await this.notifyPolicyChanged();
|
|
161
|
+
return this.ruleFromDoc(doc);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public async updateBlockRule(id: string, patch: Partial<Pick<ISecurityBlockRule, 'value' | 'matchMode' | 'reason' | 'enabled'>>, actor = 'system'): Promise<ISecurityBlockRule | null> {
|
|
165
|
+
const doc = await SecurityBlockRuleDoc.findById(id);
|
|
166
|
+
if (!doc) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (patch.value !== undefined) doc.value = patch.value.trim();
|
|
170
|
+
if (patch.matchMode !== undefined) doc.matchMode = patch.matchMode;
|
|
171
|
+
if (patch.reason !== undefined) doc.reason = patch.reason;
|
|
172
|
+
if (patch.enabled !== undefined) doc.enabled = patch.enabled;
|
|
173
|
+
doc.updatedAt = Date.now();
|
|
174
|
+
await doc.save();
|
|
175
|
+
await this.writeAudit('updateBlockRule', actor, { id, patch });
|
|
176
|
+
await this.notifyPolicyChanged();
|
|
177
|
+
return this.ruleFromDoc(doc);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public async deleteBlockRule(id: string, actor = 'system'): Promise<boolean> {
|
|
181
|
+
const doc = await SecurityBlockRuleDoc.findById(id);
|
|
182
|
+
if (!doc) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
await doc.delete();
|
|
186
|
+
await this.writeAudit('deleteBlockRule', actor, { id });
|
|
187
|
+
await this.notifyPolicyChanged();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public async compilePolicy(): Promise<ISecurityCompiledPolicy> {
|
|
192
|
+
const rules = await SecurityBlockRuleDoc.findEnabled();
|
|
193
|
+
const intelligenceDocs = await IpIntelligenceDoc.findAll();
|
|
194
|
+
const blockedIps = new Set<string>();
|
|
195
|
+
const blockedCidrs = new Set<string>();
|
|
196
|
+
|
|
197
|
+
for (const rule of rules) {
|
|
198
|
+
const normalizedValue = rule.value.trim();
|
|
199
|
+
if (!normalizedValue) continue;
|
|
200
|
+
|
|
201
|
+
if (rule.type === 'ip') {
|
|
202
|
+
const ip = this.normalizeIp(normalizedValue);
|
|
203
|
+
if (ip && plugins.net.isIP(ip)) blockedIps.add(ip);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (rule.type === 'cidr') {
|
|
208
|
+
const cidr = this.normalizeCidr(normalizedValue);
|
|
209
|
+
if (cidr) blockedCidrs.add(cidr);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const doc of intelligenceDocs) {
|
|
214
|
+
if (!this.ruleMatchesIntelligence(rule, doc)) continue;
|
|
215
|
+
const cidr = this.normalizeCidr(doc.networkRange || '');
|
|
216
|
+
if (cidr) {
|
|
217
|
+
blockedCidrs.add(cidr);
|
|
218
|
+
} else if (this.normalizeIp(doc.ipAddress)) {
|
|
219
|
+
blockedIps.add(this.normalizeIp(doc.ipAddress)!);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
blockedIps: [...blockedIps].sort(),
|
|
226
|
+
blockedCidrs: [...blockedCidrs].sort(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public async compileSmartProxyPolicy(): Promise<ISecurityCompiledPolicy> {
|
|
231
|
+
return await this.compilePolicy();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot | undefined> {
|
|
235
|
+
const policy = await this.compilePolicy();
|
|
236
|
+
const blockedIps = [
|
|
237
|
+
...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
|
|
238
|
+
...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
|
|
239
|
+
];
|
|
240
|
+
return blockedIps.length > 0 ? { blockedIps } : undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
|
|
244
|
+
const rules = await SecurityBlockRuleDoc.findEnabled();
|
|
245
|
+
return rules.some((rule) => rule.type === 'asn' || rule.type === 'organization'
|
|
246
|
+
? this.ruleMatchesIntelligence(rule, doc)
|
|
247
|
+
: false);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private ruleMatchesIntelligence(rule: SecurityBlockRuleDoc, doc: IpIntelligenceDoc): boolean {
|
|
251
|
+
const value = rule.value.trim().toLowerCase();
|
|
252
|
+
if (!value) return false;
|
|
253
|
+
|
|
254
|
+
if (rule.type === 'asn') {
|
|
255
|
+
return String(doc.asn ?? '') === value.replace(/^as/i, '');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (rule.type === 'organization') {
|
|
259
|
+
const candidates = [doc.asnOrg, doc.registrantOrg]
|
|
260
|
+
.filter(Boolean)
|
|
261
|
+
.map((candidate) => candidate!.toLowerCase());
|
|
262
|
+
if (rule.matchMode === 'exact') {
|
|
263
|
+
return candidates.some((candidate) => candidate === value);
|
|
264
|
+
}
|
|
265
|
+
return candidates.some((candidate) => candidate.includes(value));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private normalizeIp(ipAddress: string): string | undefined {
|
|
272
|
+
const ip = ipAddress.trim();
|
|
273
|
+
if (ip.startsWith('::ffff:')) {
|
|
274
|
+
return ip.slice('::ffff:'.length);
|
|
275
|
+
}
|
|
276
|
+
return plugins.net.isIP(ip) ? ip : undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private normalizeCidr(value: string): string | undefined {
|
|
280
|
+
const [rawIp, rawPrefix] = value.trim().split('/');
|
|
281
|
+
if (!rawIp || !rawPrefix) return undefined;
|
|
282
|
+
const ip = this.normalizeIp(rawIp);
|
|
283
|
+
if (!ip) return undefined;
|
|
284
|
+
const prefix = Number(rawPrefix);
|
|
285
|
+
const maxPrefix = plugins.net.isIP(ip) === 4 ? 32 : 128;
|
|
286
|
+
if (!Number.isInteger(prefix) || prefix < 0 || prefix > maxPrefix) return undefined;
|
|
287
|
+
return `${ip}/${prefix}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private isPublicIp(ip: string): boolean {
|
|
291
|
+
const family = plugins.net.isIP(ip);
|
|
292
|
+
if (family === 4) {
|
|
293
|
+
const parts = ip.split('.').map((part) => Number(part));
|
|
294
|
+
const [a, b] = parts;
|
|
295
|
+
if (a === 10 || a === 127 || a === 0 || a >= 224) return false;
|
|
296
|
+
if (a === 100 && b >= 64 && b <= 127) return false;
|
|
297
|
+
if (a === 169 && b === 254) return false;
|
|
298
|
+
if (a === 172 && b >= 16 && b <= 31) return false;
|
|
299
|
+
if (a === 192 && b === 168) return false;
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (family === 6) {
|
|
303
|
+
const lower = ip.toLowerCase();
|
|
304
|
+
if (lower === '::1' || lower === '::') return false;
|
|
305
|
+
if (lower.startsWith('fe80:') || lower.startsWith('fc') || lower.startsWith('fd')) return false;
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private ruleFromDoc(doc: SecurityBlockRuleDoc): ISecurityBlockRule {
|
|
312
|
+
return {
|
|
313
|
+
id: doc.id,
|
|
314
|
+
type: doc.type,
|
|
315
|
+
value: doc.value,
|
|
316
|
+
matchMode: doc.matchMode,
|
|
317
|
+
enabled: doc.enabled,
|
|
318
|
+
reason: doc.reason,
|
|
319
|
+
createdAt: doc.createdAt,
|
|
320
|
+
updatedAt: doc.updatedAt,
|
|
321
|
+
createdBy: doc.createdBy,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private async writeAudit(action: string, actor: string, details: Record<string, unknown>): Promise<void> {
|
|
326
|
+
const doc = new SecurityPolicyAuditDoc();
|
|
327
|
+
doc.id = plugins.uuid.v4();
|
|
328
|
+
doc.action = action;
|
|
329
|
+
doc.actor = actor;
|
|
330
|
+
doc.details = details;
|
|
331
|
+
doc.createdAt = Date.now();
|
|
332
|
+
await doc.save();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private async notifyPolicyChanged(): Promise<void> {
|
|
336
|
+
if (this.onPolicyChanged) {
|
|
337
|
+
await this.onPolicyChanged();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
package/ts/security/index.ts
CHANGED
|
@@ -18,4 +18,10 @@ export {
|
|
|
18
18
|
ThreatCategory,
|
|
19
19
|
type IScanResult,
|
|
20
20
|
type IContentScannerOptions
|
|
21
|
-
} from './classes.contentscanner.js';
|
|
21
|
+
} from './classes.contentscanner.js';
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
SecurityPolicyManager,
|
|
25
|
+
type ISecurityPolicyManagerOptions,
|
|
26
|
+
type IRemoteIngressFirewallSnapshot,
|
|
27
|
+
} from './classes.security-policy-manager.js';
|
package/ts_web/appstate.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface INetworkState {
|
|
|
54
54
|
topIPs: Array<{ ip: string; count: number }>;
|
|
55
55
|
topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
|
|
56
56
|
throughputByIP: Array<{ ip: string; in: number; out: number }>;
|
|
57
|
+
ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
|
|
57
58
|
domainActivity: interfaces.data.IDomainActivity[];
|
|
58
59
|
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
|
|
59
60
|
requestsPerSecond: number;
|
|
@@ -66,6 +67,16 @@ export interface INetworkState {
|
|
|
66
67
|
error: string | null;
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
export interface ISecurityPolicyState {
|
|
71
|
+
rules: interfaces.data.ISecurityBlockRule[];
|
|
72
|
+
ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
|
|
73
|
+
compiledPolicy: interfaces.data.ISecurityCompiledPolicy | null;
|
|
74
|
+
auditEvents: interfaces.data.ISecurityPolicyAuditEvent[];
|
|
75
|
+
isLoading: boolean;
|
|
76
|
+
error: string | null;
|
|
77
|
+
lastUpdated: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
export interface ICertificateState {
|
|
70
81
|
certificates: interfaces.requests.ICertificateInfo[];
|
|
71
82
|
summary: { total: number; valid: number; expiring: number; expired: number; failed: number; unknown: number };
|
|
@@ -164,6 +175,7 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|
|
164
175
|
topIPs: [],
|
|
165
176
|
topIPsByBandwidth: [],
|
|
166
177
|
throughputByIP: [],
|
|
178
|
+
ipIntelligence: [],
|
|
167
179
|
domainActivity: [],
|
|
168
180
|
throughputHistory: [],
|
|
169
181
|
requestsPerSecond: 0,
|
|
@@ -178,6 +190,20 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|
|
178
190
|
'soft'
|
|
179
191
|
);
|
|
180
192
|
|
|
193
|
+
export const securityPolicyStatePart = await appState.getStatePart<ISecurityPolicyState>(
|
|
194
|
+
'securityPolicy',
|
|
195
|
+
{
|
|
196
|
+
rules: [],
|
|
197
|
+
ipIntelligence: [],
|
|
198
|
+
compiledPolicy: null,
|
|
199
|
+
auditEvents: [],
|
|
200
|
+
isLoading: false,
|
|
201
|
+
error: null,
|
|
202
|
+
lastUpdated: 0,
|
|
203
|
+
},
|
|
204
|
+
'soft',
|
|
205
|
+
);
|
|
206
|
+
|
|
181
207
|
export const emailOpsStatePart = await appState.getStatePart<IEmailOpsState>(
|
|
182
208
|
'emailOps',
|
|
183
209
|
{
|
|
@@ -517,9 +543,18 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
517
543
|
interfaces.requests.IReq_GetNetworkStats
|
|
518
544
|
>('/typedrequest', 'getNetworkStats');
|
|
519
545
|
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
546
|
+
const ipIntelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
547
|
+
interfaces.requests.IReq_ListIpIntelligence
|
|
548
|
+
>('/typedrequest', 'listIpIntelligence');
|
|
549
|
+
|
|
550
|
+
const [networkStatsResponse, ipIntelligenceResponse] = await Promise.all([
|
|
551
|
+
networkStatsRequest.fire({
|
|
552
|
+
identity: context.identity,
|
|
553
|
+
}),
|
|
554
|
+
ipIntelligenceRequest.fire({
|
|
555
|
+
identity: context.identity,
|
|
556
|
+
}),
|
|
557
|
+
]);
|
|
523
558
|
|
|
524
559
|
// Use the connections data for the connection list
|
|
525
560
|
// and network stats for throughput and IP analytics
|
|
@@ -561,6 +596,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
561
596
|
topIPs: networkStatsResponse.topIPs || [],
|
|
562
597
|
topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
|
|
563
598
|
throughputByIP: networkStatsResponse.throughputByIP || [],
|
|
599
|
+
ipIntelligence: ipIntelligenceResponse.records || [],
|
|
564
600
|
domainActivity: networkStatsResponse.domainActivity || [],
|
|
565
601
|
throughputHistory: networkStatsResponse.throughputHistory || [],
|
|
566
602
|
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
|
|
@@ -582,6 +618,182 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
582
618
|
}
|
|
583
619
|
});
|
|
584
620
|
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// Security Policy Actions
|
|
623
|
+
// ============================================================================
|
|
624
|
+
|
|
625
|
+
export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
|
|
626
|
+
async (statePartArg): Promise<ISecurityPolicyState> => {
|
|
627
|
+
const context = getActionContext();
|
|
628
|
+
const currentState = statePartArg.getState()!;
|
|
629
|
+
if (!context.identity) return currentState;
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
const rulesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
633
|
+
interfaces.requests.IReq_ListSecurityBlockRules
|
|
634
|
+
>('/typedrequest', 'listSecurityBlockRules');
|
|
635
|
+
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
636
|
+
interfaces.requests.IReq_ListIpIntelligence
|
|
637
|
+
>('/typedrequest', 'listIpIntelligence');
|
|
638
|
+
const compiledPolicyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
639
|
+
interfaces.requests.IReq_GetCompiledSecurityPolicy
|
|
640
|
+
>('/typedrequest', 'getCompiledSecurityPolicy');
|
|
641
|
+
const auditRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
642
|
+
interfaces.requests.IReq_ListSecurityPolicyAudit
|
|
643
|
+
>('/typedrequest', 'listSecurityPolicyAudit');
|
|
644
|
+
|
|
645
|
+
const [rulesResponse, intelligenceResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
|
|
646
|
+
rulesRequest.fire({ identity: context.identity }),
|
|
647
|
+
intelligenceRequest.fire({ identity: context.identity }),
|
|
648
|
+
compiledPolicyRequest.fire({ identity: context.identity }),
|
|
649
|
+
auditRequest.fire({ identity: context.identity, limit: 100 }),
|
|
650
|
+
]);
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
rules: rulesResponse.rules || [],
|
|
654
|
+
ipIntelligence: intelligenceResponse.records || [],
|
|
655
|
+
compiledPolicy: compiledPolicyResponse.policy,
|
|
656
|
+
auditEvents: auditResponse.events || [],
|
|
657
|
+
isLoading: false,
|
|
658
|
+
error: null,
|
|
659
|
+
lastUpdated: Date.now(),
|
|
660
|
+
};
|
|
661
|
+
} catch (error: unknown) {
|
|
662
|
+
return {
|
|
663
|
+
...currentState,
|
|
664
|
+
isLoading: false,
|
|
665
|
+
error: error instanceof Error ? error.message : 'Failed to fetch security policy',
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
export const createSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
|
|
672
|
+
type: interfaces.data.TSecurityBlockRuleType;
|
|
673
|
+
value: string;
|
|
674
|
+
matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
|
|
675
|
+
reason?: string;
|
|
676
|
+
enabled?: boolean;
|
|
677
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
|
|
678
|
+
const context = getActionContext();
|
|
679
|
+
const currentState = statePartArg.getState()!;
|
|
680
|
+
if (!context.identity) return currentState;
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
684
|
+
interfaces.requests.IReq_CreateSecurityBlockRule
|
|
685
|
+
>('/typedrequest', 'createSecurityBlockRule');
|
|
686
|
+
|
|
687
|
+
const response = await request.fire({
|
|
688
|
+
identity: context.identity,
|
|
689
|
+
type: dataArg.type,
|
|
690
|
+
value: dataArg.value,
|
|
691
|
+
matchMode: dataArg.matchMode,
|
|
692
|
+
reason: dataArg.reason,
|
|
693
|
+
enabled: dataArg.enabled,
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
if (!response.success) {
|
|
697
|
+
return { ...currentState, error: response.message || 'Failed to create security block rule' };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
|
|
701
|
+
} catch (error: unknown) {
|
|
702
|
+
return {
|
|
703
|
+
...currentState,
|
|
704
|
+
error: error instanceof Error ? error.message : 'Failed to create security block rule',
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
export const updateSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
|
|
710
|
+
id: string;
|
|
711
|
+
value?: string;
|
|
712
|
+
matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
|
|
713
|
+
reason?: string;
|
|
714
|
+
enabled?: boolean;
|
|
715
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
|
|
716
|
+
const context = getActionContext();
|
|
717
|
+
const currentState = statePartArg.getState()!;
|
|
718
|
+
if (!context.identity) return currentState;
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
722
|
+
interfaces.requests.IReq_UpdateSecurityBlockRule
|
|
723
|
+
>('/typedrequest', 'updateSecurityBlockRule');
|
|
724
|
+
|
|
725
|
+
const response = await request.fire({
|
|
726
|
+
identity: context.identity,
|
|
727
|
+
id: dataArg.id,
|
|
728
|
+
value: dataArg.value,
|
|
729
|
+
matchMode: dataArg.matchMode,
|
|
730
|
+
reason: dataArg.reason,
|
|
731
|
+
enabled: dataArg.enabled,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
if (!response.success) {
|
|
735
|
+
return { ...currentState, error: response.message || 'Failed to update security block rule' };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
|
|
739
|
+
} catch (error: unknown) {
|
|
740
|
+
return {
|
|
741
|
+
...currentState,
|
|
742
|
+
error: error instanceof Error ? error.message : 'Failed to update security block rule',
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
export const deleteSecurityBlockRuleAction = securityPolicyStatePart.createAction<string>(
|
|
748
|
+
async (statePartArg, ruleId, actionContext): Promise<ISecurityPolicyState> => {
|
|
749
|
+
const context = getActionContext();
|
|
750
|
+
const currentState = statePartArg.getState()!;
|
|
751
|
+
if (!context.identity) return currentState;
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
755
|
+
interfaces.requests.IReq_DeleteSecurityBlockRule
|
|
756
|
+
>('/typedrequest', 'deleteSecurityBlockRule');
|
|
757
|
+
|
|
758
|
+
const response = await request.fire({ identity: context.identity, id: ruleId });
|
|
759
|
+
if (!response.success) {
|
|
760
|
+
return { ...currentState, error: response.message || 'Failed to delete security block rule' };
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
|
|
764
|
+
} catch (error: unknown) {
|
|
765
|
+
return {
|
|
766
|
+
...currentState,
|
|
767
|
+
error: error instanceof Error ? error.message : 'Failed to delete security block rule',
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
export const refreshIpIntelligenceAction = securityPolicyStatePart.createAction<string>(
|
|
774
|
+
async (statePartArg, ipAddress, actionContext): Promise<ISecurityPolicyState> => {
|
|
775
|
+
const context = getActionContext();
|
|
776
|
+
const currentState = statePartArg.getState()!;
|
|
777
|
+
if (!context.identity) return currentState;
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
781
|
+
interfaces.requests.IReq_RefreshIpIntelligence
|
|
782
|
+
>('/typedrequest', 'refreshIpIntelligence');
|
|
783
|
+
const response = await request.fire({ identity: context.identity, ipAddress });
|
|
784
|
+
if (!response.success) {
|
|
785
|
+
return { ...currentState, error: response.message || 'Failed to refresh IP intelligence' };
|
|
786
|
+
}
|
|
787
|
+
return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
|
|
788
|
+
} catch (error: unknown) {
|
|
789
|
+
return {
|
|
790
|
+
...currentState,
|
|
791
|
+
error: error instanceof Error ? error.message : 'Failed to refresh IP intelligence',
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
);
|
|
796
|
+
|
|
585
797
|
// ============================================================================
|
|
586
798
|
// Email Operations Actions
|
|
587
799
|
// ============================================================================
|
|
@@ -2665,6 +2877,27 @@ async function dispatchCombinedRefreshActionInner() {
|
|
|
2665
2877
|
isLoading: false,
|
|
2666
2878
|
error: null,
|
|
2667
2879
|
});
|
|
2880
|
+
|
|
2881
|
+
try {
|
|
2882
|
+
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2883
|
+
interfaces.requests.IReq_ListIpIntelligence
|
|
2884
|
+
>('/typedrequest', 'listIpIntelligence');
|
|
2885
|
+
const intelligenceResponse = await intelligenceRequest.fire({ identity: context.identity });
|
|
2886
|
+
networkStatePart.setState({
|
|
2887
|
+
...networkStatePart.getState()!,
|
|
2888
|
+
ipIntelligence: intelligenceResponse.records || [],
|
|
2889
|
+
});
|
|
2890
|
+
} catch (error) {
|
|
2891
|
+
console.error('IP intelligence refresh failed:', error);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
if (currentView === 'security') {
|
|
2896
|
+
try {
|
|
2897
|
+
await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
|
|
2898
|
+
} catch (error) {
|
|
2899
|
+
console.error('Security policy refresh failed:', error);
|
|
2900
|
+
}
|
|
2668
2901
|
}
|
|
2669
2902
|
|
|
2670
2903
|
// Refresh certificate data if on Domains > Certificates subview
|