@sentriflow/core 0.2.0 → 0.2.1
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/README.md +66 -0
- package/package.json +7 -2
- package/src/constants.ts +4 -1
- package/src/engine/RuleExecutor.ts +7 -1
- package/src/grx2-loader/GRX2ExtendedLoader.ts +645 -0
- package/src/grx2-loader/MachineId.ts +51 -0
- package/src/grx2-loader/index.ts +47 -0
- package/src/grx2-loader/types.ts +277 -0
- package/src/helpers/arista/helpers.ts +165 -95
- package/src/helpers/aruba/helpers.ts +11 -5
- package/src/helpers/cisco/helpers.ts +16 -8
- package/src/helpers/common/helpers.ts +19 -13
- package/src/helpers/common/validation.ts +6 -6
- package/src/helpers/cumulus/helpers.ts +11 -7
- package/src/helpers/extreme/helpers.ts +8 -5
- package/src/helpers/fortinet/helpers.ts +16 -6
- package/src/helpers/huawei/helpers.ts +112 -61
- package/src/helpers/juniper/helpers.ts +36 -20
- package/src/helpers/mikrotik/helpers.ts +10 -3
- package/src/helpers/nokia/helpers.ts +71 -42
- package/src/helpers/paloalto/helpers.ts +51 -41
- package/src/helpers/vyos/helpers.ts +58 -31
- package/src/index.ts +3 -0
- package/src/pack-loader/PackLoader.ts +29 -4
- package/src/parser/SchemaAwareParser.ts +84 -0
- package/src/parser/vendors/cisco-ios.ts +19 -5
- package/src/parser/vendors/cisco-nxos.ts +10 -2
|
@@ -52,8 +52,9 @@ export const hasPlaintextPassword = (node: ConfigNode): boolean => {
|
|
|
52
52
|
* @returns true if service password-encryption is configured
|
|
53
53
|
*/
|
|
54
54
|
export const hasServicePasswordEncryption = (ast: ConfigNode[]): boolean => {
|
|
55
|
+
if (!Array.isArray(ast)) return false;
|
|
55
56
|
return ast.some((node) =>
|
|
56
|
-
equalsIgnoreCase(node
|
|
57
|
+
equalsIgnoreCase(node?.id ?? '', 'service password-encryption')
|
|
57
58
|
);
|
|
58
59
|
};
|
|
59
60
|
|
|
@@ -63,8 +64,9 @@ export const hasServicePasswordEncryption = (ast: ConfigNode[]): boolean => {
|
|
|
63
64
|
* @returns true if SSH v2 is configured
|
|
64
65
|
*/
|
|
65
66
|
export const hasSshVersion2 = (ast: ConfigNode[]): boolean => {
|
|
67
|
+
if (!Array.isArray(ast)) return false;
|
|
66
68
|
return ast.some((node) =>
|
|
67
|
-
/^ip\s+ssh\s+version\s+2/i.test(node
|
|
69
|
+
/^ip\s+ssh\s+version\s+2/i.test(node?.id ?? '')
|
|
68
70
|
);
|
|
69
71
|
};
|
|
70
72
|
|
|
@@ -74,13 +76,14 @@ export const hasSshVersion2 = (ast: ConfigNode[]): boolean => {
|
|
|
74
76
|
* @returns Array of weak ciphers found
|
|
75
77
|
*/
|
|
76
78
|
export const getWeakSshCiphers = (ast: ConfigNode[]): string[] => {
|
|
79
|
+
if (!Array.isArray(ast)) return [];
|
|
77
80
|
const weakCiphers = ['3des-cbc', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'blowfish-cbc'];
|
|
78
81
|
const found: string[] = [];
|
|
79
82
|
|
|
80
83
|
for (const node of ast) {
|
|
81
|
-
if (/^ip\s+ssh\s+ciphers/i.test(node
|
|
84
|
+
if (/^ip\s+ssh\s+ciphers/i.test(node?.id ?? '')) {
|
|
82
85
|
for (const cipher of weakCiphers) {
|
|
83
|
-
if (includesIgnoreCase(node
|
|
86
|
+
if (includesIgnoreCase(node?.id ?? '', cipher)) {
|
|
84
87
|
found.push(cipher);
|
|
85
88
|
}
|
|
86
89
|
}
|
|
@@ -95,21 +98,22 @@ export const getWeakSshCiphers = (ast: ConfigNode[]): string[] => {
|
|
|
95
98
|
* @returns true if telnet is properly disabled
|
|
96
99
|
*/
|
|
97
100
|
export const isTelnetDisabled = (ast: ConfigNode[]): boolean => {
|
|
101
|
+
if (!Array.isArray(ast)) return true;
|
|
98
102
|
// Check for 'no management telnet' or management telnet with shutdown
|
|
99
103
|
const noMgmtTelnet = ast.some((node) =>
|
|
100
|
-
/^no\s+management\s+telnet/i.test(node.id)
|
|
104
|
+
node?.id && /^no\s+management\s+telnet/i.test(node.id)
|
|
101
105
|
);
|
|
102
106
|
|
|
103
107
|
if (noMgmtTelnet) return true;
|
|
104
108
|
|
|
105
109
|
// Check if management telnet section exists and is shutdown
|
|
106
110
|
const mgmtTelnet = ast.find((node) =>
|
|
107
|
-
/^management\s+telnet/i.test(node.id)
|
|
111
|
+
node?.id && /^management\s+telnet/i.test(node.id)
|
|
108
112
|
);
|
|
109
113
|
|
|
110
|
-
if (mgmtTelnet) {
|
|
114
|
+
if (mgmtTelnet?.children) {
|
|
111
115
|
return mgmtTelnet.children.some((child) =>
|
|
112
|
-
equalsIgnoreCase(child.id, 'shutdown')
|
|
116
|
+
child?.id && equalsIgnoreCase(child.id, 'shutdown')
|
|
113
117
|
);
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -123,11 +127,12 @@ export const isTelnetDisabled = (ast: ConfigNode[]): boolean => {
|
|
|
123
127
|
* @returns true if HTTP server is disabled
|
|
124
128
|
*/
|
|
125
129
|
export const isHttpServerDisabled = (ast: ConfigNode[]): boolean => {
|
|
130
|
+
if (!Array.isArray(ast)) return true;
|
|
126
131
|
const hasNoHttp = ast.some((node) =>
|
|
127
|
-
/^no\s+ip\s+http\s+server/i.test(node
|
|
132
|
+
/^no\s+ip\s+http\s+server/i.test(node?.id ?? '')
|
|
128
133
|
);
|
|
129
134
|
const hasHttp = ast.some((node) =>
|
|
130
|
-
/^ip\s+http\s+server/i.test(node
|
|
135
|
+
/^ip\s+http\s+server/i.test(node?.id ?? '') && !/^no\s+/i.test(node?.id ?? '')
|
|
131
136
|
);
|
|
132
137
|
return hasNoHttp || !hasHttp;
|
|
133
138
|
};
|
|
@@ -138,12 +143,13 @@ export const isHttpServerDisabled = (ast: ConfigNode[]): boolean => {
|
|
|
138
143
|
* @returns Array of insecure community configurations found
|
|
139
144
|
*/
|
|
140
145
|
export const getInsecureSnmpCommunities = (ast: ConfigNode[]): string[] => {
|
|
146
|
+
if (!Array.isArray(ast)) return [];
|
|
141
147
|
const insecure: string[] = [];
|
|
142
148
|
const defaultCommunities = ['public', 'private', 'community'];
|
|
143
149
|
|
|
144
150
|
for (const node of ast) {
|
|
145
|
-
if (/^snmp-server\s+community\s+/i.test(node
|
|
146
|
-
const match = node
|
|
151
|
+
if (/^snmp-server\s+community\s+/i.test(node?.id ?? '')) {
|
|
152
|
+
const match = node?.id?.match(/snmp-server\s+community\s+(\S+)/i);
|
|
147
153
|
if (match?.[1]) {
|
|
148
154
|
const community = match[1];
|
|
149
155
|
if (defaultCommunities.some((dc) => equalsIgnoreCase(community, dc))) {
|
|
@@ -163,9 +169,10 @@ export const getInsecureSnmpCommunities = (ast: ConfigNode[]): string[] => {
|
|
|
163
169
|
* @returns true if SNMPv3 with priv mode is configured
|
|
164
170
|
*/
|
|
165
171
|
export const hasSnmpV3AuthPriv = (ast: ConfigNode[]): boolean => {
|
|
172
|
+
if (!Array.isArray(ast)) return false;
|
|
166
173
|
return ast.some((node) =>
|
|
167
|
-
/^snmp-server\s+group\s+\S+\s+v3\s+priv/i.test(node
|
|
168
|
-
/^snmp-server\s+user\s+\S+\s+\S+\s+v3\s+auth\s+\S+\s+\S+\s+priv/i.test(node
|
|
174
|
+
/^snmp-server\s+group\s+\S+\s+v3\s+priv/i.test(node?.id ?? '') ||
|
|
175
|
+
/^snmp-server\s+user\s+\S+\s+\S+\s+v3\s+auth\s+\S+\s+\S+\s+priv/i.test(node?.id ?? '')
|
|
169
176
|
);
|
|
170
177
|
};
|
|
171
178
|
|
|
@@ -175,11 +182,13 @@ export const hasSnmpV3AuthPriv = (ast: ConfigNode[]): boolean => {
|
|
|
175
182
|
* @returns true if NTP authentication is configured
|
|
176
183
|
*/
|
|
177
184
|
export const hasNtpAuthentication = (ast: ConfigNode[]): boolean => {
|
|
185
|
+
// Guard against collision: other vendors pass single node, Arista expects array
|
|
186
|
+
if (!Array.isArray(ast)) return false;
|
|
178
187
|
const hasAuthenticate = ast.some((node) =>
|
|
179
|
-
/^ntp\s+authenticate$/i.test(node
|
|
188
|
+
/^ntp\s+authenticate$/i.test(node?.id ?? '')
|
|
180
189
|
);
|
|
181
190
|
const hasTrustedKey = ast.some((node) =>
|
|
182
|
-
/^ntp\s+trusted-key\s+/i.test(node
|
|
191
|
+
/^ntp\s+trusted-key\s+/i.test(node?.id ?? '')
|
|
183
192
|
);
|
|
184
193
|
return hasAuthenticate && hasTrustedKey;
|
|
185
194
|
};
|
|
@@ -190,8 +199,9 @@ export const hasNtpAuthentication = (ast: ConfigNode[]): boolean => {
|
|
|
190
199
|
* @returns true if AAA authentication login is configured
|
|
191
200
|
*/
|
|
192
201
|
export const hasAaaAuthenticationLogin = (ast: ConfigNode[]): boolean => {
|
|
202
|
+
if (!Array.isArray(ast)) return false;
|
|
193
203
|
return ast.some((node) =>
|
|
194
|
-
/^aaa\s+authentication\s+login\s+/i.test(node
|
|
204
|
+
/^aaa\s+authentication\s+login\s+/i.test(node?.id ?? '')
|
|
195
205
|
);
|
|
196
206
|
};
|
|
197
207
|
|
|
@@ -201,8 +211,9 @@ export const hasAaaAuthenticationLogin = (ast: ConfigNode[]): boolean => {
|
|
|
201
211
|
* @returns true if TACACS+ server is configured
|
|
202
212
|
*/
|
|
203
213
|
export const hasTacacsServer = (ast: ConfigNode[]): boolean => {
|
|
214
|
+
if (!Array.isArray(ast)) return false;
|
|
204
215
|
return ast.some((node) =>
|
|
205
|
-
/^tacacs-server\s+host\s+/i.test(node
|
|
216
|
+
/^tacacs-server\s+host\s+/i.test(node?.id ?? '')
|
|
206
217
|
);
|
|
207
218
|
};
|
|
208
219
|
|
|
@@ -212,8 +223,9 @@ export const hasTacacsServer = (ast: ConfigNode[]): boolean => {
|
|
|
212
223
|
* @returns true if AAA accounting is configured
|
|
213
224
|
*/
|
|
214
225
|
export const hasAaaAccounting = (ast: ConfigNode[]): boolean => {
|
|
226
|
+
if (!Array.isArray(ast)) return false;
|
|
215
227
|
return ast.some((node) =>
|
|
216
|
-
/^aaa\s+accounting\s+/i.test(node
|
|
228
|
+
/^aaa\s+accounting\s+/i.test(node?.id ?? '')
|
|
217
229
|
);
|
|
218
230
|
};
|
|
219
231
|
|
|
@@ -223,9 +235,10 @@ export const hasAaaAccounting = (ast: ConfigNode[]): boolean => {
|
|
|
223
235
|
* @returns true if management VRF is properly configured
|
|
224
236
|
*/
|
|
225
237
|
export const hasManagementVrf = (ast: ConfigNode[]): boolean => {
|
|
238
|
+
if (!Array.isArray(ast)) return false;
|
|
226
239
|
return ast.some((node) =>
|
|
227
|
-
/^vrf\s+instance\s+MGMT/i.test(node
|
|
228
|
-
/^vrf\s+instance\s+management/i.test(node
|
|
240
|
+
/^vrf\s+instance\s+MGMT/i.test(node?.id ?? '') ||
|
|
241
|
+
/^vrf\s+instance\s+management/i.test(node?.id ?? '')
|
|
229
242
|
);
|
|
230
243
|
};
|
|
231
244
|
|
|
@@ -235,6 +248,7 @@ export const hasManagementVrf = (ast: ConfigNode[]): boolean => {
|
|
|
235
248
|
* @returns Array of information disclosure issues found
|
|
236
249
|
*/
|
|
237
250
|
export const getBannerInfoDisclosure = (ast: ConfigNode[]): string[] => {
|
|
251
|
+
if (!Array.isArray(ast)) return [];
|
|
238
252
|
const issues: string[] = [];
|
|
239
253
|
const sensitivePatterns = [
|
|
240
254
|
{ pattern: /version\s+\d+/i, desc: 'software version' },
|
|
@@ -245,8 +259,8 @@ export const getBannerInfoDisclosure = (ast: ConfigNode[]): string[] => {
|
|
|
245
259
|
];
|
|
246
260
|
|
|
247
261
|
for (const node of ast) {
|
|
248
|
-
if (/^banner\s+(login|motd)/i.test(node
|
|
249
|
-
const bannerText = node
|
|
262
|
+
if (/^banner\s+(login|motd)/i.test(node?.id ?? '')) {
|
|
263
|
+
const bannerText = node?.rawText ?? node?.id ?? '';
|
|
250
264
|
for (const { pattern, desc } of sensitivePatterns) {
|
|
251
265
|
if (pattern.test(bannerText)) {
|
|
252
266
|
issues.push(`Banner contains ${desc}`);
|
|
@@ -263,10 +277,12 @@ export const getBannerInfoDisclosure = (ast: ConfigNode[]): string[] => {
|
|
|
263
277
|
* @returns The timeout value in minutes, or undefined if not set
|
|
264
278
|
*/
|
|
265
279
|
export const getConsoleIdleTimeout = (ast: ConfigNode[]): number | undefined => {
|
|
280
|
+
if (!Array.isArray(ast)) return undefined;
|
|
266
281
|
for (const node of ast) {
|
|
267
|
-
if (/^management\s+console/i.test(node.id)) {
|
|
282
|
+
if (node?.id && /^management\s+console/i.test(node.id)) {
|
|
283
|
+
if (!node?.children) continue;
|
|
268
284
|
for (const child of node.children) {
|
|
269
|
-
const match = child
|
|
285
|
+
const match = child?.id?.match(/idle-timeout\s+(\d+)/i);
|
|
270
286
|
if (match?.[1]) {
|
|
271
287
|
return parseInteger(match[1]) ?? undefined;
|
|
272
288
|
}
|
|
@@ -282,9 +298,10 @@ export const getConsoleIdleTimeout = (ast: ConfigNode[]): number | undefined =>
|
|
|
282
298
|
* @returns true if ZTP is disabled
|
|
283
299
|
*/
|
|
284
300
|
export const isZtpDisabled = (ast: ConfigNode[]): boolean => {
|
|
301
|
+
if (!Array.isArray(ast)) return false;
|
|
285
302
|
return ast.some((node) =>
|
|
286
|
-
/^no\s+zerotouch\s+enable/i.test(node
|
|
287
|
-
equalsIgnoreCase(node
|
|
303
|
+
/^no\s+zerotouch\s+enable/i.test(node?.id ?? '') ||
|
|
304
|
+
equalsIgnoreCase(node?.id ?? '', 'zerotouch cancel')
|
|
288
305
|
);
|
|
289
306
|
};
|
|
290
307
|
|
|
@@ -298,8 +315,9 @@ export const isZtpDisabled = (ast: ConfigNode[]): boolean => {
|
|
|
298
315
|
* @returns true if system control-plane ACL is configured
|
|
299
316
|
*/
|
|
300
317
|
export const hasControlPlaneAcl = (ast: ConfigNode[]): boolean => {
|
|
318
|
+
if (!Array.isArray(ast)) return false;
|
|
301
319
|
return ast.some((node) =>
|
|
302
|
-
/^system\s+control-plane/i.test(node
|
|
320
|
+
/^system\s+control-plane/i.test(node?.id ?? '')
|
|
303
321
|
);
|
|
304
322
|
};
|
|
305
323
|
|
|
@@ -309,8 +327,9 @@ export const hasControlPlaneAcl = (ast: ConfigNode[]): boolean => {
|
|
|
309
327
|
* @returns true if CoPP policy is customized
|
|
310
328
|
*/
|
|
311
329
|
export const hasCoppPolicy = (ast: ConfigNode[]): boolean => {
|
|
330
|
+
if (!Array.isArray(ast)) return false;
|
|
312
331
|
return ast.some((node) =>
|
|
313
|
-
/^policy-map\s+type\s+copp/i.test(node
|
|
332
|
+
/^policy-map\s+type\s+copp/i.test(node?.id ?? '')
|
|
314
333
|
);
|
|
315
334
|
};
|
|
316
335
|
|
|
@@ -320,8 +339,9 @@ export const hasCoppPolicy = (ast: ConfigNode[]): boolean => {
|
|
|
320
339
|
* @returns true if ip redirects are disabled
|
|
321
340
|
*/
|
|
322
341
|
export const hasNoIpRedirects = (interfaceNode: ConfigNode): boolean => {
|
|
342
|
+
if (!interfaceNode?.children) return false;
|
|
323
343
|
return interfaceNode.children.some((child) =>
|
|
324
|
-
/^no\s+ip\s+redirects/i.test(child.id)
|
|
344
|
+
child?.id && /^no\s+ip\s+redirects/i.test(child.id)
|
|
325
345
|
);
|
|
326
346
|
};
|
|
327
347
|
|
|
@@ -331,8 +351,9 @@ export const hasNoIpRedirects = (interfaceNode: ConfigNode): boolean => {
|
|
|
331
351
|
* @returns true if ip unreachables are disabled
|
|
332
352
|
*/
|
|
333
353
|
export const hasNoIpUnreachables = (interfaceNode: ConfigNode): boolean => {
|
|
354
|
+
if (!interfaceNode?.children) return false;
|
|
334
355
|
return interfaceNode.children.some((child) =>
|
|
335
|
-
/^no\s+ip\s+unreachables/i.test(child.id)
|
|
356
|
+
child?.id && /^no\s+ip\s+unreachables/i.test(child.id)
|
|
336
357
|
);
|
|
337
358
|
};
|
|
338
359
|
|
|
@@ -342,17 +363,18 @@ export const hasNoIpUnreachables = (interfaceNode: ConfigNode): boolean => {
|
|
|
342
363
|
* @returns true if authentication is configured
|
|
343
364
|
*/
|
|
344
365
|
export const hasRoutingProtocolAuth = (routerNode: ConfigNode): boolean => {
|
|
366
|
+
if (!routerNode?.id || !routerNode?.children) return false;
|
|
345
367
|
// Check for OSPF authentication
|
|
346
368
|
if (/^router\s+ospf/i.test(routerNode.id)) {
|
|
347
369
|
return routerNode.children.some((child) =>
|
|
348
|
-
/authentication/i.test(child.id)
|
|
370
|
+
child?.id && /authentication/i.test(child.id)
|
|
349
371
|
);
|
|
350
372
|
}
|
|
351
373
|
|
|
352
374
|
// Check for IS-IS authentication
|
|
353
375
|
if (/^router\s+isis/i.test(routerNode.id)) {
|
|
354
376
|
return routerNode.children.some((child) =>
|
|
355
|
-
/authentication/i.test(child.id)
|
|
377
|
+
child?.id && /authentication/i.test(child.id)
|
|
356
378
|
);
|
|
357
379
|
}
|
|
358
380
|
|
|
@@ -365,8 +387,9 @@ export const hasRoutingProtocolAuth = (routerNode: ConfigNode): boolean => {
|
|
|
365
387
|
* @returns true if BFD is configured
|
|
366
388
|
*/
|
|
367
389
|
export const hasBfd = (ast: ConfigNode[]): boolean => {
|
|
390
|
+
if (!Array.isArray(ast)) return false;
|
|
368
391
|
return ast.some((node) =>
|
|
369
|
-
/^bfd\s+/i.test(node
|
|
392
|
+
/^bfd\s+/i.test(node?.id ?? '')
|
|
370
393
|
);
|
|
371
394
|
};
|
|
372
395
|
|
|
@@ -380,15 +403,16 @@ export const hasBfd = (ast: ConfigNode[]): boolean => {
|
|
|
380
403
|
* @returns Object with storm control status for each type
|
|
381
404
|
*/
|
|
382
405
|
export const getStormControlStatus = (interfaceNode: ConfigNode): { broadcast: boolean; multicast: boolean; unicast: boolean } => {
|
|
406
|
+
if (!interfaceNode?.children) return { broadcast: false, multicast: false, unicast: false };
|
|
383
407
|
return {
|
|
384
408
|
broadcast: interfaceNode.children.some((child) =>
|
|
385
|
-
/^storm-control\s+broadcast/i.test(child.id)
|
|
409
|
+
child?.id && /^storm-control\s+broadcast/i.test(child.id)
|
|
386
410
|
),
|
|
387
411
|
multicast: interfaceNode.children.some((child) =>
|
|
388
|
-
/^storm-control\s+multicast/i.test(child.id)
|
|
412
|
+
child?.id && /^storm-control\s+multicast/i.test(child.id)
|
|
389
413
|
),
|
|
390
414
|
unicast: interfaceNode.children.some((child) =>
|
|
391
|
-
/^storm-control\s+unknown-unicast/i.test(child.id)
|
|
415
|
+
child?.id && /^storm-control\s+unknown-unicast/i.test(child.id)
|
|
392
416
|
),
|
|
393
417
|
};
|
|
394
418
|
};
|
|
@@ -399,8 +423,9 @@ export const getStormControlStatus = (interfaceNode: ConfigNode): { broadcast: b
|
|
|
399
423
|
* @returns true if DHCP snooping is configured
|
|
400
424
|
*/
|
|
401
425
|
export const hasDhcpSnooping = (ast: ConfigNode[]): boolean => {
|
|
426
|
+
if (!Array.isArray(ast)) return false;
|
|
402
427
|
return ast.some((node) =>
|
|
403
|
-
/^ip\s+dhcp\s+snooping$/i.test(node
|
|
428
|
+
/^ip\s+dhcp\s+snooping$/i.test(node?.id ?? '')
|
|
404
429
|
);
|
|
405
430
|
};
|
|
406
431
|
|
|
@@ -410,8 +435,9 @@ export const hasDhcpSnooping = (ast: ConfigNode[]): boolean => {
|
|
|
410
435
|
* @returns true if interface is DHCP snooping trusted
|
|
411
436
|
*/
|
|
412
437
|
export const isDhcpSnoopingTrust = (interfaceNode: ConfigNode): boolean => {
|
|
438
|
+
if (!interfaceNode?.children) return false;
|
|
413
439
|
return interfaceNode.children.some((child) =>
|
|
414
|
-
/^ip\s+dhcp\s+snooping\s+trust/i.test(child.id)
|
|
440
|
+
child?.id && /^ip\s+dhcp\s+snooping\s+trust/i.test(child.id)
|
|
415
441
|
);
|
|
416
442
|
};
|
|
417
443
|
|
|
@@ -421,8 +447,9 @@ export const isDhcpSnoopingTrust = (interfaceNode: ConfigNode): boolean => {
|
|
|
421
447
|
* @returns true if DAI is configured
|
|
422
448
|
*/
|
|
423
449
|
export const hasDynamicArpInspection = (ast: ConfigNode[]): boolean => {
|
|
450
|
+
if (!Array.isArray(ast)) return false;
|
|
424
451
|
return ast.some((node) =>
|
|
425
|
-
/^ip\s+arp\s+inspection\s+vlan/i.test(node
|
|
452
|
+
/^ip\s+arp\s+inspection\s+vlan/i.test(node?.id ?? '')
|
|
426
453
|
);
|
|
427
454
|
};
|
|
428
455
|
|
|
@@ -432,8 +459,9 @@ export const hasDynamicArpInspection = (ast: ConfigNode[]): boolean => {
|
|
|
432
459
|
* @returns true if interface is ARP inspection trusted
|
|
433
460
|
*/
|
|
434
461
|
export const isArpInspectionTrust = (interfaceNode: ConfigNode): boolean => {
|
|
462
|
+
if (!interfaceNode?.children) return false;
|
|
435
463
|
return interfaceNode.children.some((child) =>
|
|
436
|
-
/^ip\s+arp\s+inspection\s+trust/i.test(child.id)
|
|
464
|
+
child?.id && /^ip\s+arp\s+inspection\s+trust/i.test(child.id)
|
|
437
465
|
);
|
|
438
466
|
};
|
|
439
467
|
|
|
@@ -443,8 +471,9 @@ export const isArpInspectionTrust = (interfaceNode: ConfigNode): boolean => {
|
|
|
443
471
|
* @returns true if IP verify source is configured
|
|
444
472
|
*/
|
|
445
473
|
export const hasIpSourceGuard = (interfaceNode: ConfigNode): boolean => {
|
|
474
|
+
if (!interfaceNode?.children) return false;
|
|
446
475
|
return interfaceNode.children.some((child) =>
|
|
447
|
-
/^ip\s+verify\s+source/i.test(child.id)
|
|
476
|
+
child?.id && /^ip\s+verify\s+source/i.test(child.id)
|
|
448
477
|
);
|
|
449
478
|
};
|
|
450
479
|
|
|
@@ -454,8 +483,9 @@ export const hasIpSourceGuard = (interfaceNode: ConfigNode): boolean => {
|
|
|
454
483
|
* @returns true if port security is configured
|
|
455
484
|
*/
|
|
456
485
|
export const hasPortSecurity = (interfaceNode: ConfigNode): boolean => {
|
|
486
|
+
if (!interfaceNode?.children) return false;
|
|
457
487
|
return interfaceNode.children.some((child) =>
|
|
458
|
-
/^switchport\s+port-security/i.test(child.id)
|
|
488
|
+
child?.id && /^switchport\s+port-security/i.test(child.id)
|
|
459
489
|
);
|
|
460
490
|
};
|
|
461
491
|
|
|
@@ -471,17 +501,18 @@ export const hasPortSecurity = (interfaceNode: ConfigNode): boolean => {
|
|
|
471
501
|
*/
|
|
472
502
|
export const getBgpNeighborsWithoutAuth = (routerBgpNode: ConfigNode, neighborIp?: string): string[] => {
|
|
473
503
|
const neighborsWithoutAuth: string[] = [];
|
|
504
|
+
if (!routerBgpNode?.children) return neighborsWithoutAuth;
|
|
474
505
|
const neighborConfigs = new Map<string, { hasPassword: boolean }>();
|
|
475
506
|
|
|
476
507
|
for (const child of routerBgpNode.children) {
|
|
477
|
-
const neighborMatch = child
|
|
508
|
+
const neighborMatch = child?.id?.match(/^neighbor\s+(\S+)/i);
|
|
478
509
|
if (neighborMatch?.[1]) {
|
|
479
510
|
const ip = neighborMatch[1];
|
|
480
511
|
if (!neighborConfigs.has(ip)) {
|
|
481
512
|
neighborConfigs.set(ip, { hasPassword: false });
|
|
482
513
|
}
|
|
483
514
|
|
|
484
|
-
if (/password/i.test(child.id)) {
|
|
515
|
+
if (child?.id && /password/i.test(child.id)) {
|
|
485
516
|
const config = neighborConfigs.get(ip);
|
|
486
517
|
if (config) {
|
|
487
518
|
config.hasPassword = true;
|
|
@@ -508,11 +539,12 @@ export const getBgpNeighborsWithoutAuth = (routerBgpNode: ConfigNode, neighborIp
|
|
|
508
539
|
*/
|
|
509
540
|
export const getBgpNeighborsWithoutTtlSecurity = (routerBgpNode: ConfigNode): string[] => {
|
|
510
541
|
const neighborsWithoutTtl: string[] = [];
|
|
542
|
+
if (!routerBgpNode?.children) return neighborsWithoutTtl;
|
|
511
543
|
const neighborConfigs = new Map<string, { hasTtl: boolean; isEbgp: boolean }>();
|
|
512
|
-
const localAs = routerBgpNode
|
|
544
|
+
const localAs = routerBgpNode?.id?.match(/router\s+bgp\s+(\d+)/i)?.[1];
|
|
513
545
|
|
|
514
546
|
for (const child of routerBgpNode.children) {
|
|
515
|
-
const neighborMatch = child
|
|
547
|
+
const neighborMatch = child?.id?.match(/^neighbor\s+(\S+)\s+remote-as\s+(\d+)/i);
|
|
516
548
|
if (neighborMatch?.[1] && neighborMatch?.[2]) {
|
|
517
549
|
const ip = neighborMatch[1];
|
|
518
550
|
const remoteAs = neighborMatch[2];
|
|
@@ -522,7 +554,7 @@ export const getBgpNeighborsWithoutTtlSecurity = (routerBgpNode: ConfigNode): st
|
|
|
522
554
|
});
|
|
523
555
|
}
|
|
524
556
|
|
|
525
|
-
const ttlMatch = child
|
|
557
|
+
const ttlMatch = child?.id?.match(/^neighbor\s+(\S+)\s+ttl\s+maximum-hops/i);
|
|
526
558
|
if (ttlMatch?.[1]) {
|
|
527
559
|
const config = neighborConfigs.get(ttlMatch[1]);
|
|
528
560
|
if (config) {
|
|
@@ -547,15 +579,16 @@ export const getBgpNeighborsWithoutTtlSecurity = (routerBgpNode: ConfigNode): st
|
|
|
547
579
|
*/
|
|
548
580
|
export const getBgpNeighborsWithoutMaxRoutes = (routerBgpNode: ConfigNode): string[] => {
|
|
549
581
|
const neighborsWithoutMax: string[] = [];
|
|
582
|
+
if (!routerBgpNode?.children) return neighborsWithoutMax;
|
|
550
583
|
const neighborConfigs = new Map<string, boolean>();
|
|
551
584
|
|
|
552
585
|
for (const child of routerBgpNode.children) {
|
|
553
|
-
const neighborMatch = child
|
|
586
|
+
const neighborMatch = child?.id?.match(/^neighbor\s+(\S+)\s+remote-as/i);
|
|
554
587
|
if (neighborMatch?.[1]) {
|
|
555
588
|
neighborConfigs.set(neighborMatch[1], false);
|
|
556
589
|
}
|
|
557
590
|
|
|
558
|
-
const maxMatch = child
|
|
591
|
+
const maxMatch = child?.id?.match(/^neighbor\s+(\S+)\s+maximum-routes/i);
|
|
559
592
|
if (maxMatch?.[1]) {
|
|
560
593
|
neighborConfigs.set(maxMatch[1], true);
|
|
561
594
|
}
|
|
@@ -576,8 +609,9 @@ export const getBgpNeighborsWithoutMaxRoutes = (routerBgpNode: ConfigNode): stri
|
|
|
576
609
|
* @returns true if graceful restart is configured
|
|
577
610
|
*/
|
|
578
611
|
export const hasBgpGracefulRestart = (routerBgpNode: ConfigNode): boolean => {
|
|
612
|
+
if (!routerBgpNode?.children) return false;
|
|
579
613
|
return routerBgpNode.children.some((child) =>
|
|
580
|
-
/^bgp\s+graceful-restart/i.test(child.id)
|
|
614
|
+
child?.id && /^bgp\s+graceful-restart/i.test(child.id)
|
|
581
615
|
);
|
|
582
616
|
};
|
|
583
617
|
|
|
@@ -587,8 +621,9 @@ export const hasBgpGracefulRestart = (routerBgpNode: ConfigNode): boolean => {
|
|
|
587
621
|
* @returns true if log-neighbor-changes is configured
|
|
588
622
|
*/
|
|
589
623
|
export const hasBgpLogNeighborChanges = (routerBgpNode: ConfigNode): boolean => {
|
|
624
|
+
if (!routerBgpNode?.children) return false;
|
|
590
625
|
return routerBgpNode.children.some((child) =>
|
|
591
|
-
/^bgp\s+log-neighbor-changes/i.test(child.id)
|
|
626
|
+
child?.id && /^bgp\s+log-neighbor-changes/i.test(child.id)
|
|
592
627
|
);
|
|
593
628
|
};
|
|
594
629
|
|
|
@@ -602,8 +637,9 @@ export const hasBgpLogNeighborChanges = (routerBgpNode: ConfigNode): boolean =>
|
|
|
602
637
|
* @returns true if RPKI cache is configured
|
|
603
638
|
*/
|
|
604
639
|
export const hasRpkiConfiguration = (routerBgpNode: ConfigNode): boolean => {
|
|
640
|
+
if (!routerBgpNode?.children) return false;
|
|
605
641
|
return routerBgpNode.children.some((child) =>
|
|
606
|
-
/^rpki\s+cache/i.test(child.id)
|
|
642
|
+
child?.id && /^rpki\s+cache/i.test(child.id)
|
|
607
643
|
);
|
|
608
644
|
};
|
|
609
645
|
|
|
@@ -613,8 +649,9 @@ export const hasRpkiConfiguration = (routerBgpNode: ConfigNode): boolean => {
|
|
|
613
649
|
* @returns true if origin validation is configured
|
|
614
650
|
*/
|
|
615
651
|
export const hasRpkiOriginValidation = (routerBgpNode: ConfigNode): boolean => {
|
|
652
|
+
if (!routerBgpNode?.children) return false;
|
|
616
653
|
return routerBgpNode.children.some((child) =>
|
|
617
|
-
/^rpki\s+origin-validation/i.test(child.id)
|
|
654
|
+
child?.id && /^rpki\s+origin-validation/i.test(child.id)
|
|
618
655
|
);
|
|
619
656
|
};
|
|
620
657
|
|
|
@@ -628,11 +665,12 @@ export const hasRpkiOriginValidation = (routerBgpNode: ConfigNode): boolean => {
|
|
|
628
665
|
* @returns Object with uRPF mode if configured
|
|
629
666
|
*/
|
|
630
667
|
export const getUrpfMode = (interfaceNode: ConfigNode): { enabled: boolean; mode?: 'strict' | 'loose' } => {
|
|
668
|
+
if (!interfaceNode?.children) return { enabled: false };
|
|
631
669
|
for (const child of interfaceNode.children) {
|
|
632
|
-
if (/^ip\s+verify\s+unicast\s+source\s+reachable-via\s+rx/i.test(child.id)) {
|
|
670
|
+
if (child?.id && /^ip\s+verify\s+unicast\s+source\s+reachable-via\s+rx/i.test(child.id)) {
|
|
633
671
|
return { enabled: true, mode: 'strict' };
|
|
634
672
|
}
|
|
635
|
-
if (/^ip\s+verify\s+unicast\s+source\s+reachable-via\s+any/i.test(child.id)) {
|
|
673
|
+
if (child?.id && /^ip\s+verify\s+unicast\s+source\s+reachable-via\s+any/i.test(child.id)) {
|
|
636
674
|
return { enabled: true, mode: 'loose' };
|
|
637
675
|
}
|
|
638
676
|
}
|
|
@@ -674,14 +712,16 @@ export const getMlagReloadDelays = (mlagNode: ConfigNode): { mlag: boolean; nonM
|
|
|
674
712
|
* @returns true if EVPN peer group has password
|
|
675
713
|
*/
|
|
676
714
|
export const hasEvpnPeerAuth = (routerBgpNode: ConfigNode): boolean => {
|
|
715
|
+
if (!routerBgpNode?.children) return false;
|
|
677
716
|
// Look for EVPN peer group with password
|
|
678
717
|
let evpnPeerGroup: string | undefined;
|
|
679
718
|
|
|
680
719
|
for (const child of routerBgpNode.children) {
|
|
681
720
|
// Find EVPN address family activation
|
|
682
|
-
if (/^address-family\s+evpn/i.test(child.id)) {
|
|
721
|
+
if (child?.id && /^address-family\s+evpn/i.test(child.id)) {
|
|
722
|
+
if (!child?.children) continue;
|
|
683
723
|
for (const subchild of child.children) {
|
|
684
|
-
const match = subchild
|
|
724
|
+
const match = subchild?.id?.match(/neighbor\s+(\S+)\s+activate/i);
|
|
685
725
|
if (match?.[1]) {
|
|
686
726
|
evpnPeerGroup = match[1];
|
|
687
727
|
}
|
|
@@ -693,7 +733,7 @@ export const hasEvpnPeerAuth = (routerBgpNode: ConfigNode): boolean => {
|
|
|
693
733
|
|
|
694
734
|
// Check if peer group has password
|
|
695
735
|
return routerBgpNode.children.some((child) =>
|
|
696
|
-
includesIgnoreCase(child.id, `neighbor ${evpnPeerGroup}`) &&
|
|
736
|
+
child?.id && includesIgnoreCase(child.id, `neighbor ${evpnPeerGroup}`) &&
|
|
697
737
|
includesIgnoreCase(child.id, 'password')
|
|
698
738
|
);
|
|
699
739
|
};
|
|
@@ -709,11 +749,12 @@ export const hasEvpnPeerAuth = (routerBgpNode: ConfigNode): boolean => {
|
|
|
709
749
|
* @returns true if logging meets minimum level requirement
|
|
710
750
|
*/
|
|
711
751
|
export const hasLoggingLevel = (ast: ConfigNode[], minLevel: string): boolean => {
|
|
752
|
+
if (!Array.isArray(ast)) return false;
|
|
712
753
|
const levels = ['emergencies', 'alerts', 'critical', 'errors', 'warnings', 'notifications', 'informational', 'debugging'];
|
|
713
754
|
const minIndex = levels.indexOf(minLevel.toLowerCase());
|
|
714
755
|
|
|
715
756
|
for (const node of ast) {
|
|
716
|
-
const match = node
|
|
757
|
+
const match = (node?.id ?? '').match(/^logging\s+(?:buffered|trap)\s+(\S+)/i);
|
|
717
758
|
if (match?.[1]) {
|
|
718
759
|
const configuredIndex = levels.indexOf(match[1].toLowerCase());
|
|
719
760
|
if (configuredIndex >= minIndex) {
|
|
@@ -730,8 +771,9 @@ export const hasLoggingLevel = (ast: ConfigNode[], minLevel: string): boolean =>
|
|
|
730
771
|
* @returns true if logging source-interface is configured
|
|
731
772
|
*/
|
|
732
773
|
export const hasLoggingSourceInterface = (ast: ConfigNode[]): boolean => {
|
|
774
|
+
if (!Array.isArray(ast)) return false;
|
|
733
775
|
return ast.some((node) =>
|
|
734
|
-
/^logging\s+source-interface/i.test(node
|
|
776
|
+
/^logging\s+source-interface/i.test(node?.id ?? '')
|
|
735
777
|
);
|
|
736
778
|
};
|
|
737
779
|
|
|
@@ -741,8 +783,9 @@ export const hasLoggingSourceInterface = (ast: ConfigNode[]): boolean => {
|
|
|
741
783
|
* @returns true if event-monitor is configured
|
|
742
784
|
*/
|
|
743
785
|
export const hasEventMonitor = (ast: ConfigNode[]): boolean => {
|
|
786
|
+
if (!Array.isArray(ast)) return false;
|
|
744
787
|
return ast.some((node) =>
|
|
745
|
-
/^event-monitor$/i.test(node
|
|
788
|
+
/^event-monitor$/i.test(node?.id ?? '')
|
|
746
789
|
);
|
|
747
790
|
};
|
|
748
791
|
|
|
@@ -756,8 +799,9 @@ export const hasEventMonitor = (ast: ConfigNode[]): boolean => {
|
|
|
756
799
|
* @returns true if VRRP authentication is configured
|
|
757
800
|
*/
|
|
758
801
|
export const hasVrrpAuthentication = (interfaceNode: ConfigNode): boolean => {
|
|
802
|
+
if (!interfaceNode?.children) return false;
|
|
759
803
|
return interfaceNode.children.some((child) =>
|
|
760
|
-
/^vrrp\s+\d+\s+authentication/i.test(child.id)
|
|
804
|
+
child?.id && /^vrrp\s+\d+\s+authentication/i.test(child.id)
|
|
761
805
|
);
|
|
762
806
|
};
|
|
763
807
|
|
|
@@ -767,8 +811,9 @@ export const hasVrrpAuthentication = (interfaceNode: ConfigNode): boolean => {
|
|
|
767
811
|
* @returns true if ip virtual-router mac-address is configured
|
|
768
812
|
*/
|
|
769
813
|
export const hasVirtualRouterMac = (ast: ConfigNode[]): boolean => {
|
|
814
|
+
if (!Array.isArray(ast)) return false;
|
|
770
815
|
return ast.some((node) =>
|
|
771
|
-
/^ip\s+virtual-router\s+mac-address/i.test(node
|
|
816
|
+
/^ip\s+virtual-router\s+mac-address/i.test(node?.id ?? '')
|
|
772
817
|
);
|
|
773
818
|
};
|
|
774
819
|
|
|
@@ -783,11 +828,12 @@ export const hasVirtualRouterMac = (ast: ConfigNode[]): boolean => {
|
|
|
783
828
|
* @returns true if interface appears to be external facing
|
|
784
829
|
*/
|
|
785
830
|
export const isExternalInterface = (interfaceNode: ConfigNode): boolean => {
|
|
831
|
+
if (!interfaceNode?.children) return false;
|
|
786
832
|
const description = interfaceNode.children.find((child) =>
|
|
787
|
-
startsWithIgnoreCase(child.id, 'description ')
|
|
833
|
+
child?.id && startsWithIgnoreCase(child.id, 'description ')
|
|
788
834
|
);
|
|
789
835
|
|
|
790
|
-
if (description) {
|
|
836
|
+
if (description?.id) {
|
|
791
837
|
return (
|
|
792
838
|
includesIgnoreCase(description.id, 'wan') ||
|
|
793
839
|
includesIgnoreCase(description.id, 'internet') ||
|
|
@@ -807,8 +853,9 @@ export const isExternalInterface = (interfaceNode: ConfigNode): boolean => {
|
|
|
807
853
|
* @returns true if interface is configured as access port
|
|
808
854
|
*/
|
|
809
855
|
export const isAccessPort = (interfaceNode: ConfigNode): boolean => {
|
|
856
|
+
if (!interfaceNode?.children) return false;
|
|
810
857
|
return interfaceNode.children.some((child) =>
|
|
811
|
-
/^switchport\s+mode\s+access/i.test(child.id)
|
|
858
|
+
child?.id && /^switchport\s+mode\s+access/i.test(child.id)
|
|
812
859
|
);
|
|
813
860
|
};
|
|
814
861
|
|
|
@@ -818,8 +865,9 @@ export const isAccessPort = (interfaceNode: ConfigNode): boolean => {
|
|
|
818
865
|
* @returns true if interface is configured as trunk port
|
|
819
866
|
*/
|
|
820
867
|
export const isTrunkPort = (interfaceNode: ConfigNode): boolean => {
|
|
868
|
+
if (!interfaceNode?.children) return false;
|
|
821
869
|
return interfaceNode.children.some((child) =>
|
|
822
|
-
/^switchport\s+mode\s+trunk/i.test(child.id)
|
|
870
|
+
child?.id && /^switchport\s+mode\s+trunk/i.test(child.id)
|
|
823
871
|
);
|
|
824
872
|
};
|
|
825
873
|
|
|
@@ -829,8 +877,9 @@ export const isTrunkPort = (interfaceNode: ConfigNode): boolean => {
|
|
|
829
877
|
* @returns true if mlag configuration block exists
|
|
830
878
|
*/
|
|
831
879
|
export const hasMlagConfiguration = (ast: ConfigNode[]): boolean => {
|
|
880
|
+
if (!Array.isArray(ast)) return false;
|
|
832
881
|
return ast.some((node) =>
|
|
833
|
-
startsWithIgnoreCase(node
|
|
882
|
+
startsWithIgnoreCase(node?.id ?? '', 'mlag configuration')
|
|
834
883
|
);
|
|
835
884
|
};
|
|
836
885
|
|
|
@@ -842,8 +891,9 @@ export const hasMlagConfiguration = (ast: ConfigNode[]): boolean => {
|
|
|
842
891
|
export const getMlagConfiguration = (
|
|
843
892
|
ast: ConfigNode[]
|
|
844
893
|
): ConfigNode | undefined => {
|
|
894
|
+
if (!Array.isArray(ast)) return undefined;
|
|
845
895
|
return ast.find((node) =>
|
|
846
|
-
startsWithIgnoreCase(node
|
|
896
|
+
startsWithIgnoreCase(node?.id ?? '', 'mlag configuration')
|
|
847
897
|
);
|
|
848
898
|
};
|
|
849
899
|
|
|
@@ -869,8 +919,9 @@ export const checkMlagRequirements = (
|
|
|
869
919
|
* @returns true if management api http-commands is configured
|
|
870
920
|
*/
|
|
871
921
|
export const hasManagementApi = (ast: ConfigNode[]): boolean => {
|
|
922
|
+
if (!Array.isArray(ast)) return false;
|
|
872
923
|
return ast.some((node) =>
|
|
873
|
-
startsWithIgnoreCase(node
|
|
924
|
+
startsWithIgnoreCase(node?.id ?? '', 'management api')
|
|
874
925
|
);
|
|
875
926
|
};
|
|
876
927
|
|
|
@@ -880,8 +931,9 @@ export const hasManagementApi = (ast: ConfigNode[]): boolean => {
|
|
|
880
931
|
* @returns Array of management API configuration nodes
|
|
881
932
|
*/
|
|
882
933
|
export const getManagementApiNodes = (ast: ConfigNode[]): ConfigNode[] => {
|
|
934
|
+
if (!Array.isArray(ast)) return [];
|
|
883
935
|
return ast.filter((node) =>
|
|
884
|
-
startsWithIgnoreCase(node
|
|
936
|
+
startsWithIgnoreCase(node?.id ?? '', 'management api')
|
|
885
937
|
);
|
|
886
938
|
};
|
|
887
939
|
|
|
@@ -891,12 +943,13 @@ export const getManagementApiNodes = (ast: ConfigNode[]): ConfigNode[] => {
|
|
|
891
943
|
* @returns true if HTTPS transport is configured
|
|
892
944
|
*/
|
|
893
945
|
export const hasHttpsTransport = (apiNode: ConfigNode): boolean => {
|
|
946
|
+
if (!apiNode?.children) return false;
|
|
894
947
|
// Check for "protocol https" or "no shutdown" with https
|
|
895
948
|
const hasProtocolHttps = apiNode.children.some((child) =>
|
|
896
|
-
includesIgnoreCase(child.id, 'protocol https')
|
|
949
|
+
child?.id && includesIgnoreCase(child.id, 'protocol https')
|
|
897
950
|
);
|
|
898
951
|
const hasTransportHttps = apiNode.children.some((child) =>
|
|
899
|
-
includesIgnoreCase(child.id, 'transport https')
|
|
952
|
+
child?.id && includesIgnoreCase(child.id, 'transport https')
|
|
900
953
|
);
|
|
901
954
|
return hasProtocolHttps || hasTransportHttps;
|
|
902
955
|
};
|
|
@@ -942,9 +995,10 @@ export const getVxlanVniMappings = (
|
|
|
942
995
|
vxlanNode: ConfigNode
|
|
943
996
|
): { vni: string; vlan?: string }[] => {
|
|
944
997
|
const mappings: { vni: string; vlan?: string }[] = [];
|
|
998
|
+
if (!vxlanNode?.children) return mappings;
|
|
945
999
|
|
|
946
1000
|
for (const child of vxlanNode.children) {
|
|
947
|
-
const vniMatch = child
|
|
1001
|
+
const vniMatch = child?.id?.match(/vxlan\s+vni\s+(\d+)\s+vlan\s+(\d+)/i);
|
|
948
1002
|
if (vniMatch) {
|
|
949
1003
|
const vni = vniMatch[1];
|
|
950
1004
|
if (!vni) {
|
|
@@ -955,7 +1009,7 @@ export const getVxlanVniMappings = (
|
|
|
955
1009
|
continue;
|
|
956
1010
|
}
|
|
957
1011
|
|
|
958
|
-
const simpleMatch = child
|
|
1012
|
+
const simpleMatch = child?.id?.match(/vxlan\s+vni\s+(\d+)/i);
|
|
959
1013
|
if (simpleMatch) {
|
|
960
1014
|
const vni = simpleMatch[1];
|
|
961
1015
|
if (!vni) {
|
|
@@ -983,8 +1037,9 @@ export const hasVxlanSourceInterface = (vxlanNode: ConfigNode): boolean => {
|
|
|
983
1037
|
* @returns The MLAG ID if configured, undefined otherwise
|
|
984
1038
|
*/
|
|
985
1039
|
export const getMlagId = (interfaceNode: ConfigNode): string | undefined => {
|
|
1040
|
+
if (!interfaceNode?.children) return undefined;
|
|
986
1041
|
const mlagCmd = interfaceNode.children.find((child) =>
|
|
987
|
-
/^mlag\s+\d+/i.test(child.id)
|
|
1042
|
+
child?.id && /^mlag\s+\d+/i.test(child.id)
|
|
988
1043
|
);
|
|
989
1044
|
if (!mlagCmd) return undefined;
|
|
990
1045
|
|
|
@@ -1044,12 +1099,13 @@ export const isEthernetInterface = (node: ConfigNode): boolean => {
|
|
|
1044
1099
|
* @returns true if daemon(s) are configured
|
|
1045
1100
|
*/
|
|
1046
1101
|
export const hasDaemon = (ast: ConfigNode[], daemonName?: string): boolean => {
|
|
1102
|
+
if (!Array.isArray(ast)) return false;
|
|
1047
1103
|
if (daemonName) {
|
|
1048
1104
|
return ast.some((node) =>
|
|
1049
|
-
equalsIgnoreCase(node
|
|
1105
|
+
equalsIgnoreCase(node?.id ?? '', `daemon ${daemonName}`)
|
|
1050
1106
|
);
|
|
1051
1107
|
}
|
|
1052
|
-
return ast.some((node) => startsWithIgnoreCase(node
|
|
1108
|
+
return ast.some((node) => startsWithIgnoreCase(node?.id ?? '', 'daemon '));
|
|
1053
1109
|
};
|
|
1054
1110
|
|
|
1055
1111
|
/**
|
|
@@ -1058,7 +1114,8 @@ export const hasDaemon = (ast: ConfigNode[], daemonName?: string): boolean => {
|
|
|
1058
1114
|
* @returns true if event-handler(s) are configured
|
|
1059
1115
|
*/
|
|
1060
1116
|
export const hasEventHandler = (ast: ConfigNode[]): boolean => {
|
|
1061
|
-
|
|
1117
|
+
if (!Array.isArray(ast)) return false;
|
|
1118
|
+
return ast.some((node) => startsWithIgnoreCase(node?.id ?? '', 'event-handler '));
|
|
1062
1119
|
};
|
|
1063
1120
|
|
|
1064
1121
|
/**
|
|
@@ -1067,8 +1124,9 @@ export const hasEventHandler = (ast: ConfigNode[]): boolean => {
|
|
|
1067
1124
|
* @returns Array of VRF instance nodes
|
|
1068
1125
|
*/
|
|
1069
1126
|
export const getVrfInstances = (ast: ConfigNode[]): ConfigNode[] => {
|
|
1127
|
+
if (!Array.isArray(ast)) return [];
|
|
1070
1128
|
return ast.filter((node) =>
|
|
1071
|
-
/^vrf\s+instance\s+\S+/i.test(node
|
|
1129
|
+
/^vrf\s+instance\s+\S+/i.test(node?.id ?? '')
|
|
1072
1130
|
);
|
|
1073
1131
|
};
|
|
1074
1132
|
|
|
@@ -1078,8 +1136,9 @@ export const getVrfInstances = (ast: ConfigNode[]): ConfigNode[] => {
|
|
|
1078
1136
|
* @returns The VRF name if configured, undefined otherwise
|
|
1079
1137
|
*/
|
|
1080
1138
|
export const getInterfaceVrf = (interfaceNode: ConfigNode): string | undefined => {
|
|
1139
|
+
if (!interfaceNode?.children) return undefined;
|
|
1081
1140
|
const vrfCmd = interfaceNode.children.find((child) =>
|
|
1082
|
-
/^vrf\s+\S+/i.test(child.id)
|
|
1141
|
+
child?.id && /^vrf\s+\S+/i.test(child.id)
|
|
1083
1142
|
);
|
|
1084
1143
|
if (!vrfCmd) return undefined;
|
|
1085
1144
|
|
|
@@ -1093,8 +1152,9 @@ export const getInterfaceVrf = (interfaceNode: ConfigNode): string | undefined =
|
|
|
1093
1152
|
* @returns true if EVPN address-family is configured
|
|
1094
1153
|
*/
|
|
1095
1154
|
export const hasEvpnAddressFamily = (routerBgpNode: ConfigNode): boolean => {
|
|
1155
|
+
if (!routerBgpNode?.children) return false;
|
|
1096
1156
|
return routerBgpNode.children.some((child) =>
|
|
1097
|
-
/^address-family\s+evpn/i.test(child.id)
|
|
1157
|
+
child?.id && /^address-family\s+evpn/i.test(child.id)
|
|
1098
1158
|
);
|
|
1099
1159
|
};
|
|
1100
1160
|
|
|
@@ -1104,8 +1164,9 @@ export const hasEvpnAddressFamily = (routerBgpNode: ConfigNode): boolean => {
|
|
|
1104
1164
|
* @returns true if ip virtual-router address is configured
|
|
1105
1165
|
*/
|
|
1106
1166
|
export const hasVirtualRouterAddress = (interfaceNode: ConfigNode): boolean => {
|
|
1167
|
+
if (!interfaceNode?.children) return false;
|
|
1107
1168
|
return interfaceNode.children.some((child) =>
|
|
1108
|
-
/^ip\s+virtual-router\s+address/i.test(child.id)
|
|
1169
|
+
child?.id && /^ip\s+virtual-router\s+address/i.test(child.id)
|
|
1109
1170
|
);
|
|
1110
1171
|
};
|
|
1111
1172
|
|
|
@@ -1115,8 +1176,9 @@ export const hasVirtualRouterAddress = (interfaceNode: ConfigNode): boolean => {
|
|
|
1115
1176
|
* @returns true if ip address is configured
|
|
1116
1177
|
*/
|
|
1117
1178
|
export const hasIpAddress = (interfaceNode: ConfigNode): boolean => {
|
|
1179
|
+
if (!interfaceNode?.children) return false;
|
|
1118
1180
|
return interfaceNode.children.some((child) =>
|
|
1119
|
-
/^ip\s+address\s+\d+\.\d+\.\d+\.\d+/i.test(child.id)
|
|
1181
|
+
child?.id && /^ip\s+address\s+\d+\.\d+\.\d+\.\d+/i.test(child.id)
|
|
1120
1182
|
);
|
|
1121
1183
|
};
|
|
1122
1184
|
|
|
@@ -1126,11 +1188,12 @@ export const hasIpAddress = (interfaceNode: ConfigNode): boolean => {
|
|
|
1126
1188
|
* @returns true if interface is shutdown
|
|
1127
1189
|
*/
|
|
1128
1190
|
export const isShutdown = (interfaceNode: ConfigNode): boolean => {
|
|
1191
|
+
if (!interfaceNode?.children) return false;
|
|
1129
1192
|
const hasShutdown = interfaceNode.children.some((child) =>
|
|
1130
|
-
equalsIgnoreCase(child.id, 'shutdown')
|
|
1193
|
+
child?.id && equalsIgnoreCase(child.id, 'shutdown')
|
|
1131
1194
|
);
|
|
1132
1195
|
const hasNoShutdown = interfaceNode.children.some((child) =>
|
|
1133
|
-
equalsIgnoreCase(child.id, 'no shutdown')
|
|
1196
|
+
child?.id && equalsIgnoreCase(child.id, 'no shutdown')
|
|
1134
1197
|
);
|
|
1135
1198
|
return hasShutdown && !hasNoShutdown;
|
|
1136
1199
|
};
|
|
@@ -1141,8 +1204,9 @@ export const isShutdown = (interfaceNode: ConfigNode): boolean => {
|
|
|
1141
1204
|
* @returns The description if configured, undefined otherwise
|
|
1142
1205
|
*/
|
|
1143
1206
|
export const getInterfaceDescription = (interfaceNode: ConfigNode): string | undefined => {
|
|
1207
|
+
if (!interfaceNode?.children) return undefined;
|
|
1144
1208
|
const descCmd = interfaceNode.children.find((child) =>
|
|
1145
|
-
startsWithIgnoreCase(child.id, 'description ')
|
|
1209
|
+
child?.id && startsWithIgnoreCase(child.id, 'description ')
|
|
1146
1210
|
);
|
|
1147
1211
|
if (!descCmd) return undefined;
|
|
1148
1212
|
|
|
@@ -1155,8 +1219,9 @@ export const getInterfaceDescription = (interfaceNode: ConfigNode): string | und
|
|
|
1155
1219
|
* @returns true if NTP server(s) are configured
|
|
1156
1220
|
*/
|
|
1157
1221
|
export const hasNtpServer = (ast: ConfigNode[]): boolean => {
|
|
1222
|
+
if (!Array.isArray(ast)) return false;
|
|
1158
1223
|
return ast.some((node) =>
|
|
1159
|
-
/^ntp\s+server\s+/i.test(node
|
|
1224
|
+
/^ntp\s+server\s+/i.test(node?.id ?? '')
|
|
1160
1225
|
);
|
|
1161
1226
|
};
|
|
1162
1227
|
|
|
@@ -1166,8 +1231,9 @@ export const hasNtpServer = (ast: ConfigNode[]): boolean => {
|
|
|
1166
1231
|
* @returns true if logging host is configured
|
|
1167
1232
|
*/
|
|
1168
1233
|
export const hasLoggingHost = (ast: ConfigNode[]): boolean => {
|
|
1234
|
+
if (!Array.isArray(ast)) return false;
|
|
1169
1235
|
return ast.some((node) =>
|
|
1170
|
-
/^logging\s+host\s+/i.test(node
|
|
1236
|
+
/^logging\s+host\s+/i.test(node?.id ?? '')
|
|
1171
1237
|
);
|
|
1172
1238
|
};
|
|
1173
1239
|
|
|
@@ -1177,8 +1243,9 @@ export const hasLoggingHost = (ast: ConfigNode[]): boolean => {
|
|
|
1177
1243
|
* @returns true if SNMP is configured
|
|
1178
1244
|
*/
|
|
1179
1245
|
export const hasSnmpServer = (ast: ConfigNode[]): boolean => {
|
|
1246
|
+
if (!Array.isArray(ast)) return false;
|
|
1180
1247
|
return ast.some((node) =>
|
|
1181
|
-
/^snmp-server\s+/i.test(node
|
|
1248
|
+
/^snmp-server\s+/i.test(node?.id ?? '')
|
|
1182
1249
|
);
|
|
1183
1250
|
};
|
|
1184
1251
|
|
|
@@ -1188,8 +1255,9 @@ export const hasSnmpServer = (ast: ConfigNode[]): boolean => {
|
|
|
1188
1255
|
* @returns true if AAA is configured
|
|
1189
1256
|
*/
|
|
1190
1257
|
export const hasAaa = (ast: ConfigNode[]): boolean => {
|
|
1258
|
+
if (!Array.isArray(ast)) return false;
|
|
1191
1259
|
return ast.some((node) =>
|
|
1192
|
-
/^aaa\s+/i.test(node
|
|
1260
|
+
/^aaa\s+/i.test(node?.id ?? '')
|
|
1193
1261
|
);
|
|
1194
1262
|
};
|
|
1195
1263
|
|
|
@@ -1199,8 +1267,9 @@ export const hasAaa = (ast: ConfigNode[]): boolean => {
|
|
|
1199
1267
|
* @returns true if spanning-tree is configured
|
|
1200
1268
|
*/
|
|
1201
1269
|
export const hasSpanningTree = (ast: ConfigNode[]): boolean => {
|
|
1270
|
+
if (!Array.isArray(ast)) return false;
|
|
1202
1271
|
return ast.some((node) =>
|
|
1203
|
-
/^spanning-tree\s+/i.test(node
|
|
1272
|
+
/^spanning-tree\s+/i.test(node?.id ?? '')
|
|
1204
1273
|
);
|
|
1205
1274
|
};
|
|
1206
1275
|
|
|
@@ -1210,11 +1279,12 @@ export const hasSpanningTree = (ast: ConfigNode[]): boolean => {
|
|
|
1210
1279
|
* @returns The spanning-tree mode (mstp, rapid-pvst, none, etc.)
|
|
1211
1280
|
*/
|
|
1212
1281
|
export const getSpanningTreeMode = (ast: ConfigNode[]): string | undefined => {
|
|
1282
|
+
if (!Array.isArray(ast)) return undefined;
|
|
1213
1283
|
const stpNode = ast.find((node) =>
|
|
1214
|
-
/^spanning-tree\s+mode\s+/i.test(node
|
|
1284
|
+
/^spanning-tree\s+mode\s+/i.test(node?.id ?? '')
|
|
1215
1285
|
);
|
|
1216
1286
|
if (!stpNode) return undefined;
|
|
1217
1287
|
|
|
1218
|
-
const match = stpNode
|
|
1288
|
+
const match = stpNode?.id?.match(/spanning-tree\s+mode\s+(\S+)/i);
|
|
1219
1289
|
return match ? match[1] : undefined;
|
|
1220
1290
|
};
|