@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,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
|
+
};
|