@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.
Files changed (71) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +86 -0
  3. package/package.json +60 -0
  4. package/src/constants.ts +77 -0
  5. package/src/engine/RuleExecutor.ts +256 -0
  6. package/src/engine/Runner.ts +312 -0
  7. package/src/engine/SandboxedExecutor.ts +208 -0
  8. package/src/errors.ts +88 -0
  9. package/src/helpers/arista/helpers.ts +1220 -0
  10. package/src/helpers/arista/index.ts +12 -0
  11. package/src/helpers/aruba/helpers.ts +637 -0
  12. package/src/helpers/aruba/index.ts +13 -0
  13. package/src/helpers/cisco/helpers.ts +534 -0
  14. package/src/helpers/cisco/index.ts +11 -0
  15. package/src/helpers/common/helpers.ts +265 -0
  16. package/src/helpers/common/index.ts +5 -0
  17. package/src/helpers/common/validation.ts +280 -0
  18. package/src/helpers/cumulus/helpers.ts +676 -0
  19. package/src/helpers/cumulus/index.ts +12 -0
  20. package/src/helpers/extreme/helpers.ts +422 -0
  21. package/src/helpers/extreme/index.ts +12 -0
  22. package/src/helpers/fortinet/helpers.ts +892 -0
  23. package/src/helpers/fortinet/index.ts +12 -0
  24. package/src/helpers/huawei/helpers.ts +790 -0
  25. package/src/helpers/huawei/index.ts +11 -0
  26. package/src/helpers/index.ts +53 -0
  27. package/src/helpers/juniper/helpers.ts +756 -0
  28. package/src/helpers/juniper/index.ts +12 -0
  29. package/src/helpers/mikrotik/helpers.ts +722 -0
  30. package/src/helpers/mikrotik/index.ts +12 -0
  31. package/src/helpers/nokia/helpers.ts +856 -0
  32. package/src/helpers/nokia/index.ts +11 -0
  33. package/src/helpers/paloalto/helpers.ts +939 -0
  34. package/src/helpers/paloalto/index.ts +12 -0
  35. package/src/helpers/vyos/helpers.ts +429 -0
  36. package/src/helpers/vyos/index.ts +12 -0
  37. package/src/index.ts +30 -0
  38. package/src/json-rules/ExpressionEvaluator.ts +292 -0
  39. package/src/json-rules/HelperRegistry.ts +177 -0
  40. package/src/json-rules/JsonRuleCompiler.ts +339 -0
  41. package/src/json-rules/JsonRuleValidator.ts +371 -0
  42. package/src/json-rules/index.ts +97 -0
  43. package/src/json-rules/schema.json +350 -0
  44. package/src/json-rules/types.ts +303 -0
  45. package/src/pack-loader/PackLoader.ts +332 -0
  46. package/src/pack-loader/index.ts +17 -0
  47. package/src/pack-loader/types.ts +135 -0
  48. package/src/parser/IncrementalParser.ts +527 -0
  49. package/src/parser/Sanitizer.ts +104 -0
  50. package/src/parser/SchemaAwareParser.ts +504 -0
  51. package/src/parser/VendorSchema.ts +72 -0
  52. package/src/parser/vendors/arista-eos.ts +206 -0
  53. package/src/parser/vendors/aruba-aoscx.ts +123 -0
  54. package/src/parser/vendors/aruba-aosswitch.ts +113 -0
  55. package/src/parser/vendors/aruba-wlc.ts +173 -0
  56. package/src/parser/vendors/cisco-ios.ts +110 -0
  57. package/src/parser/vendors/cisco-nxos.ts +107 -0
  58. package/src/parser/vendors/cumulus-linux.ts +161 -0
  59. package/src/parser/vendors/extreme-exos.ts +154 -0
  60. package/src/parser/vendors/extreme-voss.ts +167 -0
  61. package/src/parser/vendors/fortinet-fortigate.ts +217 -0
  62. package/src/parser/vendors/huawei-vrp.ts +192 -0
  63. package/src/parser/vendors/index.ts +1521 -0
  64. package/src/parser/vendors/juniper-junos.ts +230 -0
  65. package/src/parser/vendors/mikrotik-routeros.ts +274 -0
  66. package/src/parser/vendors/nokia-sros.ts +251 -0
  67. package/src/parser/vendors/paloalto-panos.ts +264 -0
  68. package/src/parser/vendors/vyos-vyos.ts +454 -0
  69. package/src/types/ConfigNode.ts +72 -0
  70. package/src/types/DeclarativeRule.ts +158 -0
  71. package/src/types/IRule.ts +270 -0
