@sentriflow/core 0.1.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/LICENSE +190 -0
- package/README.md +86 -0
- package/package.json +60 -0
- package/src/constants.ts +77 -0
- package/src/engine/RuleExecutor.ts +256 -0
- package/src/engine/Runner.ts +312 -0
- package/src/engine/SandboxedExecutor.ts +208 -0
- package/src/errors.ts +88 -0
- package/src/helpers/arista/helpers.ts +1220 -0
- package/src/helpers/arista/index.ts +12 -0
- package/src/helpers/aruba/helpers.ts +637 -0
- package/src/helpers/aruba/index.ts +13 -0
- package/src/helpers/cisco/helpers.ts +534 -0
- package/src/helpers/cisco/index.ts +11 -0
- package/src/helpers/common/helpers.ts +265 -0
- package/src/helpers/common/index.ts +5 -0
- package/src/helpers/common/validation.ts +280 -0
- package/src/helpers/cumulus/helpers.ts +676 -0
- package/src/helpers/cumulus/index.ts +12 -0
- package/src/helpers/extreme/helpers.ts +422 -0
- package/src/helpers/extreme/index.ts +12 -0
- package/src/helpers/fortinet/helpers.ts +892 -0
- package/src/helpers/fortinet/index.ts +12 -0
- package/src/helpers/huawei/helpers.ts +790 -0
- package/src/helpers/huawei/index.ts +11 -0
- package/src/helpers/index.ts +53 -0
- package/src/helpers/juniper/helpers.ts +756 -0
- package/src/helpers/juniper/index.ts +12 -0
- package/src/helpers/mikrotik/helpers.ts +722 -0
- package/src/helpers/mikrotik/index.ts +12 -0
- package/src/helpers/nokia/helpers.ts +856 -0
- package/src/helpers/nokia/index.ts +11 -0
- package/src/helpers/paloalto/helpers.ts +939 -0
- package/src/helpers/paloalto/index.ts +12 -0
- package/src/helpers/vyos/helpers.ts +429 -0
- package/src/helpers/vyos/index.ts +12 -0
- package/src/index.ts +30 -0
- package/src/json-rules/ExpressionEvaluator.ts +292 -0
- package/src/json-rules/HelperRegistry.ts +177 -0
- package/src/json-rules/JsonRuleCompiler.ts +339 -0
- package/src/json-rules/JsonRuleValidator.ts +371 -0
- package/src/json-rules/index.ts +97 -0
- package/src/json-rules/schema.json +350 -0
- package/src/json-rules/types.ts +303 -0
- package/src/pack-loader/PackLoader.ts +332 -0
- package/src/pack-loader/index.ts +17 -0
- package/src/pack-loader/types.ts +135 -0
- package/src/parser/IncrementalParser.ts +527 -0
- package/src/parser/Sanitizer.ts +104 -0
- package/src/parser/SchemaAwareParser.ts +504 -0
- package/src/parser/VendorSchema.ts +72 -0
- package/src/parser/vendors/arista-eos.ts +206 -0
- package/src/parser/vendors/aruba-aoscx.ts +123 -0
- package/src/parser/vendors/aruba-aosswitch.ts +113 -0
- package/src/parser/vendors/aruba-wlc.ts +173 -0
- package/src/parser/vendors/cisco-ios.ts +110 -0
- package/src/parser/vendors/cisco-nxos.ts +107 -0
- package/src/parser/vendors/cumulus-linux.ts +161 -0
- package/src/parser/vendors/extreme-exos.ts +154 -0
- package/src/parser/vendors/extreme-voss.ts +167 -0
- package/src/parser/vendors/fortinet-fortigate.ts +217 -0
- package/src/parser/vendors/huawei-vrp.ts +192 -0
- package/src/parser/vendors/index.ts +1521 -0
- package/src/parser/vendors/juniper-junos.ts +230 -0
- package/src/parser/vendors/mikrotik-routeros.ts +274 -0
- package/src/parser/vendors/nokia-sros.ts +251 -0
- package/src/parser/vendors/paloalto-panos.ts +264 -0
- package/src/parser/vendors/vyos-vyos.ts +454 -0
- package/src/types/ConfigNode.ts +72 -0
- package/src/types/DeclarativeRule.ts +158 -0
- package/src/types/IRule.ts +270 -0
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
// packages/rule-helpers/src/paloalto/helpers.ts
|
|
2
|
+
// Palo Alto PAN-OS-specific helper functions
|
|
3
|
+
|
|
4
|
+
import type { ConfigNode } from '../../types/ConfigNode';
|
|
5
|
+
import { hasChildCommand, getChildCommand, parseIp } from '../common/helpers';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Find a stanza by name within a node's children (case-insensitive)
|
|
9
|
+
* @param node The parent ConfigNode
|
|
10
|
+
* @param stanzaName The stanza name to find
|
|
11
|
+
* @returns The matching child node, or undefined
|
|
12
|
+
*/
|
|
13
|
+
export const findStanza = (
|
|
14
|
+
node: ConfigNode,
|
|
15
|
+
stanzaName: string
|
|
16
|
+
): ConfigNode | undefined => {
|
|
17
|
+
return node.children.find(
|
|
18
|
+
(child) => child.id.toLowerCase() === stanzaName.toLowerCase()
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find all stanzas matching a pattern within a node's children
|
|
24
|
+
* @param node The parent ConfigNode
|
|
25
|
+
* @param pattern The regex pattern to match
|
|
26
|
+
* @returns Array of matching child nodes
|
|
27
|
+
*/
|
|
28
|
+
export const findStanzas = (node: ConfigNode, pattern: RegExp): ConfigNode[] => {
|
|
29
|
+
return node.children.filter((child) => pattern.test(child.id.toLowerCase()));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a security rule has logging enabled
|
|
34
|
+
* @param ruleNode The security rule ConfigNode
|
|
35
|
+
* @returns Object indicating log-start and log-end status
|
|
36
|
+
*/
|
|
37
|
+
export const hasLogging = (
|
|
38
|
+
ruleNode: ConfigNode
|
|
39
|
+
): { logStart: boolean; logEnd: boolean } => {
|
|
40
|
+
const logStart = hasChildCommand(ruleNode, 'log-start');
|
|
41
|
+
const logEnd = hasChildCommand(ruleNode, 'log-end');
|
|
42
|
+
return { logStart, logEnd };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a security rule has a security profile attached
|
|
47
|
+
* @param ruleNode The security rule ConfigNode
|
|
48
|
+
* @returns true if any security profile is attached
|
|
49
|
+
*/
|
|
50
|
+
export const hasSecurityProfile = (ruleNode: ConfigNode): boolean => {
|
|
51
|
+
// Check for profile-setting stanza
|
|
52
|
+
const profileSetting = findStanza(ruleNode, 'profile-setting');
|
|
53
|
+
if (profileSetting && profileSetting.children.length > 0) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check for individual profile commands
|
|
58
|
+
const profileKeywords = [
|
|
59
|
+
'virus',
|
|
60
|
+
'spyware',
|
|
61
|
+
'vulnerability',
|
|
62
|
+
'url-filtering',
|
|
63
|
+
'file-blocking',
|
|
64
|
+
'wildfire-analysis',
|
|
65
|
+
'data-filtering',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return profileKeywords.some((keyword) => hasChildCommand(ruleNode, keyword));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a rule action is "allow" (vs deny/drop/reset)
|
|
73
|
+
* @param ruleNode The rule ConfigNode
|
|
74
|
+
* @returns true if the action is allow
|
|
75
|
+
*/
|
|
76
|
+
export const isAllowRule = (ruleNode: ConfigNode): boolean => {
|
|
77
|
+
const action = getChildCommand(ruleNode, 'action');
|
|
78
|
+
if (!action) return false;
|
|
79
|
+
return action.id.toLowerCase().includes('allow');
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a rule action is "deny" or "drop" or "reset"
|
|
84
|
+
* @param ruleNode The rule ConfigNode
|
|
85
|
+
* @returns true if the action is deny/drop/reset
|
|
86
|
+
*/
|
|
87
|
+
export const isDenyRule = (ruleNode: ConfigNode): boolean => {
|
|
88
|
+
const action = getChildCommand(ruleNode, 'action');
|
|
89
|
+
if (!action) return false;
|
|
90
|
+
const actionId = action.id.toLowerCase();
|
|
91
|
+
return (
|
|
92
|
+
actionId.includes('deny') ||
|
|
93
|
+
actionId.includes('drop') ||
|
|
94
|
+
actionId.includes('reset')
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the source zones from a rule
|
|
100
|
+
* @param ruleNode The rule ConfigNode
|
|
101
|
+
* @returns Array of source zone names
|
|
102
|
+
*/
|
|
103
|
+
export const getSourceZones = (ruleNode: ConfigNode): string[] => {
|
|
104
|
+
const from = findStanza(ruleNode, 'from');
|
|
105
|
+
if (!from) return [];
|
|
106
|
+
return from.children.map((child) => child.id.trim());
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the destination zones from a rule
|
|
111
|
+
* @param ruleNode The rule ConfigNode
|
|
112
|
+
* @returns Array of destination zone names
|
|
113
|
+
*/
|
|
114
|
+
export const getDestinationZones = (ruleNode: ConfigNode): string[] => {
|
|
115
|
+
const to = findStanza(ruleNode, 'to');
|
|
116
|
+
if (!to) return [];
|
|
117
|
+
return to.children.map((child) => child.id.trim());
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the applications from a rule
|
|
122
|
+
* @param ruleNode The rule ConfigNode
|
|
123
|
+
* @returns Array of application names
|
|
124
|
+
*/
|
|
125
|
+
export const getApplications = (ruleNode: ConfigNode): string[] => {
|
|
126
|
+
// Check for "application" stanza with children
|
|
127
|
+
const application = findStanza(ruleNode, 'application');
|
|
128
|
+
if (application && application.children.length > 0) {
|
|
129
|
+
return application.children.map((child) => child.id.trim());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Also check for inline "application <value>" commands
|
|
133
|
+
const appCommands = ruleNode.children.filter((child) =>
|
|
134
|
+
child.id.toLowerCase().startsWith('application ')
|
|
135
|
+
);
|
|
136
|
+
if (appCommands.length > 0) {
|
|
137
|
+
return appCommands.map((cmd) => {
|
|
138
|
+
const parts = cmd.id.split(/\s+/);
|
|
139
|
+
return parts.slice(1).join(' ').replace(/;$/, '').trim();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return [];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a rule uses "any" application (risky)
|
|
148
|
+
* @param ruleNode The rule ConfigNode
|
|
149
|
+
* @returns true if application is "any"
|
|
150
|
+
*/
|
|
151
|
+
export const hasAnyApplication = (ruleNode: ConfigNode): boolean => {
|
|
152
|
+
const apps = getApplications(ruleNode);
|
|
153
|
+
return apps.some((app) => app.toLowerCase() === 'any');
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if a rule uses "any" source (0.0.0.0/0 or "any")
|
|
158
|
+
* @param ruleNode The rule ConfigNode
|
|
159
|
+
* @returns true if source is "any"
|
|
160
|
+
*/
|
|
161
|
+
export const hasAnySource = (ruleNode: ConfigNode): boolean => {
|
|
162
|
+
// Check for "source" stanza with children
|
|
163
|
+
const source = findStanza(ruleNode, 'source');
|
|
164
|
+
if (source && source.children.length > 0) {
|
|
165
|
+
return source.children.some((child) => {
|
|
166
|
+
const id = child.id.toLowerCase().trim().replace(/;$/, '');
|
|
167
|
+
return id === 'any' || id === '0.0.0.0/0';
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Also check for inline "source any" or "source <value>" commands
|
|
172
|
+
const sourceCommands = ruleNode.children.filter((child) =>
|
|
173
|
+
child.id.toLowerCase().startsWith('source ')
|
|
174
|
+
);
|
|
175
|
+
if (sourceCommands.length > 0) {
|
|
176
|
+
return sourceCommands.some((cmd) => {
|
|
177
|
+
const value = cmd.id.split(/\s+/).slice(1).join(' ').toLowerCase().replace(/;$/, '').trim();
|
|
178
|
+
return value === 'any' || value === '0.0.0.0/0';
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if a rule uses "any" destination
|
|
187
|
+
* @param ruleNode The rule ConfigNode
|
|
188
|
+
* @returns true if destination is "any"
|
|
189
|
+
*/
|
|
190
|
+
export const hasAnyDestination = (ruleNode: ConfigNode): boolean => {
|
|
191
|
+
// Check for "destination" stanza with children
|
|
192
|
+
const destination = findStanza(ruleNode, 'destination');
|
|
193
|
+
if (destination && destination.children.length > 0) {
|
|
194
|
+
return destination.children.some((child) => {
|
|
195
|
+
const id = child.id.toLowerCase().trim().replace(/;$/, '');
|
|
196
|
+
return id === 'any' || id === '0.0.0.0/0';
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Also check for inline "destination any" or "destination <value>" commands
|
|
201
|
+
const destCommands = ruleNode.children.filter((child) =>
|
|
202
|
+
child.id.toLowerCase().startsWith('destination ')
|
|
203
|
+
);
|
|
204
|
+
if (destCommands.length > 0) {
|
|
205
|
+
return destCommands.some((cmd) => {
|
|
206
|
+
const value = cmd.id.split(/\s+/).slice(1).join(' ').toLowerCase().replace(/;$/, '').trim();
|
|
207
|
+
return value === 'any' || value === '0.0.0.0/0';
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return false;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if a rule uses "any" service (all TCP/UDP ports)
|
|
216
|
+
* @param ruleNode The rule ConfigNode
|
|
217
|
+
* @returns true if service is "any"
|
|
218
|
+
*/
|
|
219
|
+
export const hasAnyService = (ruleNode: ConfigNode): boolean => {
|
|
220
|
+
// Check for "service" stanza with children
|
|
221
|
+
const service = findStanza(ruleNode, 'service');
|
|
222
|
+
if (service && service.children.length > 0) {
|
|
223
|
+
return service.children.some((child) => {
|
|
224
|
+
const id = child.id.toLowerCase().trim().replace(/;$/, '');
|
|
225
|
+
return id === 'any';
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Also check for inline "service any" or "service <value>" commands
|
|
230
|
+
const serviceCommands = ruleNode.children.filter((child) =>
|
|
231
|
+
child.id.toLowerCase().startsWith('service ')
|
|
232
|
+
);
|
|
233
|
+
if (serviceCommands.length > 0) {
|
|
234
|
+
return serviceCommands.some((cmd) => {
|
|
235
|
+
const value = cmd.id.split(/\s+/).slice(1).join(' ').toLowerCase().replace(/;$/, '').trim();
|
|
236
|
+
return value === 'any';
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return false;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if a rule is disabled
|
|
245
|
+
* @param ruleNode The rule ConfigNode
|
|
246
|
+
* @returns true if the rule is disabled
|
|
247
|
+
*/
|
|
248
|
+
export const isRuleDisabled = (ruleNode: ConfigNode): boolean => {
|
|
249
|
+
const disabled = getChildCommand(ruleNode, 'disabled');
|
|
250
|
+
if (!disabled) return false;
|
|
251
|
+
return disabled.id.toLowerCase().includes('yes') || disabled.id.toLowerCase().includes('true');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get all security rules from a rulebase
|
|
256
|
+
* @param rulebaseNode The rulebase ConfigNode
|
|
257
|
+
* @returns Array of security rule nodes
|
|
258
|
+
*/
|
|
259
|
+
export const getSecurityRules = (rulebaseNode: ConfigNode): ConfigNode[] => {
|
|
260
|
+
const security = findStanza(rulebaseNode, 'security');
|
|
261
|
+
if (!security) return [];
|
|
262
|
+
|
|
263
|
+
const rules = findStanza(security, 'rules');
|
|
264
|
+
if (!rules) return [];
|
|
265
|
+
|
|
266
|
+
return rules.children;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get all NAT rules from a rulebase
|
|
271
|
+
* @param rulebaseNode The rulebase ConfigNode
|
|
272
|
+
* @returns Array of NAT rule nodes
|
|
273
|
+
*/
|
|
274
|
+
export const getNatRules = (rulebaseNode: ConfigNode): ConfigNode[] => {
|
|
275
|
+
const nat = findStanza(rulebaseNode, 'nat');
|
|
276
|
+
if (!nat) return [];
|
|
277
|
+
|
|
278
|
+
const rules = findStanza(nat, 'rules');
|
|
279
|
+
if (!rules) return [];
|
|
280
|
+
|
|
281
|
+
return rules.children;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if HA (High Availability) is configured
|
|
286
|
+
* @param deviceconfigNode The deviceconfig ConfigNode
|
|
287
|
+
* @returns true if HA is configured
|
|
288
|
+
*/
|
|
289
|
+
export const isHAConfigured = (deviceconfigNode: ConfigNode): boolean => {
|
|
290
|
+
const ha = findStanza(deviceconfigNode, 'high-availability');
|
|
291
|
+
if (!ha) return false;
|
|
292
|
+
return ha.children.length > 0;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Check if an interface is a physical Ethernet port
|
|
297
|
+
* @param interfaceName The interface name
|
|
298
|
+
* @returns true if it's a physical ethernet port
|
|
299
|
+
*/
|
|
300
|
+
export const isPhysicalEthernetPort = (interfaceName: string): boolean => {
|
|
301
|
+
return /^ethernet\d+\/\d+$/i.test(interfaceName);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Check if an interface is a loopback
|
|
306
|
+
* @param interfaceName The interface name
|
|
307
|
+
* @returns true if it's a loopback interface
|
|
308
|
+
*/
|
|
309
|
+
export const isLoopbackInterface = (interfaceName: string): boolean => {
|
|
310
|
+
return /^loopback\.\d+$/i.test(interfaceName);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check if an interface is a tunnel
|
|
315
|
+
* @param interfaceName The interface name
|
|
316
|
+
* @returns true if it's a tunnel interface
|
|
317
|
+
*/
|
|
318
|
+
export const isTunnelInterface = (interfaceName: string): boolean => {
|
|
319
|
+
return /^tunnel\.\d+$/i.test(interfaceName);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if an interface is an aggregate (LACP)
|
|
324
|
+
* @param interfaceName The interface name
|
|
325
|
+
* @returns true if it's an aggregate interface
|
|
326
|
+
*/
|
|
327
|
+
export const isAggregateInterface = (interfaceName: string): boolean => {
|
|
328
|
+
return /^ae\d+$/i.test(interfaceName);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Extract zone name from a zone configuration node
|
|
333
|
+
* @param zoneNode The zone ConfigNode
|
|
334
|
+
* @returns The zone name
|
|
335
|
+
*/
|
|
336
|
+
export const getZoneName = (zoneNode: ConfigNode): string => {
|
|
337
|
+
// Zone node ID is typically the zone name itself
|
|
338
|
+
return zoneNode.id.split(/\s+/)[0] || zoneNode.id;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if zone protection profile is applied to a zone
|
|
343
|
+
* @param zoneNode The zone ConfigNode
|
|
344
|
+
* @returns true if zone protection profile is configured
|
|
345
|
+
*/
|
|
346
|
+
export const hasZoneProtectionProfile = (zoneNode: ConfigNode): boolean => {
|
|
347
|
+
return hasChildCommand(zoneNode, 'zone-protection-profile');
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if user identification is enabled on a zone
|
|
352
|
+
* @param zoneNode The zone ConfigNode
|
|
353
|
+
* @returns true if user identification is enabled
|
|
354
|
+
*/
|
|
355
|
+
export const hasUserIdentification = (zoneNode: ConfigNode): boolean => {
|
|
356
|
+
const network = findStanza(zoneNode, 'network');
|
|
357
|
+
if (!network) return false;
|
|
358
|
+
return hasChildCommand(network, 'enable-user-identification');
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Parse PAN-OS address format (e.g., "10.0.0.1/24" or "10.0.0.1-10.0.0.255")
|
|
363
|
+
* @param address The address string
|
|
364
|
+
* @returns Object with parsed address info, or null if invalid
|
|
365
|
+
*/
|
|
366
|
+
export const parsePanosAddress = (
|
|
367
|
+
address: string
|
|
368
|
+
): { ip: number; prefix?: number; rangeEnd?: number } | null => {
|
|
369
|
+
// CIDR format: 10.0.0.1/24
|
|
370
|
+
if (address.includes('/')) {
|
|
371
|
+
const parts = address.split('/');
|
|
372
|
+
if (parts.length !== 2) return null;
|
|
373
|
+
|
|
374
|
+
const [ipStr, prefixStr] = parts;
|
|
375
|
+
if (!ipStr || !prefixStr) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const ip = parseIp(ipStr);
|
|
380
|
+
const prefix = parseInt(prefixStr, 10);
|
|
381
|
+
|
|
382
|
+
if (ip === null || isNaN(prefix) || prefix < 0 || prefix > 32) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return { ip, prefix };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Range format: 10.0.0.1-10.0.0.255
|
|
390
|
+
if (address.includes('-')) {
|
|
391
|
+
const parts = address.split('-');
|
|
392
|
+
if (parts.length !== 2) return null;
|
|
393
|
+
|
|
394
|
+
const [startStr, endStr] = parts;
|
|
395
|
+
if (!startStr || !endStr) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const ip = parseIp(startStr);
|
|
400
|
+
const rangeEnd = parseIp(endStr);
|
|
401
|
+
|
|
402
|
+
if (ip === null || rangeEnd === null) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { ip, rangeEnd };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Single IP
|
|
410
|
+
const ip = parseIp(address);
|
|
411
|
+
if (ip === null) return null;
|
|
412
|
+
|
|
413
|
+
return { ip };
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Check if WildFire is configured
|
|
418
|
+
* @param profilesNode The profiles ConfigNode
|
|
419
|
+
* @returns true if WildFire analysis is configured
|
|
420
|
+
*/
|
|
421
|
+
export const hasWildfireProfile = (profilesNode: ConfigNode): boolean => {
|
|
422
|
+
const wildfire = findStanza(profilesNode, 'wildfire-analysis');
|
|
423
|
+
return wildfire !== undefined && wildfire.children.length > 0;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Check if URL Filtering is configured
|
|
428
|
+
* @param profilesNode The profiles ConfigNode
|
|
429
|
+
* @returns true if URL filtering is configured
|
|
430
|
+
*/
|
|
431
|
+
export const hasUrlFilteringProfile = (profilesNode: ConfigNode): boolean => {
|
|
432
|
+
const urlFiltering = findStanza(profilesNode, 'url-filtering');
|
|
433
|
+
return urlFiltering !== undefined && urlFiltering.children.length > 0;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Check if Anti-Virus profile is configured
|
|
438
|
+
* @param profilesNode The profiles ConfigNode
|
|
439
|
+
* @returns true if AV profile is configured
|
|
440
|
+
*/
|
|
441
|
+
export const hasAntiVirusProfile = (profilesNode: ConfigNode): boolean => {
|
|
442
|
+
const virus = findStanza(profilesNode, 'virus');
|
|
443
|
+
return virus !== undefined && virus.children.length > 0;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Check if Anti-Spyware profile is configured
|
|
448
|
+
* @param profilesNode The profiles ConfigNode
|
|
449
|
+
* @returns true if Anti-Spyware profile is configured
|
|
450
|
+
*/
|
|
451
|
+
export const hasAntiSpywareProfile = (profilesNode: ConfigNode): boolean => {
|
|
452
|
+
const spyware = findStanza(profilesNode, 'spyware');
|
|
453
|
+
return spyware !== undefined && spyware.children.length > 0;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Check if Vulnerability Protection profile is configured
|
|
458
|
+
* @param profilesNode The profiles ConfigNode
|
|
459
|
+
* @returns true if Vulnerability Protection profile is configured
|
|
460
|
+
*/
|
|
461
|
+
export const hasVulnerabilityProfile = (profilesNode: ConfigNode): boolean => {
|
|
462
|
+
const vuln = findStanza(profilesNode, 'vulnerability');
|
|
463
|
+
return vuln !== undefined && vuln.children.length > 0;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Check if File Blocking profile is configured
|
|
468
|
+
* @param profilesNode The profiles ConfigNode
|
|
469
|
+
* @returns true if File Blocking profile is configured
|
|
470
|
+
*/
|
|
471
|
+
export const hasFileBlockingProfile = (profilesNode: ConfigNode): boolean => {
|
|
472
|
+
const fileBlocking = findStanza(profilesNode, 'file-blocking');
|
|
473
|
+
return fileBlocking !== undefined && fileBlocking.children.length > 0;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Check if password complexity is configured
|
|
478
|
+
* @param systemNode The system ConfigNode
|
|
479
|
+
* @returns true if password complexity is configured
|
|
480
|
+
*/
|
|
481
|
+
export const hasPasswordComplexity = (systemNode: ConfigNode): boolean => {
|
|
482
|
+
const passwordComplexity = findStanza(systemNode, 'password-complexity');
|
|
483
|
+
if (!passwordComplexity) return false;
|
|
484
|
+
return hasChildCommand(passwordComplexity, 'enabled');
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Get password complexity settings
|
|
489
|
+
* @param systemNode The system ConfigNode
|
|
490
|
+
* @returns Object with password complexity settings
|
|
491
|
+
*/
|
|
492
|
+
export const getPasswordComplexitySettings = (
|
|
493
|
+
systemNode: ConfigNode
|
|
494
|
+
): {
|
|
495
|
+
enabled: boolean;
|
|
496
|
+
minLength: number | null;
|
|
497
|
+
minUppercase: number | null;
|
|
498
|
+
minLowercase: number | null;
|
|
499
|
+
minNumeric: number | null;
|
|
500
|
+
minSpecial: number | null;
|
|
501
|
+
} => {
|
|
502
|
+
const defaults = {
|
|
503
|
+
enabled: false,
|
|
504
|
+
minLength: null as number | null,
|
|
505
|
+
minUppercase: null as number | null,
|
|
506
|
+
minLowercase: null as number | null,
|
|
507
|
+
minNumeric: null as number | null,
|
|
508
|
+
minSpecial: null as number | null,
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const passwordComplexity = findStanza(systemNode, 'password-complexity');
|
|
512
|
+
if (!passwordComplexity) return defaults;
|
|
513
|
+
|
|
514
|
+
const enabledCmd = getChildCommand(passwordComplexity, 'enabled');
|
|
515
|
+
const enabled = enabledCmd?.id.toLowerCase().includes('yes') ?? false;
|
|
516
|
+
|
|
517
|
+
const getNumericValue = (key: string): number | null => {
|
|
518
|
+
const cmd = getChildCommand(passwordComplexity, key);
|
|
519
|
+
if (!cmd) return null;
|
|
520
|
+
const match = cmd.id.match(/(\d+)/);
|
|
521
|
+
return match?.[1] ? parseInt(match[1], 10) : null;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
enabled,
|
|
526
|
+
minLength: getNumericValue('minimum-length'),
|
|
527
|
+
minUppercase: getNumericValue('minimum-uppercase-letters'),
|
|
528
|
+
minLowercase: getNumericValue('minimum-lowercase-letters'),
|
|
529
|
+
minNumeric: getNumericValue('minimum-numeric-letters'),
|
|
530
|
+
minSpecial: getNumericValue('minimum-special-characters'),
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Check if SNMP is configured with v3 (secure) or v2c (less secure)
|
|
536
|
+
* @param systemNode The system ConfigNode
|
|
537
|
+
* @returns Object indicating SNMP configuration status
|
|
538
|
+
*/
|
|
539
|
+
export const getSnmpConfiguration = (
|
|
540
|
+
systemNode: ConfigNode
|
|
541
|
+
): { configured: boolean; hasV3: boolean; hasV2c: boolean; hasCommunityPublic: boolean } => {
|
|
542
|
+
const snmpSetting = findStanza(systemNode, 'snmp-setting');
|
|
543
|
+
if (!snmpSetting) {
|
|
544
|
+
return { configured: false, hasV3: false, hasV2c: false, hasCommunityPublic: false };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const accessSetting = findStanza(snmpSetting, 'access-setting');
|
|
548
|
+
if (!accessSetting) {
|
|
549
|
+
return { configured: false, hasV3: false, hasV2c: false, hasCommunityPublic: false };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const version = findStanza(accessSetting, 'version');
|
|
553
|
+
if (!version) {
|
|
554
|
+
return { configured: false, hasV3: false, hasV2c: false, hasCommunityPublic: false };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const hasV3 = findStanza(version, 'v3') !== undefined;
|
|
558
|
+
const v2c = findStanza(version, 'v2c');
|
|
559
|
+
const hasV2c = v2c !== undefined;
|
|
560
|
+
|
|
561
|
+
// Check for default/weak community strings
|
|
562
|
+
let hasCommunityPublic = false;
|
|
563
|
+
if (v2c) {
|
|
564
|
+
const communityString = getChildCommand(v2c, 'snmp-community-string');
|
|
565
|
+
if (communityString) {
|
|
566
|
+
const value = communityString.id.toLowerCase();
|
|
567
|
+
hasCommunityPublic =
|
|
568
|
+
value.includes('public') || value.includes('private') || value.includes('community');
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return { configured: true, hasV3, hasV2c, hasCommunityPublic };
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Check if decryption profile has secure TLS settings
|
|
577
|
+
* @param decryptionProfileNode The decryption profile ConfigNode
|
|
578
|
+
* @returns Object with TLS security assessment
|
|
579
|
+
*/
|
|
580
|
+
export const getDecryptionTlsSettings = (
|
|
581
|
+
decryptionProfileNode: ConfigNode
|
|
582
|
+
): { hasMinVersion: boolean; minVersion: string | null; hasWeakCiphers: boolean } => {
|
|
583
|
+
const sslProtocolSettings = findStanza(decryptionProfileNode, 'ssl-protocol-settings');
|
|
584
|
+
if (!sslProtocolSettings) {
|
|
585
|
+
return { hasMinVersion: false, minVersion: null, hasWeakCiphers: false };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const minVersionCmd = getChildCommand(sslProtocolSettings, 'min-version');
|
|
589
|
+
let minVersion: string | null = null;
|
|
590
|
+
let hasMinVersion = false;
|
|
591
|
+
|
|
592
|
+
if (minVersionCmd) {
|
|
593
|
+
hasMinVersion = true;
|
|
594
|
+
const match = minVersionCmd.id.match(/min-version\s+(\S+)/i);
|
|
595
|
+
minVersion = match?.[1]?.replace(/;$/, '') ?? null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Check for weak ciphers
|
|
599
|
+
const encAlgoCmd = getChildCommand(sslProtocolSettings, 'enc-algo');
|
|
600
|
+
let hasWeakCiphers = false;
|
|
601
|
+
if (encAlgoCmd) {
|
|
602
|
+
const value = encAlgoCmd.id.toLowerCase();
|
|
603
|
+
hasWeakCiphers =
|
|
604
|
+
value.includes('rc4') ||
|
|
605
|
+
value.includes('3des') ||
|
|
606
|
+
value.includes('des') ||
|
|
607
|
+
value.includes('null');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return { hasMinVersion, minVersion, hasWeakCiphers };
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Get IKE crypto profile settings for security assessment
|
|
615
|
+
* @param ikeProfileNode The IKE crypto profile ConfigNode
|
|
616
|
+
* @returns Object with security assessment
|
|
617
|
+
*/
|
|
618
|
+
export const getIkeCryptoSettings = (
|
|
619
|
+
ikeProfileNode: ConfigNode
|
|
620
|
+
): {
|
|
621
|
+
hasWeakDH: boolean;
|
|
622
|
+
hasWeakHash: boolean;
|
|
623
|
+
hasWeakEncryption: boolean;
|
|
624
|
+
dhGroups: string[];
|
|
625
|
+
hashes: string[];
|
|
626
|
+
encryptions: string[];
|
|
627
|
+
} => {
|
|
628
|
+
const dhGroups: string[] = [];
|
|
629
|
+
const hashes: string[] = [];
|
|
630
|
+
const encryptions: string[] = [];
|
|
631
|
+
|
|
632
|
+
// Extract DH groups
|
|
633
|
+
const dhGroupCmd = getChildCommand(ikeProfileNode, 'dh-group');
|
|
634
|
+
if (dhGroupCmd) {
|
|
635
|
+
const match = dhGroupCmd.id.match(/dh-group\s+\[([^\]]+)\]/i);
|
|
636
|
+
if (match?.[1]) {
|
|
637
|
+
dhGroups.push(...match[1].split(/\s+/).filter((g) => g.length > 0));
|
|
638
|
+
} else {
|
|
639
|
+
const singleMatch = dhGroupCmd.id.match(/dh-group\s+(\S+)/i);
|
|
640
|
+
if (singleMatch?.[1]) {
|
|
641
|
+
dhGroups.push(singleMatch[1].replace(/;$/, ''));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Extract hash algorithms
|
|
647
|
+
const hashCmd = getChildCommand(ikeProfileNode, 'hash');
|
|
648
|
+
if (hashCmd) {
|
|
649
|
+
const match = hashCmd.id.match(/hash\s+\[([^\]]+)\]/i);
|
|
650
|
+
if (match?.[1]) {
|
|
651
|
+
hashes.push(...match[1].split(/\s+/).filter((h) => h.length > 0));
|
|
652
|
+
} else {
|
|
653
|
+
const singleMatch = hashCmd.id.match(/hash\s+(\S+)/i);
|
|
654
|
+
if (singleMatch?.[1]) {
|
|
655
|
+
hashes.push(singleMatch[1].replace(/;$/, ''));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Extract encryption algorithms
|
|
661
|
+
const encCmd = getChildCommand(ikeProfileNode, 'encryption');
|
|
662
|
+
if (encCmd) {
|
|
663
|
+
const match = encCmd.id.match(/encryption\s+\[([^\]]+)\]/i);
|
|
664
|
+
if (match?.[1]) {
|
|
665
|
+
encryptions.push(...match[1].split(/\s+/).filter((e) => e.length > 0));
|
|
666
|
+
} else {
|
|
667
|
+
const singleMatch = encCmd.id.match(/encryption\s+(\S+)/i);
|
|
668
|
+
if (singleMatch?.[1]) {
|
|
669
|
+
encryptions.push(singleMatch[1].replace(/;$/, ''));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Check for weak settings
|
|
675
|
+
const weakDHGroups = ['group1', 'group2', 'group5'];
|
|
676
|
+
const weakHashes = ['md5', 'sha1'];
|
|
677
|
+
const weakEncryptions = ['des', '3des'];
|
|
678
|
+
|
|
679
|
+
const hasWeakDH = dhGroups.some((g) => weakDHGroups.includes(g.toLowerCase()));
|
|
680
|
+
const hasWeakHash = hashes.some((h) => weakHashes.includes(h.toLowerCase()));
|
|
681
|
+
const hasWeakEncryption = encryptions.some((e) => weakEncryptions.includes(e.toLowerCase()));
|
|
682
|
+
|
|
683
|
+
return { hasWeakDH, hasWeakHash, hasWeakEncryption, dhGroups, hashes, encryptions };
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Check if zone protection profile has flood protection configured
|
|
688
|
+
* @param zppNode The zone protection profile ConfigNode
|
|
689
|
+
* @returns Object indicating flood protection status
|
|
690
|
+
*/
|
|
691
|
+
export const hasFloodProtection = (
|
|
692
|
+
zppNode: ConfigNode
|
|
693
|
+
): { hasSyn: boolean; hasUdp: boolean; hasIcmp: boolean; hasOtherIp: boolean } => {
|
|
694
|
+
const flood = findStanza(zppNode, 'flood');
|
|
695
|
+
if (!flood) {
|
|
696
|
+
return { hasSyn: false, hasUdp: false, hasIcmp: false, hasOtherIp: false };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const tcpSyn = findStanza(flood, 'tcp-syn');
|
|
700
|
+
const udp = findStanza(flood, 'udp');
|
|
701
|
+
const icmp = findStanza(flood, 'icmp');
|
|
702
|
+
const otherIp = findStanza(flood, 'other-ip');
|
|
703
|
+
|
|
704
|
+
const isEnabled = (stanza: ConfigNode | undefined): boolean => {
|
|
705
|
+
if (!stanza) return false;
|
|
706
|
+
const enableCmd = getChildCommand(stanza, 'enable');
|
|
707
|
+
return enableCmd?.id.toLowerCase().includes('yes') ?? false;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
hasSyn: isEnabled(tcpSyn),
|
|
712
|
+
hasUdp: isEnabled(udp),
|
|
713
|
+
hasIcmp: isEnabled(icmp),
|
|
714
|
+
hasOtherIp: isEnabled(otherIp),
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Check if zone protection profile has reconnaissance protection
|
|
720
|
+
* @param zppNode The zone protection profile ConfigNode
|
|
721
|
+
* @returns true if scan/reconnaissance protection is configured
|
|
722
|
+
*/
|
|
723
|
+
export const hasReconProtection = (zppNode: ConfigNode): boolean => {
|
|
724
|
+
const scan = findStanza(zppNode, 'scan');
|
|
725
|
+
if (!scan) return false;
|
|
726
|
+
|
|
727
|
+
// Check for at least one scan protection type
|
|
728
|
+
const tcpPortScan = findStanza(scan, 'tcp-port-scan');
|
|
729
|
+
const hostSweep = findStanza(scan, 'host-sweep');
|
|
730
|
+
const udpPortScan = findStanza(scan, 'udp-port-scan');
|
|
731
|
+
|
|
732
|
+
return tcpPortScan !== undefined || hostSweep !== undefined || udpPortScan !== undefined;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Check if User-ID is enabled on a zone (for untrust zone check)
|
|
737
|
+
* @param zoneNode The zone ConfigNode
|
|
738
|
+
* @returns true if User-ID is enabled
|
|
739
|
+
*/
|
|
740
|
+
export const isUserIdEnabled = (zoneNode: ConfigNode): boolean => {
|
|
741
|
+
// Check direct enable-user-identification command
|
|
742
|
+
if (hasChildCommand(zoneNode, 'enable-user-identification')) {
|
|
743
|
+
const cmd = getChildCommand(zoneNode, 'enable-user-identification');
|
|
744
|
+
return cmd?.id.toLowerCase().includes('yes') ?? false;
|
|
745
|
+
}
|
|
746
|
+
return false;
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Check if HA has backup links configured
|
|
751
|
+
* @param haNode The high-availability ConfigNode
|
|
752
|
+
* @returns Object indicating backup link status
|
|
753
|
+
*/
|
|
754
|
+
export const getHABackupStatus = (
|
|
755
|
+
haNode: ConfigNode
|
|
756
|
+
): { hasHa1Backup: boolean; hasHa2Backup: boolean; hasHeartbeatBackup: boolean } => {
|
|
757
|
+
const interfaceStanza = findStanza(haNode, 'interface');
|
|
758
|
+
if (!interfaceStanza) {
|
|
759
|
+
return { hasHa1Backup: false, hasHa2Backup: false, hasHeartbeatBackup: false };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const hasHa1Backup = findStanza(interfaceStanza, 'ha1-backup') !== undefined;
|
|
763
|
+
const hasHa2Backup = findStanza(interfaceStanza, 'ha2-backup') !== undefined;
|
|
764
|
+
|
|
765
|
+
// Check for heartbeat backup in election-option
|
|
766
|
+
const group = findStanza(haNode, 'group');
|
|
767
|
+
let hasHeartbeatBackup = false;
|
|
768
|
+
if (group) {
|
|
769
|
+
const electionOption = findStanza(group, 'election-option');
|
|
770
|
+
if (electionOption) {
|
|
771
|
+
const heartbeatCmd = getChildCommand(electionOption, 'heartbeat-backup');
|
|
772
|
+
hasHeartbeatBackup = heartbeatCmd?.id.toLowerCase().includes('yes') ?? false;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return { hasHa1Backup, hasHa2Backup, hasHeartbeatBackup };
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Check if HA has link monitoring configured
|
|
781
|
+
* @param haNode The high-availability ConfigNode
|
|
782
|
+
* @returns true if link monitoring is configured
|
|
783
|
+
*/
|
|
784
|
+
export const hasHALinkMonitoring = (haNode: ConfigNode): boolean => {
|
|
785
|
+
const linkMonitoring = findStanza(haNode, 'link-monitoring');
|
|
786
|
+
if (!linkMonitoring) return false;
|
|
787
|
+
|
|
788
|
+
const linkGroup = findStanza(linkMonitoring, 'link-group');
|
|
789
|
+
return linkGroup !== undefined && linkGroup.children.length > 0;
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Check if HA has path monitoring configured
|
|
794
|
+
* @param haNode The high-availability ConfigNode
|
|
795
|
+
* @returns true if path monitoring is configured
|
|
796
|
+
*/
|
|
797
|
+
export const hasHAPathMonitoring = (haNode: ConfigNode): boolean => {
|
|
798
|
+
const pathMonitoring = findStanza(haNode, 'path-monitoring');
|
|
799
|
+
if (!pathMonitoring) return false;
|
|
800
|
+
|
|
801
|
+
const pathGroup = findStanza(pathMonitoring, 'path-group');
|
|
802
|
+
return pathGroup !== undefined && pathGroup.children.length > 0;
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Check if log forwarding is configured
|
|
807
|
+
* @param logSettingsNode The log-settings ConfigNode
|
|
808
|
+
* @returns Object indicating log forwarding status
|
|
809
|
+
*/
|
|
810
|
+
export const getLogForwardingStatus = (
|
|
811
|
+
logSettingsNode: ConfigNode
|
|
812
|
+
): { hasSyslog: boolean; hasPanorama: boolean; hasEmail: boolean } => {
|
|
813
|
+
const profiles = findStanza(logSettingsNode, 'profiles');
|
|
814
|
+
if (!profiles) {
|
|
815
|
+
return { hasSyslog: false, hasPanorama: false, hasEmail: false };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
let hasSyslog = false;
|
|
819
|
+
let hasPanorama = false;
|
|
820
|
+
let hasEmail = false;
|
|
821
|
+
|
|
822
|
+
// Check each profile for forwarding destinations
|
|
823
|
+
for (const profile of profiles.children) {
|
|
824
|
+
const matchList = findStanza(profile, 'match-list');
|
|
825
|
+
if (matchList) {
|
|
826
|
+
for (const match of matchList.children) {
|
|
827
|
+
if (findStanza(match, 'send-syslog')) hasSyslog = true;
|
|
828
|
+
if (hasChildCommand(match, 'send-to-panorama')) {
|
|
829
|
+
const cmd = getChildCommand(match, 'send-to-panorama');
|
|
830
|
+
if (cmd?.id.toLowerCase().includes('yes')) hasPanorama = true;
|
|
831
|
+
}
|
|
832
|
+
if (findStanza(match, 'send-email')) hasEmail = true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return { hasSyslog, hasPanorama, hasEmail };
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Check if dynamic content updates are scheduled
|
|
842
|
+
* @param systemNode The system ConfigNode
|
|
843
|
+
* @returns Object indicating update schedule status
|
|
844
|
+
*/
|
|
845
|
+
export const getUpdateScheduleStatus = (
|
|
846
|
+
systemNode: ConfigNode
|
|
847
|
+
): {
|
|
848
|
+
hasThreats: boolean;
|
|
849
|
+
hasAntivirus: boolean;
|
|
850
|
+
hasWildfire: boolean;
|
|
851
|
+
wildfireRealtime: boolean;
|
|
852
|
+
} => {
|
|
853
|
+
const updateSchedule = findStanza(systemNode, 'update-schedule');
|
|
854
|
+
if (!updateSchedule) {
|
|
855
|
+
return { hasThreats: false, hasAntivirus: false, hasWildfire: false, wildfireRealtime: false };
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const threats = findStanza(updateSchedule, 'threats');
|
|
859
|
+
const antivirus = findStanza(updateSchedule, 'anti-virus');
|
|
860
|
+
const wildfire = findStanza(updateSchedule, 'wildfire');
|
|
861
|
+
|
|
862
|
+
let wildfireRealtime = false;
|
|
863
|
+
if (wildfire) {
|
|
864
|
+
const recurring = findStanza(wildfire, 'recurring');
|
|
865
|
+
if (recurring) {
|
|
866
|
+
wildfireRealtime = hasChildCommand(recurring, 'real-time');
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return {
|
|
871
|
+
hasThreats: threats !== undefined && threats.children.length > 0,
|
|
872
|
+
hasAntivirus: antivirus !== undefined && antivirus.children.length > 0,
|
|
873
|
+
hasWildfire: wildfire !== undefined && wildfire.children.length > 0,
|
|
874
|
+
wildfireRealtime,
|
|
875
|
+
};
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Get all decryption rules from a rulebase
|
|
880
|
+
* @param rulebaseNode The rulebase ConfigNode
|
|
881
|
+
* @returns Array of decryption rule nodes
|
|
882
|
+
*/
|
|
883
|
+
export const getDecryptionRules = (rulebaseNode: ConfigNode): ConfigNode[] => {
|
|
884
|
+
const decryption = findStanza(rulebaseNode, 'decryption');
|
|
885
|
+
if (!decryption) return [];
|
|
886
|
+
|
|
887
|
+
const rules = findStanza(decryption, 'rules');
|
|
888
|
+
if (!rules) return [];
|
|
889
|
+
|
|
890
|
+
return rules.children;
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Check if a decryption rule uses "decrypt" action
|
|
895
|
+
* @param ruleNode The decryption rule ConfigNode
|
|
896
|
+
* @returns true if the action is decrypt
|
|
897
|
+
*/
|
|
898
|
+
export const isDecryptRule = (ruleNode: ConfigNode): boolean => {
|
|
899
|
+
const action = getChildCommand(ruleNode, 'action');
|
|
900
|
+
if (!action) return false;
|
|
901
|
+
return action.id.toLowerCase().includes('decrypt');
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Get interface management profile settings
|
|
906
|
+
* @param profileNode The interface-management-profile ConfigNode
|
|
907
|
+
* @returns Object indicating enabled services
|
|
908
|
+
*/
|
|
909
|
+
export const getInterfaceManagementServices = (
|
|
910
|
+
profileNode: ConfigNode
|
|
911
|
+
): {
|
|
912
|
+
https: boolean;
|
|
913
|
+
http: boolean;
|
|
914
|
+
ssh: boolean;
|
|
915
|
+
telnet: boolean;
|
|
916
|
+
ping: boolean;
|
|
917
|
+
snmp: boolean;
|
|
918
|
+
} => {
|
|
919
|
+
// Use exact matching with word boundary to avoid "https" matching "http"
|
|
920
|
+
const isServiceEnabled = (serviceName: string): boolean => {
|
|
921
|
+
// Look for exact service name followed by space/end (e.g., "http yes" not "https yes")
|
|
922
|
+
const cmd = profileNode.children.find((child) => {
|
|
923
|
+
const lowerId = child.id.toLowerCase();
|
|
924
|
+
// Match exact service name: "http yes", "http no", etc.
|
|
925
|
+
return lowerId === serviceName || lowerId.startsWith(serviceName + ' ');
|
|
926
|
+
});
|
|
927
|
+
if (!cmd) return false;
|
|
928
|
+
return cmd.id.toLowerCase().includes('yes');
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
https: isServiceEnabled('https'),
|
|
933
|
+
http: isServiceEnabled('http'),
|
|
934
|
+
ssh: isServiceEnabled('ssh'),
|
|
935
|
+
telnet: isServiceEnabled('telnet'),
|
|
936
|
+
ping: isServiceEnabled('ping'),
|
|
937
|
+
snmp: isServiceEnabled('snmp'),
|
|
938
|
+
};
|
|
939
|
+
};
|