@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,856 @@
|
|
|
1
|
+
// packages/rule-helpers/src/nokia/helpers.ts
|
|
2
|
+
// Nokia SR OS specific helper functions
|
|
3
|
+
|
|
4
|
+
import type { ConfigNode } from '../../types/ConfigNode';
|
|
5
|
+
import { hasChildCommand, getChildCommand, getChildCommands } from '../common/helpers';
|
|
6
|
+
|
|
7
|
+
// Re-export common helpers for convenience
|
|
8
|
+
export { hasChildCommand, getChildCommand, getChildCommands } from '../common/helpers';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if admin-state is enabled (admin-state enable or admin-state up)
|
|
12
|
+
* Nokia SR OS uses admin-state for enabling/disabling most components
|
|
13
|
+
*/
|
|
14
|
+
export const isAdminStateEnabled = (node: ConfigNode): boolean => {
|
|
15
|
+
// Check direct children first (more common case)
|
|
16
|
+
const directCheck = node.children.some((child) => {
|
|
17
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
18
|
+
return rawText === 'admin-state enable' || rawText === 'admin-state up';
|
|
19
|
+
});
|
|
20
|
+
if (directCheck) return true;
|
|
21
|
+
|
|
22
|
+
// Also check if admin-state is in the node's own rawText (for compact configs)
|
|
23
|
+
const nodeText = node.rawText.toLowerCase();
|
|
24
|
+
return nodeText.includes('admin-state enable') || nodeText.includes('admin-state up');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if admin-state is disabled (admin-state disable or no admin-state command)
|
|
29
|
+
*/
|
|
30
|
+
export const isAdminStateDisabled = (node: ConfigNode): boolean => {
|
|
31
|
+
// Check direct children first
|
|
32
|
+
const directCheck = node.children.some((child) => {
|
|
33
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
34
|
+
return rawText === 'admin-state disable';
|
|
35
|
+
});
|
|
36
|
+
if (directCheck) return true;
|
|
37
|
+
|
|
38
|
+
// Also check node's own rawText
|
|
39
|
+
return node.rawText.toLowerCase().includes('admin-state disable');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if component is shutdown (has shutdown command)
|
|
44
|
+
*/
|
|
45
|
+
export const isShutdown = (node: ConfigNode): boolean => {
|
|
46
|
+
return node.children.some((child) => {
|
|
47
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
48
|
+
return rawText === 'shutdown';
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if interface/component is enabled (has admin-state enable and no shutdown)
|
|
54
|
+
*/
|
|
55
|
+
export const isEnabled = (node: ConfigNode): boolean => {
|
|
56
|
+
const hasAdminStateEnabled = isAdminStateEnabled(node);
|
|
57
|
+
const hasShutdown = isShutdown(node);
|
|
58
|
+
return hasAdminStateEnabled && !hasShutdown;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a port is a physical port (not LAG, loopback, or system)
|
|
63
|
+
* Nokia port format: slot/mda/port (e.g., 1/1/1, 1/2/3)
|
|
64
|
+
*/
|
|
65
|
+
export const isPhysicalPort = (portName: string): boolean => {
|
|
66
|
+
const name = portName.toLowerCase();
|
|
67
|
+
// Match slot/mda/port pattern
|
|
68
|
+
return /^\d+\/\d+\/\d+/.test(name);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a port is a LAG (Link Aggregation Group)
|
|
73
|
+
* Nokia LAG format: lag-N or lag N
|
|
74
|
+
*/
|
|
75
|
+
export const isLagPort = (portName: string): boolean => {
|
|
76
|
+
const name = portName.toLowerCase();
|
|
77
|
+
return name.includes('lag');
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if interface is a system interface (loopback, system)
|
|
82
|
+
*/
|
|
83
|
+
export const isSystemInterface = (interfaceName: string): boolean => {
|
|
84
|
+
const name = interfaceName.toLowerCase();
|
|
85
|
+
return (
|
|
86
|
+
name.includes('system') ||
|
|
87
|
+
name.includes('loopback') ||
|
|
88
|
+
name === '"system"'
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get port mode (network or access)
|
|
94
|
+
*/
|
|
95
|
+
export const getPortMode = (node: ConfigNode): 'network' | 'access' | undefined => {
|
|
96
|
+
// Look for ethernet mode configuration
|
|
97
|
+
const ethernetNode = node.children.find((child) =>
|
|
98
|
+
child.id.toLowerCase() === 'ethernet'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (ethernetNode) {
|
|
102
|
+
const modeCmd = ethernetNode.children.find((child) => {
|
|
103
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
104
|
+
return rawText.startsWith('mode');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (modeCmd) {
|
|
108
|
+
if (modeCmd.rawText.toLowerCase().includes('network')) {
|
|
109
|
+
return 'network';
|
|
110
|
+
}
|
|
111
|
+
if (modeCmd.rawText.toLowerCase().includes('access')) {
|
|
112
|
+
return 'access';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if port is in network mode
|
|
121
|
+
*/
|
|
122
|
+
export const isNetworkPort = (node: ConfigNode): boolean => {
|
|
123
|
+
return getPortMode(node) === 'network';
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if port is in access mode
|
|
128
|
+
*/
|
|
129
|
+
export const isAccessPort = (node: ConfigNode): boolean => {
|
|
130
|
+
return getPortMode(node) === 'access';
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if port has description configured
|
|
135
|
+
*/
|
|
136
|
+
export const hasDescription = (node: ConfigNode): boolean => {
|
|
137
|
+
return hasChildCommand(node, 'description');
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get port/interface description
|
|
142
|
+
*/
|
|
143
|
+
export const getDescription = (node: ConfigNode): string | undefined => {
|
|
144
|
+
const descCmd = getChildCommand(node, 'description');
|
|
145
|
+
if (descCmd) {
|
|
146
|
+
// Nokia descriptions are often quoted
|
|
147
|
+
const match = descCmd.rawText.match(/description\s+"([^"]+)"|description\s+(\S+)/i);
|
|
148
|
+
if (match) {
|
|
149
|
+
return match[1] || match[2];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get system name from system block
|
|
157
|
+
*/
|
|
158
|
+
export const getSystemName = (node: ConfigNode): string | undefined => {
|
|
159
|
+
const nameCmd = node.children.find((child) => {
|
|
160
|
+
return child.id.toLowerCase().startsWith('name');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (nameCmd) {
|
|
164
|
+
// Nokia system name is quoted: name "Router-Name"
|
|
165
|
+
const match = nameCmd.rawText.match(/name\s+"([^"]+)"/i);
|
|
166
|
+
if (match) {
|
|
167
|
+
return match[1];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if interface has IP address configured
|
|
175
|
+
*/
|
|
176
|
+
export const hasIpAddress = (node: ConfigNode): boolean => {
|
|
177
|
+
return node.children.some((child) => {
|
|
178
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
179
|
+
return rawText.startsWith('address');
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get interface IP address
|
|
185
|
+
*/
|
|
186
|
+
export const getIpAddress = (node: ConfigNode): string | undefined => {
|
|
187
|
+
const addrCmd = node.children.find((child) => {
|
|
188
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
189
|
+
return rawText.startsWith('address');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (addrCmd) {
|
|
193
|
+
// Match IPv4 or IPv6 address with optional prefix
|
|
194
|
+
const match = addrCmd.rawText.match(/address\s+([\d./:a-fA-F]+)/i);
|
|
195
|
+
if (match) {
|
|
196
|
+
return match[1];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if port is assigned to interface
|
|
204
|
+
*/
|
|
205
|
+
export const hasPortAssignment = (node: ConfigNode): boolean => {
|
|
206
|
+
return node.children.some((child) => {
|
|
207
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
208
|
+
return rawText.startsWith('port');
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get port assignment
|
|
214
|
+
*/
|
|
215
|
+
export const getPortAssignment = (node: ConfigNode): string | undefined => {
|
|
216
|
+
const portCmd = node.children.find((child) => {
|
|
217
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
218
|
+
return rawText.startsWith('port');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (portCmd) {
|
|
222
|
+
const match = portCmd.rawText.match(/port\s+([\d/]+)/i);
|
|
223
|
+
if (match) {
|
|
224
|
+
return match[1];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if BGP has router-id configured
|
|
232
|
+
*/
|
|
233
|
+
export const hasBgpRouterId = (node: ConfigNode): boolean => {
|
|
234
|
+
return hasChildCommand(node, 'router-id');
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get BGP router-id
|
|
239
|
+
*/
|
|
240
|
+
export const getBgpRouterId = (node: ConfigNode): string | undefined => {
|
|
241
|
+
const routerIdCmd = getChildCommand(node, 'router-id');
|
|
242
|
+
if (routerIdCmd) {
|
|
243
|
+
const match = routerIdCmd.rawText.match(/router-id\s+([\d.]+)/i);
|
|
244
|
+
if (match) {
|
|
245
|
+
return match[1];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Find a stanza by name in the configuration tree
|
|
253
|
+
*/
|
|
254
|
+
export const findStanza = (node: ConfigNode, stanzaName: string): ConfigNode | undefined => {
|
|
255
|
+
if (node.id.toLowerCase().startsWith(stanzaName.toLowerCase())) {
|
|
256
|
+
return node;
|
|
257
|
+
}
|
|
258
|
+
for (const child of node.children) {
|
|
259
|
+
const found = findStanza(child, stanzaName);
|
|
260
|
+
if (found) return found;
|
|
261
|
+
}
|
|
262
|
+
return undefined;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Find all stanzas by name in the configuration tree
|
|
267
|
+
*/
|
|
268
|
+
export const findStanzas = (node: ConfigNode, stanzaName: string): ConfigNode[] => {
|
|
269
|
+
const results: ConfigNode[] = [];
|
|
270
|
+
if (node.id.toLowerCase().startsWith(stanzaName.toLowerCase())) {
|
|
271
|
+
results.push(node);
|
|
272
|
+
}
|
|
273
|
+
for (const child of node.children) {
|
|
274
|
+
results.push(...findStanzas(child, stanzaName));
|
|
275
|
+
}
|
|
276
|
+
return results;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if SAP (Service Access Point) is configured
|
|
281
|
+
*/
|
|
282
|
+
export const hasSap = (node: ConfigNode): boolean => {
|
|
283
|
+
return node.children.some((child) => {
|
|
284
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
285
|
+
return rawText.startsWith('sap');
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get SAP identifier
|
|
291
|
+
*/
|
|
292
|
+
export const getSapId = (node: ConfigNode): string | undefined => {
|
|
293
|
+
const sapCmd = node.children.find((child) => {
|
|
294
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
295
|
+
return rawText.startsWith('sap');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (sapCmd) {
|
|
299
|
+
// SAP format: sap port:vlan (e.g., sap 1/1/1:100)
|
|
300
|
+
const match = sapCmd.rawText.match(/sap\s+([\d/:]+)/i);
|
|
301
|
+
if (match) {
|
|
302
|
+
return match[1];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return undefined;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if SNMP is configured (snmp block with admin-state)
|
|
310
|
+
*/
|
|
311
|
+
export const isSnmpEnabled = (node: ConfigNode): boolean => {
|
|
312
|
+
if (node.id.toLowerCase() === 'snmp') {
|
|
313
|
+
return isAdminStateEnabled(node);
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check if NTP is configured
|
|
320
|
+
*/
|
|
321
|
+
export const hasNtpServer = (node: ConfigNode): boolean => {
|
|
322
|
+
return node.children.some((child) => {
|
|
323
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
324
|
+
return rawText.includes('ntp-server') || rawText.includes('server');
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check if SSH is enabled in security settings
|
|
330
|
+
*/
|
|
331
|
+
export const isSshEnabled = (node: ConfigNode): boolean => {
|
|
332
|
+
if (node.id.toLowerCase().includes('security') || node.id.toLowerCase().includes('management-interface')) {
|
|
333
|
+
return node.children.some((child) => {
|
|
334
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
335
|
+
return rawText.includes('ssh') && !rawText.includes('no ssh');
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if Telnet is enabled (security concern)
|
|
343
|
+
*/
|
|
344
|
+
export const isTelnetEnabled = (node: ConfigNode): boolean => {
|
|
345
|
+
if (node.id.toLowerCase().includes('security') || node.id.toLowerCase().includes('management-interface')) {
|
|
346
|
+
return node.children.some((child) => {
|
|
347
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
348
|
+
return rawText.includes('telnet') && !rawText.includes('no telnet');
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check if authentication is configured
|
|
356
|
+
*/
|
|
357
|
+
export const hasAuthentication = (node: ConfigNode): boolean => {
|
|
358
|
+
return node.children.some((child) => {
|
|
359
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
360
|
+
if (!rawText) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
return (
|
|
364
|
+
rawText.includes('authentication') ||
|
|
365
|
+
rawText.includes('auth-key') ||
|
|
366
|
+
rawText.includes('password')
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Get interface name from quoted or unquoted format
|
|
373
|
+
* Nokia uses: interface "name" or interface name
|
|
374
|
+
*/
|
|
375
|
+
export const getInterfaceName = (node: ConfigNode): string => {
|
|
376
|
+
const match = node.id.match(/interface\s+"([^"]+)"|interface\s+(\S+)/i);
|
|
377
|
+
const quoted = match?.[1];
|
|
378
|
+
const unquoted = match?.[2];
|
|
379
|
+
if (quoted) return quoted;
|
|
380
|
+
if (unquoted) return unquoted;
|
|
381
|
+
return node.id.replace(/^interface\s+/i, '').trim();
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get router name from router block
|
|
386
|
+
* Nokia uses: router "Base" or router vprn-name
|
|
387
|
+
*/
|
|
388
|
+
export const getRouterName = (node: ConfigNode): string => {
|
|
389
|
+
const match = node.id.match(/router\s+"([^"]+)"|router\s+(\S+)/i);
|
|
390
|
+
const quoted = match?.[1];
|
|
391
|
+
const unquoted = match?.[2];
|
|
392
|
+
if (quoted) return quoted;
|
|
393
|
+
if (unquoted) return unquoted;
|
|
394
|
+
return 'Base';
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check if BGP peer has description
|
|
399
|
+
*/
|
|
400
|
+
export const hasPeerDescription = (node: ConfigNode): boolean => {
|
|
401
|
+
return hasChildCommand(node, 'description');
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get service type from service block
|
|
406
|
+
*/
|
|
407
|
+
export const getServiceType = (node: ConfigNode): 'vpls' | 'vprn' | 'epipe' | 'ies' | undefined => {
|
|
408
|
+
const id = node.id.toLowerCase();
|
|
409
|
+
if (id.includes('vpls')) return 'vpls';
|
|
410
|
+
if (id.includes('vprn')) return 'vprn';
|
|
411
|
+
if (id.includes('epipe')) return 'epipe';
|
|
412
|
+
if (id.includes('ies')) return 'ies';
|
|
413
|
+
return undefined;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get service ID from service block
|
|
418
|
+
*/
|
|
419
|
+
export const getServiceId = (node: ConfigNode): string | undefined => {
|
|
420
|
+
const match = node.id.match(/(vpls|vprn|epipe|ies)\s+(\d+)/i);
|
|
421
|
+
const serviceId = match?.[2];
|
|
422
|
+
if (serviceId) {
|
|
423
|
+
return serviceId;
|
|
424
|
+
}
|
|
425
|
+
return undefined;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Check if customer is assigned to service
|
|
430
|
+
*/
|
|
431
|
+
export const hasCustomer = (node: ConfigNode): boolean => {
|
|
432
|
+
return node.children.some((child) => {
|
|
433
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
434
|
+
return rawText?.startsWith('customer') ?? false;
|
|
435
|
+
});
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get customer ID
|
|
440
|
+
*/
|
|
441
|
+
export const getCustomerId = (node: ConfigNode): string | undefined => {
|
|
442
|
+
const customerCmd = node.children.find((child) => {
|
|
443
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
444
|
+
return rawText.startsWith('customer');
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (customerCmd) {
|
|
448
|
+
const match = customerCmd.rawText.match(/customer\s+(\d+)/i);
|
|
449
|
+
if (match) {
|
|
450
|
+
return match[1];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// Management Plane Security Helpers
|
|
458
|
+
// ============================================================================
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Recursively search for a pattern in node and its descendants
|
|
462
|
+
*/
|
|
463
|
+
const searchNodeRecursively = (
|
|
464
|
+
node: ConfigNode,
|
|
465
|
+
predicate: (rawText: string) => boolean
|
|
466
|
+
): boolean => {
|
|
467
|
+
const rawText = node.rawText.toLowerCase().trim();
|
|
468
|
+
if (predicate(rawText)) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
return node.children.some((child) => searchNodeRecursively(child, predicate));
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Check if TACACS+ is configured for AAA
|
|
476
|
+
*/
|
|
477
|
+
export const hasTacacsConfig = (node: ConfigNode): boolean => {
|
|
478
|
+
// Check for tacplus in aaa remote-servers
|
|
479
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
480
|
+
rawText.includes('tacplus') || rawText.includes('tacacs')
|
|
481
|
+
);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Check if RADIUS is configured for AAA
|
|
486
|
+
*/
|
|
487
|
+
export const hasRadiusConfig = (node: ConfigNode): boolean => {
|
|
488
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('radius'));
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Check if SSH version 2 is configured
|
|
493
|
+
*/
|
|
494
|
+
export const hasSshV2 = (node: ConfigNode): boolean => {
|
|
495
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
496
|
+
rawText === 'version 2' || rawText.includes('version 2')
|
|
497
|
+
);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check if SSHv1 is explicitly enabled (security concern)
|
|
502
|
+
*/
|
|
503
|
+
export const hasSshV1 = (node: ConfigNode): boolean => {
|
|
504
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
505
|
+
rawText === 'version 1' ||
|
|
506
|
+
(rawText.includes('version') && rawText.includes('1') && !rawText.includes('2'))
|
|
507
|
+
);
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Check if weak SSH ciphers are configured
|
|
512
|
+
*/
|
|
513
|
+
export const hasWeakSshCipher = (node: ConfigNode): boolean => {
|
|
514
|
+
const weakCiphers = ['3des-cbc', 'blowfish-cbc', 'cast128-cbc', 'arcfour', 'rijndael-cbc'];
|
|
515
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
516
|
+
weakCiphers.some((cipher) => rawText.includes(cipher))
|
|
517
|
+
);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Check if SNMPv3 with privacy is configured
|
|
522
|
+
*/
|
|
523
|
+
export const hasSnmpV3Privacy = (node: ConfigNode): boolean => {
|
|
524
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
525
|
+
rawText.includes('security-level privacy') ||
|
|
526
|
+
rawText.includes('usm') ||
|
|
527
|
+
(rawText.includes('snmpv3') && rawText.includes('privacy'))
|
|
528
|
+
);
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Check if default SNMP community strings are used
|
|
533
|
+
*/
|
|
534
|
+
export const hasDefaultSnmpCommunity = (node: ConfigNode): boolean => {
|
|
535
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
536
|
+
rawText.includes('community public') ||
|
|
537
|
+
rawText.includes('community private') ||
|
|
538
|
+
rawText.includes('community "public"') ||
|
|
539
|
+
rawText.includes('community "private"')
|
|
540
|
+
);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Check if NTP authentication is enabled
|
|
545
|
+
*/
|
|
546
|
+
export const hasNtpAuthentication = (node: ConfigNode): boolean => {
|
|
547
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
548
|
+
rawText.includes('authentication-check') ||
|
|
549
|
+
rawText.includes('authentication-key') ||
|
|
550
|
+
rawText.includes('message-digest')
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Check if management access filter is configured
|
|
556
|
+
*/
|
|
557
|
+
export const hasManagementAccessFilter = (node: ConfigNode): boolean => {
|
|
558
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
559
|
+
rawText.includes('management-access-filter')
|
|
560
|
+
);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Check if MAF has default-action deny
|
|
565
|
+
*/
|
|
566
|
+
export const hasMafDefaultDeny = (node: ConfigNode): boolean => {
|
|
567
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
568
|
+
rawText.includes('default-action deny') || rawText === 'default-action deny'
|
|
569
|
+
);
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// ============================================================================
|
|
573
|
+
// Control Plane Security Helpers
|
|
574
|
+
// ============================================================================
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Check if OSPF authentication is configured (auth-keychain or authentication-key)
|
|
578
|
+
*/
|
|
579
|
+
export const hasOspfAuthentication = (node: ConfigNode): boolean => {
|
|
580
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
581
|
+
rawText.includes('auth-keychain') ||
|
|
582
|
+
rawText.includes('authentication-key') ||
|
|
583
|
+
rawText.includes('message-digest-key')
|
|
584
|
+
);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Check if IS-IS authentication is configured
|
|
589
|
+
*/
|
|
590
|
+
export const hasIsisAuthentication = (node: ConfigNode): boolean => {
|
|
591
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
592
|
+
rawText.includes('auth-keychain') || rawText.includes('authentication-key')
|
|
593
|
+
);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Check if LDP authentication is configured
|
|
598
|
+
*/
|
|
599
|
+
export const hasLdpAuthentication = (node: ConfigNode): boolean => {
|
|
600
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
601
|
+
rawText.includes('auth-keychain') || rawText.includes('authentication-key')
|
|
602
|
+
);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Check if RSVP authentication is configured
|
|
607
|
+
*/
|
|
608
|
+
export const hasRsvpAuthentication = (node: ConfigNode): boolean => {
|
|
609
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
610
|
+
rawText.includes('auth-keychain') || rawText.includes('authentication-key')
|
|
611
|
+
);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// ============================================================================
|
|
615
|
+
// BGP Security Helpers
|
|
616
|
+
// ============================================================================
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Check if BGP authentication is configured (auth-keychain or authentication-key)
|
|
620
|
+
*/
|
|
621
|
+
export const hasBgpAuthentication = (node: ConfigNode): boolean => {
|
|
622
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
623
|
+
rawText.includes('auth-keychain') ||
|
|
624
|
+
rawText.includes('authentication-key') ||
|
|
625
|
+
rawText.includes('password')
|
|
626
|
+
);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Check if BGP TTL security (GTSM) is configured
|
|
631
|
+
*/
|
|
632
|
+
export const hasBgpTtlSecurity = (node: ConfigNode): boolean => {
|
|
633
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('ttl-security'));
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Check if BGP prefix-limit is configured
|
|
638
|
+
*/
|
|
639
|
+
export const hasBgpPrefixLimit = (node: ConfigNode): boolean => {
|
|
640
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('prefix-limit'));
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Check if BGP graceful restart is configured
|
|
645
|
+
*/
|
|
646
|
+
export const hasBgpGracefulRestart = (node: ConfigNode): boolean => {
|
|
647
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('graceful-restart'));
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Check if BGP import/export policies are configured
|
|
652
|
+
*/
|
|
653
|
+
export const hasBgpPolicies = (node: ConfigNode): boolean => {
|
|
654
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
655
|
+
rawText.includes('import') || rawText.includes('export')
|
|
656
|
+
);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Check if BGP group is external type
|
|
661
|
+
*/
|
|
662
|
+
export const isBgpExternalGroup = (node: ConfigNode): boolean => {
|
|
663
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('type external'));
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// ============================================================================
|
|
667
|
+
// CPM Filter Helpers
|
|
668
|
+
// ============================================================================
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Check if CPM filter is configured
|
|
672
|
+
*/
|
|
673
|
+
export const hasCpmFilter = (node: ConfigNode): boolean => {
|
|
674
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('cpm-filter'));
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Check if CPM filter has default-action drop
|
|
679
|
+
*/
|
|
680
|
+
export const hasCpmFilterDefaultDrop = (node: ConfigNode): boolean => {
|
|
681
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('default-action drop'));
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Check if protocol protection is enabled
|
|
686
|
+
*/
|
|
687
|
+
export const hasProtocolProtection = (node: ConfigNode): boolean => {
|
|
688
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
689
|
+
rawText.includes('protocol-protection') || rawText.includes('cpu-protection')
|
|
690
|
+
);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// ============================================================================
|
|
694
|
+
// Data Plane Security Helpers
|
|
695
|
+
// ============================================================================
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Check if uRPF is configured
|
|
699
|
+
*/
|
|
700
|
+
export const hasUrpf = (node: ConfigNode): boolean => {
|
|
701
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('urpf-check'));
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Get uRPF mode (strict, loose, or undefined)
|
|
706
|
+
*/
|
|
707
|
+
export const getUrpfMode = (node: ConfigNode): 'strict' | 'loose' | undefined => {
|
|
708
|
+
// Search recursively for urpf-check and mode
|
|
709
|
+
let mode: 'strict' | 'loose' | undefined;
|
|
710
|
+
const findMode = (n: ConfigNode): void => {
|
|
711
|
+
const rawText = n.rawText.toLowerCase();
|
|
712
|
+
if (rawText.includes('urpf-check')) {
|
|
713
|
+
if (rawText.includes('strict')) {
|
|
714
|
+
mode = 'strict';
|
|
715
|
+
} else if (rawText.includes('loose')) {
|
|
716
|
+
mode = 'loose';
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (rawText.includes('mode strict')) {
|
|
720
|
+
mode = 'strict';
|
|
721
|
+
} else if (rawText.includes('mode loose')) {
|
|
722
|
+
mode = 'loose';
|
|
723
|
+
}
|
|
724
|
+
n.children.forEach(findMode);
|
|
725
|
+
};
|
|
726
|
+
findMode(node);
|
|
727
|
+
return mode;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Check if IP filter is applied
|
|
732
|
+
*/
|
|
733
|
+
export const hasIpFilter = (node: ConfigNode): boolean => {
|
|
734
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
735
|
+
rawText.includes('ip-filter') ||
|
|
736
|
+
rawText.includes('ingress filter') ||
|
|
737
|
+
rawText.includes('egress filter')
|
|
738
|
+
);
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// ============================================================================
|
|
742
|
+
// Logging Helpers
|
|
743
|
+
// ============================================================================
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Check if syslog is configured
|
|
747
|
+
*/
|
|
748
|
+
export const hasSyslog = (node: ConfigNode): boolean => {
|
|
749
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
750
|
+
rawText.startsWith('syslog') || rawText.includes('syslog')
|
|
751
|
+
);
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Check if SNMP trap group is configured
|
|
756
|
+
*/
|
|
757
|
+
export const hasSnmpTrapGroup = (node: ConfigNode): boolean => {
|
|
758
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('snmp-trap-group'));
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Check if event-control is configured
|
|
763
|
+
*/
|
|
764
|
+
export const hasEventControl = (node: ConfigNode): boolean => {
|
|
765
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('event-control'));
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Check if accounting policy is configured
|
|
770
|
+
*/
|
|
771
|
+
export const hasAccountingPolicy = (node: ConfigNode): boolean => {
|
|
772
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('accounting-policy'));
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// ============================================================================
|
|
776
|
+
// High Availability Helpers
|
|
777
|
+
// ============================================================================
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Check if BFD is enabled on interface
|
|
781
|
+
*/
|
|
782
|
+
export const hasBfd = (node: ConfigNode): boolean => {
|
|
783
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
784
|
+
rawText.includes('bfd-liveness') || rawText.includes('bfd')
|
|
785
|
+
);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Check if MC-LAG is configured
|
|
790
|
+
*/
|
|
791
|
+
export const hasMcLag = (node: ConfigNode): boolean => {
|
|
792
|
+
return searchNodeRecursively(node, (rawText) =>
|
|
793
|
+
rawText.includes('mc-lag') || rawText.includes('multi-chassis')
|
|
794
|
+
);
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Check if MC-LAG authentication is configured
|
|
799
|
+
*/
|
|
800
|
+
export const hasMcLagAuthentication = (node: ConfigNode): boolean => {
|
|
801
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('authentication-key'));
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Check if LACP is configured
|
|
806
|
+
*/
|
|
807
|
+
export const hasLacp = (node: ConfigNode): boolean => {
|
|
808
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('lacp'));
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// ============================================================================
|
|
812
|
+
// Service Security Helpers
|
|
813
|
+
// ============================================================================
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Check if VPRN has route-distinguisher configured
|
|
817
|
+
*/
|
|
818
|
+
export const hasRouteDistinguisher = (node: ConfigNode): boolean => {
|
|
819
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('route-distinguisher'));
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Check if VPRN has vrf-target configured
|
|
824
|
+
*/
|
|
825
|
+
export const hasVrfTarget = (node: ConfigNode): boolean => {
|
|
826
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('vrf-target'));
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Check if GRT leaking is configured (potential security concern)
|
|
831
|
+
*/
|
|
832
|
+
export const hasGrtLeaking = (node: ConfigNode): boolean => {
|
|
833
|
+
return searchNodeRecursively(node, (rawText) => rawText.includes('grt-leaking'));
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Get BGP neighbor IP address from neighbor node
|
|
838
|
+
*/
|
|
839
|
+
export const getBgpNeighborIp = (node: ConfigNode): string => {
|
|
840
|
+
const match = node.id.match(/neighbor\s+"?([^"]+)"?|neighbor\s+([\d.:a-fA-F]+)/i);
|
|
841
|
+
if (match) {
|
|
842
|
+
return match[1] ?? match[2] ?? node.id.replace(/^neighbor\s+/i, '').trim();
|
|
843
|
+
}
|
|
844
|
+
return node.id.replace(/^neighbor\s+/i, '').trim();
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Get BGP group name from group node
|
|
849
|
+
*/
|
|
850
|
+
export const getBgpGroupName = (node: ConfigNode): string => {
|
|
851
|
+
const match = node.id.match(/group\s+"([^"]+)"/i);
|
|
852
|
+
if (match?.[1]) {
|
|
853
|
+
return match[1];
|
|
854
|
+
}
|
|
855
|
+
return node.id.replace(/^group\s+/i, '').replace(/"/g, '').trim();
|
|
856
|
+
};
|