@@ -0,0 +1,756 @@
1
+ // packages/rule-helpers/src/juniper/helpers.ts
2
+ // Juniper JunOS-specific helper functions
3
+ // Based on Juniper Best Practices: docs/Juniper-best-practices.md
4
+
5
+ import type { ConfigNode } from '../../types/ConfigNode';
6
+ import {
7
+ hasChildCommand,
8
+ getChildCommand,
9
+ getChildCommands,
10
+ parseIp,
11
+ prefixToMask,
12
+ equalsIgnoreCase,
13
+ includesIgnoreCase,
14
+ startsWithIgnoreCase,
15
+ parseInteger,
16
+ } from '../common/helpers';
17
+
18
+ // Re-export common helpers for convenience
19
+ export { hasChildCommand, getChildCommand, getChildCommands, parseIp } from '../common/helpers';
20
+
21
+ /**
22
+ * Check if a JunOS interface is disabled (has "disable" statement)
23
+ */
24
+ export const isDisabled = (node: ConfigNode): boolean => {
25
+ return node.children.some((child) => equalsIgnoreCase(child.id.trim(), 'disable'));
26
+ };
27
+
28
+ /**
29
+ * Check if interface is a physical port (not lo0, irb, etc.)
30
+ */
31
+ export const isPhysicalJunosPort = (interfaceName: string): boolean => {
32
+ // Physical interfaces in JunOS: ge-, xe-, et-, ae- (aggregated), etc.
33
+ return (
34
+ startsWithIgnoreCase(interfaceName, 'ge-') ||
35
+ startsWithIgnoreCase(interfaceName, 'xe-') ||
36
+ startsWithIgnoreCase(interfaceName, 'et-') ||
37
+ startsWithIgnoreCase(interfaceName, 'ae') ||
38
+ startsWithIgnoreCase(interfaceName, 'em') ||
39
+ startsWithIgnoreCase(interfaceName, 'fxp')
40
+ );
41
+ };
42
+
43
+ /**
44
+ * Check if interface is a loopback
45
+ */
46
+ export const isLoopback = (interfaceName: string): boolean => {
47
+ return startsWithIgnoreCase(interfaceName, 'lo') || equalsIgnoreCase(interfaceName, 'lo0');
48
+ };
49
+
50
+ /**
51
+ * Check if interface is an IRB (Integrated Routing and Bridging) interface
52
+ */
53
+ export const isIrbInterface = (interfaceName: string): boolean => {
54
+ return startsWithIgnoreCase(interfaceName, 'irb');
55
+ };
56
+
57
+ /**
58
+ * Parse JunOS address format (e.g., "10.0.0.1/24")
59
+ * @param address The address string with CIDR notation
60
+ * @returns Object with ip number, prefix length, and mask, or null if invalid
61
+ */
62
+ export const parseJunosAddress = (
63
+ address: string
64
+ ): { ip: number; prefix: number; mask: number } | null => {
65
+ const parts = address.split('/');
66
+ if (parts.length !== 2) return null;
67
+
68
+ const ipPart = parts[0];
69
+ const prefixPart = parts[1];
70
+ if (!ipPart || !prefixPart) return null;
71
+
72
+ const ip = parseIp(ipPart);
73
+ const prefix = parseInteger(prefixPart);
74
+
75
+ if (ip === null || prefix === null || prefix < 0 || prefix > 32) {
76
+ return null;
77
+ }
78
+
79
+ return {
80
+ ip,
81
+ prefix,
82
+ mask: prefixToMask(prefix),
83
+ };
84
+ };
85
+
86
+ /**
87
+ * Find a stanza by name within a node's children
88
+ * @param node The parent ConfigNode
89
+ * @param stanzaName The stanza name to find
90
+ * @returns The matching child node, or undefined
91
+ */
92
+ export const findStanza = (
93
+ node: ConfigNode,
94
+ stanzaName: string
95
+ ): ConfigNode | undefined => {
96
+ return node.children.find(
97
+ (child) => equalsIgnoreCase(child.id, stanzaName)
98
+ );
99
+ };
100
+
101
+ /**
102
+ * Find all stanzas matching a pattern within a node's children
103
+ * @param node The parent ConfigNode
104
+ * @param pattern The regex pattern to match
105
+ * @returns Array of matching child nodes
106
+ */
107
+ export const findStanzas = (node: ConfigNode, pattern: RegExp): ConfigNode[] => {
108
+ // Note: Pattern is expected to have 'i' flag for case-insensitive matching
109
+ return node.children.filter((child) => pattern.test(child.id));
110
+ };
111
+
112
+ /**
113
+ * Get all interface units from a JunOS interface node
114
+ * @param interfaceNode The interface ConfigNode
115
+ * @returns Array of unit nodes
116
+ */
117
+ export const getInterfaceUnits = (interfaceNode: ConfigNode): ConfigNode[] => {
118
+ return interfaceNode.children.filter((child) =>
119
+ startsWithIgnoreCase(child.id, 'unit')
120
+ );
121
+ };
122
+
123
+ /**
124
+ * Check if a policy-statement has a "then reject" or "then accept" action
125
+ * @param termNode The term ConfigNode
126
+ * @returns 'accept' | 'reject' | 'next' | undefined
127
+ */
128
+ export const getTermAction = (
129
+ termNode: ConfigNode
130
+ ): 'accept' | 'reject' | 'next' | undefined => {
131
+ // First check for inline "then action" commands (e.g., "then reject;")
132
+ for (const child of termNode.children) {
133
+ const id = child.id.trim();
134
+ if (equalsIgnoreCase(id, 'then accept') || equalsIgnoreCase(id, 'then accept;')) return 'accept';
135
+ if (equalsIgnoreCase(id, 'then reject') || equalsIgnoreCase(id, 'then reject;')) return 'reject';
136
+ if (startsWithIgnoreCase(id, 'then next')) return 'next';
137
+ }
138
+
139
+ // Then check for nested "then" stanza with children
140
+ const thenStanza = findStanza(termNode, 'then');
141
+ if (!thenStanza) return undefined;
142
+
143
+ for (const child of thenStanza.children) {
144
+ const id = child.id.trim();
145
+ if (equalsIgnoreCase(id, 'accept') || equalsIgnoreCase(id, 'accept;')) return 'accept';
146
+ if (equalsIgnoreCase(id, 'reject') || equalsIgnoreCase(id, 'reject;')) return 'reject';
147
+ if (startsWithIgnoreCase(id, 'next')) return 'next';
148
+ }
149
+
150
+ return undefined;
151
+ };
152
+
153
+ /**
154
+ * Check if a firewall filter term has a "then discard" or "then reject" action
155
+ * @param termNode The term ConfigNode
156
+ * @returns true if the term discards/rejects traffic
157
+ */
158
+ export const isFilterTermDrop = (termNode: ConfigNode): boolean => {
159
+ // First check for inline "then action" commands (e.g., "then discard;")
160
+ for (const child of termNode.children) {
161
+ const id = child.id.trim();
162
+ if (
163
+ equalsIgnoreCase(id, 'then discard') ||
164
+ equalsIgnoreCase(id, 'then discard;') ||
165
+ equalsIgnoreCase(id, 'then reject') ||
166
+ equalsIgnoreCase(id, 'then reject;')
167
+ ) {
168
+ return true;
169
+ }
170
+ }
171
+
172
+ // Then check for nested "then" stanza with children
173
+ const thenStanza = findStanza(termNode, 'then');
174
+ if (!thenStanza) return false;
175
+
176
+ for (const child of thenStanza.children) {
177
+ const id = child.id.trim();
178
+ if (equalsIgnoreCase(id, 'discard') || equalsIgnoreCase(id, 'discard;') || equalsIgnoreCase(id, 'reject') || equalsIgnoreCase(id, 'reject;')) {
179
+ return true;
180
+ }
181
+ }
182
+
183
+ return false;
184
+ };
185
+
186
+ // ============================================================================
187
+ // JUNOS-MGMT: Management Plane Security Helpers
188
+ // ============================================================================
189
+
190
+ /**
191
+ * Check if SSH v2 only is configured (protocol-version v2)
192
+ * JUNOS-MGMT-003: SSH must be version 2 only
193
+ */
194
+ export const isSshV2Only = (servicesNode: ConfigNode): boolean => {
195
+ const ssh = findStanza(servicesNode, 'ssh');
196
+ if (!ssh) return false;
197
+
198
+ for (const child of ssh.children) {
199
+ if (includesIgnoreCase(child.id, 'protocol-version') && includesIgnoreCase(child.id, 'v2') && !includesIgnoreCase(child.id, 'v1')) {
200
+ return true;
201
+ }
202
+ }
203
+ return false;
204
+ };
205
+
206
+ /**
207
+ * Check if SSH root login is denied
208
+ * JUNOS-MGMT-003: SSH root-login must be deny
209
+ */
210
+ export const isSshRootLoginDenied = (servicesNode: ConfigNode): boolean => {
211
+ const ssh = findStanza(servicesNode, 'ssh');
212
+ if (!ssh) return false;
213
+
214
+ for (const child of ssh.children) {
215
+ if (includesIgnoreCase(child.id, 'root-login') && includesIgnoreCase(child.id, 'deny')) {
216
+ return true;
217
+ }
218
+ }
219
+ return false;
220
+ };
221
+
222
+ /**
223
+ * Check if telnet service is configured (insecure)
224
+ * JUNOS-MGMT-002: Telnet should be disabled
225
+ */
226
+ export const hasTelnetService = (servicesNode: ConfigNode): boolean => {
227
+ return hasChildCommand(servicesNode, 'telnet');
228
+ };
229
+
230
+ /**
231
+ * Check if finger service is configured (insecure)
232
+ * JUNOS-MGMT-002: Finger should be disabled
233
+ */
234
+ export const hasFingerService = (servicesNode: ConfigNode): boolean => {
235
+ return hasChildCommand(servicesNode, 'finger');
236
+ };
237
+
238
+ /**
239
+ * Check if FTP service is configured (insecure)
240
+ * JUNOS-MGMT-002: FTP should be disabled
241
+ */
242
+ export const hasFtpService = (servicesNode: ConfigNode): boolean => {
243
+ return hasChildCommand(servicesNode, 'ftp');
244
+ };
245
+
246
+ /**
247
+ * Check if xnm-clear-text service is configured (insecure)
248
+ * JUNOS-MGMT-002: xnm-clear-text should be disabled
249
+ */
250
+ export const hasXnmClearText = (servicesNode: ConfigNode): boolean => {
251
+ return hasChildCommand(servicesNode, 'xnm-clear-text');
252
+ };
253
+
254
+ /**
255
+ * Check if HTTP web management is configured (insecure)
256
+ * JUNOS-MGMT-002: HTTP web management should be disabled
257
+ */
258
+ export const hasHttpWebManagement = (servicesNode: ConfigNode): boolean => {
259
+ const webMgmt = findStanza(servicesNode, 'web-management');
260
+ if (!webMgmt) return false;
261
+ return hasChildCommand(webMgmt, 'http');
262
+ };
263
+
264
+ /**
265
+ * Get insecure services list from a services node
266
+ */
267
+ export const getInsecureServices = (servicesNode: ConfigNode): string[] => {
268
+ const insecure: string[] = [];
269
+ if (hasTelnetService(servicesNode)) insecure.push('telnet');
270
+ if (hasFingerService(servicesNode)) insecure.push('finger');
271
+ if (hasFtpService(servicesNode)) insecure.push('ftp');
272
+ if (hasXnmClearText(servicesNode)) insecure.push('xnm-clear-text');
273
+ if (hasHttpWebManagement(servicesNode)) insecure.push('web-management http');
274
+ return insecure;
275
+ };
276
+
277
+ /**
278
+ * Check if TACACS+ is configured
279
+ * JUNOS-MGMT-004: AAA should use TACACS+ or RADIUS
280
+ */
281
+ export const hasTacacsServer = (systemNode: ConfigNode): boolean => {
282
+ return hasChildCommand(systemNode, 'tacplus-server');
283
+ };
284
+
285
+ /**
286
+ * Check if RADIUS is configured
287
+ * JUNOS-MGMT-004: AAA should use TACACS+ or RADIUS
288
+ */
289
+ export const hasRadiusServer = (systemNode: ConfigNode): boolean => {
290
+ return hasChildCommand(systemNode, 'radius-server');
291
+ };
292
+
293
+ /**
294
+ * Check if authentication-order is configured
295
+ * JUNOS-MGMT-004: Authentication order should be configured
296
+ */
297
+ export const hasAuthenticationOrder = (systemNode: ConfigNode): boolean => {
298
+ return hasChildCommand(systemNode, 'authentication-order');
299
+ };
300
+
301
+ /**
302
+ * Check if SNMPv3 is configured (preferred over v1/v2c)
303
+ * JUNOS-MGMT-005: SNMPv3 with authPriv is recommended
304
+ */
305
+ export const hasSnmpV3 = (snmpNode: ConfigNode): boolean => {
306
+ return hasChildCommand(snmpNode, 'v3');
307
+ };
308
+
309
+ /**
310
+ * Check if NTP authentication is configured
311
+ * JUNOS-MGMT-006: NTP should have authentication
312
+ */
313
+ export const hasNtpAuthentication = (ntpNode: ConfigNode): boolean => {
314
+ return (
315
+ hasChildCommand(ntpNode, 'authentication-key') && hasChildCommand(ntpNode, 'trusted-key')
316
+ );
317
+ };
318
+
319
+ /**
320
+ * Check if login banner is configured
321
+ * JUNOS-MGMT-007: Login banner should be configured
322
+ */
323
+ export const hasLoginBanner = (systemNode: ConfigNode): boolean => {
324
+ const login = findStanza(systemNode, 'login');
325
+ if (!login) return false;
326
+ return hasChildCommand(login, 'message');
327
+ };
328
+
329
+ /**
330
+ * Check if console log-out-on-disconnect is configured
331
+ * JUNOS-MGMT-008: Console security
332
+ */
333
+ export const hasConsoleLogoutOnDisconnect = (systemNode: ConfigNode): boolean => {
334
+ const ports = findStanza(systemNode, 'ports');
335
+ if (!ports) return false;
336
+ const console = findStanza(ports, 'console');
337
+ if (!console) return false;
338
+ return hasChildCommand(console, 'log-out-on-disconnect');
339
+ };
340
+
341
+ /**
342
+ * Check if auxiliary port is disabled
343
+ * JUNOS-MGMT-008: Auxiliary port should be disabled
344
+ */
345
+ export const isAuxPortDisabled = (systemNode: ConfigNode): boolean => {
346
+ const ports = findStanza(systemNode, 'ports');
347
+ if (!ports) return false;
348
+ const aux = findStanza(ports, 'auxiliary');
349
+ if (!aux) return true; // Not configured = good
350
+ return hasChildCommand(aux, 'disable');
351
+ };
352
+
353
+ /**
354
+ * Check login retry options configuration
355
+ * JUNOS-MGMT-003: Login retry limits should be configured
356
+ */
357
+ export const hasLoginRetryOptions = (systemNode: ConfigNode): boolean => {
358
+ const login = findStanza(systemNode, 'login');
359
+ if (!login) return false;
360
+ return hasChildCommand(login, 'retry-options');
361
+ };
362
+
363
+ // ============================================================================
364
+ // JUNOS-CTRL: Control Plane Security Helpers
365
+ // ============================================================================
366
+
367
+ /**
368
+ * Check if OSPF interface has authentication configured
369
+ * JUNOS-CTRL-001: OSPF authentication
370
+ */
371
+ export const hasOspfInterfaceAuth = (interfaceNode: ConfigNode): boolean => {
372
+ return hasChildCommand(interfaceNode, 'authentication');
373
+ };
374
+
375
+ /**
376
+ * Check if OSPF area has any authenticated interfaces
377
+ */
378
+ export const hasOspfAreaAuth = (areaNode: ConfigNode): boolean => {
379
+ const interfaces = findStanzas(areaNode, /^interface/i);
380
+ return interfaces.some((iface) => hasOspfInterfaceAuth(iface));
381
+ };
382
+
383
+ /**
384
+ * Check if IS-IS interface has hello authentication
385
+ * JUNOS-CTRL-001: IS-IS authentication
386
+ */
387
+ export const hasIsisInterfaceAuth = (interfaceNode: ConfigNode): boolean => {
388
+ return hasChildCommand(interfaceNode, 'hello-authentication');
389
+ };
390
+
391
+ /**
392
+ * Check if VRRP group has authentication
393
+ * JUNOS-CTRL-002: VRRP authentication
394
+ */
395
+ export const hasVrrpAuth = (vrrpGroupNode: ConfigNode): boolean => {
396
+ return hasChildCommand(vrrpGroupNode, 'authentication');
397
+ };
398
+
399
+ /**
400
+ * Check if BFD is configured for an interface or neighbor
401
+ * JUNOS-CTRL-003: BFD should be enabled for fast failure detection
402
+ */
403
+ export const hasBfd = (node: ConfigNode): boolean => {
404
+ return hasChildCommand(node, 'bfd-liveness-detection');
405
+ };
406
+
407
+ // ============================================================================
408
+ // JUNOS-DATA: Data Plane Security Helpers
409
+ // ============================================================================
410
+
411
+ /**
412
+ * Check if interface has rpf-check (uRPF) enabled
413
+ * JUNOS-DATA-002: uRPF should be enabled
414
+ */
415
+ export const hasRpfCheck = (unitNode: ConfigNode): boolean => {
416
+ // Check under family inet
417
+ const familyInet = findStanza(unitNode, 'family inet');
418
+ if (familyInet) {
419
+ return hasChildCommand(familyInet, 'rpf-check');
420
+ }
421
+ return hasChildCommand(unitNode, 'rpf-check');
422
+ };
423
+
424
+ /**
425
+ * Check if interface has no-redirects configured
426
+ * JUNOS-DATA-001: ICMP redirects should be disabled
427
+ */
428
+ export const hasNoRedirects = (unitNode: ConfigNode): boolean => {
429
+ const familyInet = findStanza(unitNode, 'family inet');
430
+ if (familyInet) {
431
+ return hasChildCommand(familyInet, 'no-redirects');
432
+ }
433
+ return hasChildCommand(unitNode, 'no-redirects');
434
+ };
435
+
436
+ // ============================================================================
437
+ // JUNOS-BGP: BGP Security Helpers
438
+ // ============================================================================
439
+
440
+ /**
441
+ * Check if BGP neighbor has authentication-key configured
442
+ * JUNOS-BGP-001: BGP peers should have MD5 authentication
443
+ */
444
+ export const hasBgpNeighborAuth = (neighborNode: ConfigNode): boolean => {
445
+ return (
446
+ hasChildCommand(neighborNode, 'authentication-key') ||
447
+ hasChildCommand(neighborNode, 'authentication-key-chain')
448
+ );
449
+ };
450
+
451
+ /**
452
+ * Check if BGP group has authentication configured
453
+ */
454
+ export const hasBgpGroupAuth = (groupNode: ConfigNode): boolean => {
455
+ return (
456
+ hasChildCommand(groupNode, 'authentication-key') ||
457
+ hasChildCommand(groupNode, 'authentication-key-chain')
458
+ );
459
+ };
460
+
461
+ /**
462
+ * Check if BGP group has TTL security configured (GTSM)
463
+ * JUNOS-BGP-002: TTL security for eBGP peers
464
+ */
465
+ export const hasBgpTtlSecurity = (groupNode: ConfigNode): boolean => {
466
+ return hasChildCommand(groupNode, 'ttl') || hasChildCommand(groupNode, 'multihop');
467
+ };
468
+
469
+ /**
470
+ * Check if BGP group has prefix-limit configured
471
+ * JUNOS-BGP-003: Maximum prefix limits
472
+ */
473
+ export const hasBgpPrefixLimit = (groupNode: ConfigNode): boolean => {
474
+ // Check under family inet unicast
475
+ const family = findStanza(groupNode, 'family');
476
+ if (!family) {
477
+ // Also check direct children for "family inet"
478
+ for (const child of groupNode.children) {
479
+ if (startsWithIgnoreCase(child.id, 'family')) {
480
+ const hasLimit = hasChildCommand(child, 'prefix-limit');
481
+ if (hasLimit) return true;
482
+ // Check nested unicast
483
+ for (const nested of child.children) {
484
+ if (hasChildCommand(nested, 'prefix-limit')) return true;
485
+ }
486
+ }
487
+ }
488
+ return false;
489
+ }
490
+ return hasChildCommand(family, 'prefix-limit');
491
+ };
492
+
493
+ /**
494
+ * Check if BGP group has import/export policies configured
495
+ * JUNOS-BGP-004: Prefix filtering
496
+ */
497
+ export const hasBgpPolicies = (groupNode: ConfigNode): boolean => {
498
+ return hasChildCommand(groupNode, 'import') || hasChildCommand(groupNode, 'export');
499
+ };
500
+
501
+ /**
502
+ * Check if BGP group type is external (eBGP)
503
+ */
504
+ export const isBgpGroupExternal = (groupNode: ConfigNode): boolean => {
505
+ return groupNode.children.some((child) => includesIgnoreCase(child.id, 'type external'));
506
+ };
507
+
508
+ /**
509
+ * Check if graceful-restart is configured
510
+ * JUNOS-BGP-007: Graceful restart for non-disruptive failover
511
+ */
512
+ export const hasGracefulRestart = (routingOptionsNode: ConfigNode): boolean => {
513
+ return hasChildCommand(routingOptionsNode, 'graceful-restart');
514
+ };
515
+
516
+ // ============================================================================
517
+ // JUNOS-RE: Routing Engine Protection Helpers
518
+ // ============================================================================
519
+
520
+ /**
521
+ * Check if loopback interface has input filter
522
+ * JUNOS-RE-001: Protect-RE filter should be applied to lo0
523
+ */
524
+ export const hasLoopbackInputFilter = (interfacesNode: ConfigNode): boolean => {
525
+ const lo0 = findStanza(interfacesNode, 'lo0');
526
+ if (!lo0) return false;
527
+
528
+ // Check unit 0
529
+ const unit0 = findStanza(lo0, 'unit 0');
530
+ if (!unit0) return false;
531
+
532
+ // Check family inet for filter input
533
+ const familyInet = findStanza(unit0, 'family inet');
534
+ if (!familyInet) return false;
535
+
536
+ if (hasChildCommand(familyInet, 'filter input')) {
537
+ return true;
538
+ }
539
+
540
+ const filterSection = findStanza(familyInet, 'filter');
541
+ if (!filterSection) return false;
542
+
543
+ return hasChildCommand(filterSection, 'input');
544
+ };
545
+
546
+ // ============================================================================
547
+ // JUNOS-ZONE: Zone-Based Security Helpers (SRX)
548
+ // ============================================================================
549
+
550
+ /**
551
+ * Check if security zones are configured
552
+ * JUNOS-ZONE-001: Security zones should be configured
553
+ */
554
+ export const hasSecurityZones = (securityNode: ConfigNode): boolean => {
555
+ return hasChildCommand(securityNode, 'zones');
556
+ };
557
+
558
+ /**
559
+ * Check if zone has screen configured
560
+ * JUNOS-ZONE-003: Security screens should be enabled
561
+ */
562
+ export const hasZoneScreen = (zoneNode: ConfigNode): boolean => {
563
+ return hasChildCommand(zoneNode, 'screen');
564
+ };
565
+
566
+ /**
567
+ * Get zone name from a security-zone node
568
+ */
569
+ export const getZoneName = (zoneNode: ConfigNode): string | undefined => {
570
+ const match = zoneNode.id.match(/security-zone\s+(\S+)/i);
571
+ return match?.[1];
572
+ };
573
+
574
+ /**
575
+ * Check if security policies are configured
576
+ * JUNOS-ZONE-002: Security policies should be configured
577
+ */
578
+ export const hasSecurityPolicies = (securityNode: ConfigNode): boolean => {
579
+ return hasChildCommand(securityNode, 'policies');
580
+ };
581
+
582
+ /**
583
+ * Check if policy has logging enabled
584
+ */
585
+ export const hasPolicyLogging = (policyNode: ConfigNode): boolean => {
586
+ const thenStanza = findStanza(policyNode, 'then');
587
+ if (!thenStanza) return false;
588
+ return hasChildCommand(thenStanza, 'log');
589
+ };
590
+
591
+ // ============================================================================
592
+ // JUNOS-VPN: IPsec VPN Helpers
593
+ // ============================================================================
594
+
595
+ /**
596
+ * Check if IKE proposal uses strong DH group (group14 or higher)
597
+ * JUNOS-VPN-001: Use strong DH groups
598
+ */
599
+ export const hasStrongDhGroup = (proposalNode: ConfigNode): boolean => {
600
+ for (const child of proposalNode.children) {
601
+ if (includesIgnoreCase(child.id, 'dh-group')) {
602
+ // Weak groups: group1, group2, group5
603
+ if (includesIgnoreCase(child.id, 'group1') && !includesIgnoreCase(child.id, 'group14')) return false;
604
+ if (includesIgnoreCase(child.id, 'group2') && !includesIgnoreCase(child.id, 'group21')) return false;
605
+ if (includesIgnoreCase(child.id, 'group5')) return false;
606
+ // Strong groups: group14, group15, group16, group19, group20, group21
607
+ if (
608
+ includesIgnoreCase(child.id, 'group14') ||
609
+ includesIgnoreCase(child.id, 'group15') ||
610
+ includesIgnoreCase(child.id, 'group16') ||
611
+ includesIgnoreCase(child.id, 'group19') ||
612
+ includesIgnoreCase(child.id, 'group20') ||
613
+ includesIgnoreCase(child.id, 'group21')
614
+ ) {
615
+ return true;
616
+ }
617
+ }
618
+ }
619
+ return false;
620
+ };
621
+
622
+ /**
623
+ * Check if IPsec proposal uses strong encryption (AES-256)
624
+ * JUNOS-VPN-001: Use strong encryption
625
+ */
626
+ export const hasStrongEncryption = (proposalNode: ConfigNode): boolean => {
627
+ for (const child of proposalNode.children) {
628
+ if (includesIgnoreCase(child.id, 'encryption-algorithm')) {
629
+ if (includesIgnoreCase(child.id, 'aes-256') || includesIgnoreCase(child.id, 'aes-gcm-256')) {
630
+ return true;
631
+ }
632
+ }
633
+ }
634
+ return false;
635
+ };
636
+
637
+ /**
638
+ * Check if IKE gateway has dead-peer-detection
639
+ */
640
+ export const hasDpdEnabled = (gatewayNode: ConfigNode): boolean => {
641
+ return hasChildCommand(gatewayNode, 'dead-peer-detection');
642
+ };
643
+
644
+ // ============================================================================
645
+ // JUNOS-DDOS: DDoS Protection Helpers
646
+ // ============================================================================
647
+
648
+ /**
649
+ * Check if DDoS protection is configured
650
+ * JUNOS-DDOS-001: Control plane DDoS protection
651
+ */
652
+ export const hasDdosProtection = (systemNode: ConfigNode): boolean => {
653
+ return hasChildCommand(systemNode, 'ddos-protection');
654
+ };
655
+
656
+ // ============================================================================
657
+ // JUNOS-HA: High Availability Helpers
658
+ // ============================================================================
659
+
660
+ /**
661
+ * Check if GRES (Graceful Routing Engine Switchover) is configured
662
+ * JUNOS-HA-002: GRES for dual-RE systems
663
+ */
664
+ export const hasGres = (chassisNode: ConfigNode): boolean => {
665
+ const redundancy = findStanza(chassisNode, 'redundancy');
666
+ if (!redundancy) return false;
667
+ return hasChildCommand(redundancy, 'graceful-switchover');
668
+ };
669
+
670
+ /**
671
+ * Check if NSR (Nonstop Active Routing) is configured
672
+ * JUNOS-HA-003: NSR for protocol state synchronization
673
+ */
674
+ export const hasNsr = (routingOptionsNode: ConfigNode): boolean => {
675
+ return hasChildCommand(routingOptionsNode, 'nonstop-routing');
676
+ };
677
+
678
+ /**
679
+ * Check if chassis cluster is configured (SRX HA)
680
+ * JUNOS-HA-001: Chassis cluster for SRX
681
+ */
682
+ export const hasChassisCluster = (chassisNode: ConfigNode): boolean => {
683
+ return hasChildCommand(chassisNode, 'cluster');
684
+ };
685
+
686
+ // ============================================================================
687
+ // JUNOS-LOG: Logging Helpers
688
+ // ============================================================================
689
+
690
+ /**
691
+ * Check if remote syslog host is configured
692
+ * JUNOS-LOG-001: Remote syslog should be configured
693
+ */
694
+ export const hasRemoteSyslog = (syslogNode: ConfigNode): boolean => {
695
+ return hasChildCommand(syslogNode, 'host');
696
+ };
697
+
698
+ /**
699
+ * Check if syslog file archiving is configured
700
+ */
701
+ export const hasSyslogArchive = (syslogNode: ConfigNode): boolean => {
702
+ return hasChildCommand(syslogNode, 'archive');
703
+ };
704
+
705
+ /**
706
+ * Check if security logging is configured (SRX)
707
+ * JUNOS-LOG-002: Security logging
708
+ */
709
+ export const hasSecurityLogging = (securityNode: ConfigNode): boolean => {
710
+ return hasChildCommand(securityNode, 'log');
711
+ };
712
+
713
+ /**
714
+ * Check if J-Flow/NetFlow sampling is configured
715
+ * JUNOS-LOG-003: Flow monitoring
716
+ */
717
+ export const hasFlowMonitoring = (forwardingOptionsNode: ConfigNode): boolean => {
718
+ return hasChildCommand(forwardingOptionsNode, 'sampling');
719
+ };
720
+
721
+ // ============================================================================
722
+ // RPKI Helpers
723
+ // ============================================================================
724
+
725
+ /**
726
+ * Check if RPKI validation is configured
727
+ * JUNOS-BGP-006: RPKI origin validation
728
+ */
729
+ export const hasRpkiValidation = (routingOptionsNode: ConfigNode): boolean => {
730
+ return hasChildCommand(routingOptionsNode, 'validation');
731
+ };
732
+
733
+ // ============================================================================
734
+ // Security Screens Helpers
735
+ // ============================================================================
736
+
737
+ /**
738
+ * Check if screen has TCP protections
739
+ */
740
+ export const hasScreenTcpProtection = (screenNode: ConfigNode): boolean => {
741
+ return hasChildCommand(screenNode, 'tcp');
742
+ };
743
+
744
+ /**
745
+ * Check if screen has IP protections
746
+ */
747
+ export const hasScreenIpProtection = (screenNode: ConfigNode): boolean => {
748
+ return hasChildCommand(screenNode, 'ip');
749
+ };
750
+
751
+ /**
752
+ * Check if screen has ICMP protections
753
+ */
754
+ export const hasScreenIcmpProtection = (screenNode: ConfigNode): boolean => {
755
+ return hasChildCommand(screenNode, 'icmp');
756
+ };