@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,790 @@
|
|
|
1
|
+
// packages/rule-helpers/src/huawei/helpers.ts
|
|
2
|
+
// Huawei VRP-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 interface is shutdown (using Huawei's 'undo shutdown' pattern)
|
|
12
|
+
* In Huawei, interfaces are shutdown by default; 'undo shutdown' enables them
|
|
13
|
+
*/
|
|
14
|
+
export const isShutdown = (node: ConfigNode): boolean => {
|
|
15
|
+
// Check if there's a 'shutdown' command
|
|
16
|
+
const hasShutdown = node.children.some((child) => {
|
|
17
|
+
const id = child.id.toLowerCase().trim();
|
|
18
|
+
return id === 'shutdown';
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Check if there's an 'undo shutdown' command
|
|
22
|
+
const hasUndoShutdown = node.children.some((child) => {
|
|
23
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
24
|
+
return rawText === 'undo shutdown';
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Shutdown if explicitly shutdown OR no undo shutdown (Huawei default is shutdown)
|
|
28
|
+
return hasShutdown || !hasUndoShutdown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if interface is explicitly enabled (has 'undo shutdown')
|
|
33
|
+
*/
|
|
34
|
+
export const isEnabled = (node: ConfigNode): boolean => {
|
|
35
|
+
return node.children.some((child) => {
|
|
36
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
37
|
+
return rawText === 'undo shutdown';
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if interface is a physical port (not Vlanif, LoopBack, NULL, Tunnel, etc.)
|
|
43
|
+
*/
|
|
44
|
+
export const isPhysicalPort = (interfaceName: string): boolean => {
|
|
45
|
+
const name = interfaceName.toLowerCase();
|
|
46
|
+
return (
|
|
47
|
+
!name.includes('vlanif') &&
|
|
48
|
+
!name.includes('loopback') &&
|
|
49
|
+
!name.includes('null') &&
|
|
50
|
+
!name.includes('tunnel') &&
|
|
51
|
+
!name.includes('eth-trunk') &&
|
|
52
|
+
!name.includes('nve') &&
|
|
53
|
+
!name.includes('vbdif')
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if interface is a VLAN interface (Vlanif)
|
|
59
|
+
*/
|
|
60
|
+
export const isVlanInterface = (interfaceName: string): boolean => {
|
|
61
|
+
return interfaceName.toLowerCase().includes('vlanif');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if interface is a loopback interface
|
|
66
|
+
*/
|
|
67
|
+
export const isLoopbackInterface = (interfaceName: string): boolean => {
|
|
68
|
+
return interfaceName.toLowerCase().includes('loopback');
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if interface is an Eth-Trunk (LAG)
|
|
73
|
+
*/
|
|
74
|
+
export const isEthTrunk = (interfaceName: string): boolean => {
|
|
75
|
+
return interfaceName.toLowerCase().includes('eth-trunk');
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if interface is configured as trunk port
|
|
80
|
+
*/
|
|
81
|
+
export const isTrunkPort = (node: ConfigNode): boolean => {
|
|
82
|
+
return node.children.some((child) => {
|
|
83
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
84
|
+
return rawText?.includes('port link-type trunk') ?? false;
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if interface is configured as access port
|
|
90
|
+
*/
|
|
91
|
+
export const isAccessPort = (node: ConfigNode): boolean => {
|
|
92
|
+
return node.children.some((child) => {
|
|
93
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
94
|
+
return rawText?.includes('port link-type access') ?? false;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if interface is configured as hybrid port
|
|
100
|
+
*/
|
|
101
|
+
export const isHybridPort = (node: ConfigNode): boolean => {
|
|
102
|
+
return node.children.some((child) => {
|
|
103
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
104
|
+
return rawText?.includes('port link-type hybrid') ?? false;
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the default VLAN for an access port
|
|
110
|
+
*/
|
|
111
|
+
export const getDefaultVlan = (node: ConfigNode): string | undefined => {
|
|
112
|
+
const vlanCmd = node.children.find((child) => {
|
|
113
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
114
|
+
return rawText?.startsWith('port default vlan') ?? false;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (vlanCmd?.rawText) {
|
|
118
|
+
const match = vlanCmd.rawText.match(/port\s+default\s+vlan\s+(\d+)/i);
|
|
119
|
+
const vlan = match?.[1];
|
|
120
|
+
if (vlan) {
|
|
121
|
+
return vlan;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get allowed VLANs for trunk port
|
|
129
|
+
*/
|
|
130
|
+
export const getTrunkAllowedVlans = (node: ConfigNode): string | undefined => {
|
|
131
|
+
const vlanCmd = node.children.find((child) => {
|
|
132
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
133
|
+
return rawText?.startsWith('port trunk allow-pass vlan') ?? false;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (vlanCmd?.rawText) {
|
|
137
|
+
const match = vlanCmd.rawText.match(/port\s+trunk\s+allow-pass\s+vlan\s+(.+)/i);
|
|
138
|
+
const vlans = match?.[1];
|
|
139
|
+
if (vlans) {
|
|
140
|
+
return vlans.trim();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if interface has description
|
|
148
|
+
*/
|
|
149
|
+
export const hasDescription = (node: ConfigNode): boolean => {
|
|
150
|
+
return hasChildCommand(node, 'description');
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get interface description
|
|
155
|
+
*/
|
|
156
|
+
export const getDescription = (node: ConfigNode): string | undefined => {
|
|
157
|
+
const descCmd = getChildCommand(node, 'description');
|
|
158
|
+
if (descCmd?.rawText) {
|
|
159
|
+
const match = descCmd.rawText.match(/description\s+(.+)/i);
|
|
160
|
+
const description = match?.[1];
|
|
161
|
+
if (description) {
|
|
162
|
+
return description.trim();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if STP edge port is enabled (stp edged-port enable)
|
|
170
|
+
*/
|
|
171
|
+
export const hasStpEdgedPort = (node: ConfigNode): boolean => {
|
|
172
|
+
return node.children.some((child) => {
|
|
173
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
174
|
+
return rawText?.includes('stp edged-port enable') ?? false;
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if port security is enabled
|
|
180
|
+
*/
|
|
181
|
+
export const hasPortSecurity = (node: ConfigNode): boolean => {
|
|
182
|
+
return node.children.some((child) => {
|
|
183
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
184
|
+
return rawText?.includes('port-security enable') ?? false;
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if BPDU protection is enabled
|
|
190
|
+
*/
|
|
191
|
+
export const hasBpduProtection = (node: ConfigNode): boolean => {
|
|
192
|
+
return node.children.some((child) => {
|
|
193
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
194
|
+
if (!rawText) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return rawText.includes('stp bpdu-protection') || rawText.includes('bpdu-protection enable');
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get child command value for 'set <key> <value>' style commands
|
|
203
|
+
*/
|
|
204
|
+
export const getCommandValue = (node: ConfigNode, command: string): string | undefined => {
|
|
205
|
+
const cmd = node.children.find((child) => {
|
|
206
|
+
const text = child.rawText?.toLowerCase().trim();
|
|
207
|
+
return text?.startsWith(command.toLowerCase()) ?? false;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (cmd?.rawText) {
|
|
211
|
+
const rest = cmd.rawText.substring(command.length).trim();
|
|
212
|
+
return rest || undefined;
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if SSH is enabled
|
|
219
|
+
*/
|
|
220
|
+
export const isSshEnabled = (node: ConfigNode): boolean => {
|
|
221
|
+
if (node.id.toLowerCase().includes('user-interface')) {
|
|
222
|
+
return node.children.some((child) => {
|
|
223
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
224
|
+
return rawText?.includes('protocol inbound ssh') || rawText === 'protocol inbound all';
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if Telnet is enabled (security concern)
|
|
232
|
+
*/
|
|
233
|
+
export const isTelnetEnabled = (node: ConfigNode): boolean => {
|
|
234
|
+
if (node.id.toLowerCase().includes('user-interface')) {
|
|
235
|
+
return node.children.some((child) => {
|
|
236
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
237
|
+
return (
|
|
238
|
+
rawText.includes('protocol inbound telnet') ||
|
|
239
|
+
rawText === 'protocol inbound all' ||
|
|
240
|
+
// Default for VTY is often telnet
|
|
241
|
+
(!rawText.includes('protocol inbound'))
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if authentication mode AAA is configured
|
|
250
|
+
*/
|
|
251
|
+
export const hasAaaAuthentication = (node: ConfigNode): boolean => {
|
|
252
|
+
return node.children.some((child) => {
|
|
253
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
254
|
+
return rawText.includes('authentication-mode aaa');
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if password authentication is configured (less secure)
|
|
260
|
+
*/
|
|
261
|
+
export const hasPasswordAuthentication = (node: ConfigNode): boolean => {
|
|
262
|
+
return node.children.some((child) => {
|
|
263
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
264
|
+
return rawText.includes('authentication-mode password');
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if idle timeout is configured
|
|
270
|
+
*/
|
|
271
|
+
export const hasIdleTimeout = (node: ConfigNode): boolean => {
|
|
272
|
+
return hasChildCommand(node, 'idle-timeout');
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get idle timeout value in minutes
|
|
277
|
+
*/
|
|
278
|
+
export const getIdleTimeout = (node: ConfigNode): number | undefined => {
|
|
279
|
+
const timeoutCmd = getChildCommand(node, 'idle-timeout');
|
|
280
|
+
if (timeoutCmd?.rawText) {
|
|
281
|
+
const match = timeoutCmd.rawText.match(/idle-timeout\s+(\d+)/i);
|
|
282
|
+
const timeout = match?.[1];
|
|
283
|
+
if (timeout) {
|
|
284
|
+
return parseInt(timeout, 10);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return undefined;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if ACL is applied inbound on user-interface
|
|
292
|
+
*/
|
|
293
|
+
export const hasAclInbound = (node: ConfigNode): boolean => {
|
|
294
|
+
return node.children.some((child) => {
|
|
295
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
296
|
+
return rawText.match(/acl\s+\d+\s+inbound/);
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Find a stanza by name in the configuration tree
|
|
302
|
+
*/
|
|
303
|
+
export const findStanza = (node: ConfigNode, stanzaName: string): ConfigNode | undefined => {
|
|
304
|
+
if (node.id.toLowerCase().startsWith(stanzaName.toLowerCase())) {
|
|
305
|
+
return node;
|
|
306
|
+
}
|
|
307
|
+
for (const child of node.children) {
|
|
308
|
+
const found = findStanza(child, stanzaName);
|
|
309
|
+
if (found) return found;
|
|
310
|
+
}
|
|
311
|
+
return undefined;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Find all stanzas by name in the configuration tree
|
|
316
|
+
*/
|
|
317
|
+
export const findStanzas = (node: ConfigNode, stanzaName: string): ConfigNode[] => {
|
|
318
|
+
const results: ConfigNode[] = [];
|
|
319
|
+
if (node.id.toLowerCase().startsWith(stanzaName.toLowerCase())) {
|
|
320
|
+
results.push(node);
|
|
321
|
+
}
|
|
322
|
+
for (const child of node.children) {
|
|
323
|
+
results.push(...findStanzas(child, stanzaName));
|
|
324
|
+
}
|
|
325
|
+
return results;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check if local-user has password configured with cipher (encrypted)
|
|
330
|
+
*/
|
|
331
|
+
export const hasEncryptedPassword = (node: ConfigNode): boolean => {
|
|
332
|
+
return node.children.some((child) => {
|
|
333
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
334
|
+
return (
|
|
335
|
+
rawText.includes('password cipher') ||
|
|
336
|
+
rawText.includes('password irreversible-cipher')
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if local-user has plaintext password (security concern)
|
|
343
|
+
*/
|
|
344
|
+
export const hasPlaintextPassword = (node: ConfigNode): boolean => {
|
|
345
|
+
return node.children.some((child) => {
|
|
346
|
+
const rawText = child.rawText.toLowerCase().trim();
|
|
347
|
+
return rawText.includes('password simple');
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get privilege level for local-user
|
|
353
|
+
*/
|
|
354
|
+
export const getPrivilegeLevel = (node: ConfigNode): number | undefined => {
|
|
355
|
+
const privCmd = node.children.find((child) => {
|
|
356
|
+
const rawText = child.rawText?.toLowerCase();
|
|
357
|
+
return rawText?.includes('privilege level');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (privCmd?.rawText) {
|
|
361
|
+
const match = privCmd.rawText.match(/privilege\s+level\s+(\d+)/i);
|
|
362
|
+
const level = match?.[1];
|
|
363
|
+
if (level) {
|
|
364
|
+
return parseInt(level, 10);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// ============================================================================
|
|
371
|
+
// BGP Helper Functions
|
|
372
|
+
// ============================================================================
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Check if BGP peer has password authentication configured
|
|
376
|
+
*/
|
|
377
|
+
export const hasBgpPeerPassword = (node: ConfigNode, peerIp: string): boolean => {
|
|
378
|
+
return node.children.some((child) => {
|
|
379
|
+
const rawText = child.rawText?.toLowerCase();
|
|
380
|
+
return rawText?.includes(`peer ${peerIp.toLowerCase()}`) && rawText?.includes('password');
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check if BGP peer has keychain authentication configured
|
|
386
|
+
*/
|
|
387
|
+
export const hasBgpPeerKeychain = (node: ConfigNode, peerIp: string): boolean => {
|
|
388
|
+
return node.children.some((child) => {
|
|
389
|
+
const rawText = child.rawText?.toLowerCase();
|
|
390
|
+
return rawText?.includes(`peer ${peerIp.toLowerCase()}`) && rawText?.includes('keychain');
|
|
391
|
+
});
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Check if BGP peer has GTSM (valid-ttl-hops) configured
|
|
396
|
+
*/
|
|
397
|
+
export const hasBgpPeerGtsm = (node: ConfigNode, peerIp: string): boolean => {
|
|
398
|
+
return node.children.some((child) => {
|
|
399
|
+
const rawText = child.rawText?.toLowerCase();
|
|
400
|
+
return rawText?.includes(`peer ${peerIp.toLowerCase()}`) && rawText?.includes('valid-ttl-hops');
|
|
401
|
+
});
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Check if BGP peer has route-limit (maximum prefix) configured
|
|
406
|
+
*/
|
|
407
|
+
export const hasBgpPeerRouteLimit = (node: ConfigNode, peerIp: string): boolean => {
|
|
408
|
+
return node.children.some((child) => {
|
|
409
|
+
const rawText = child.rawText?.toLowerCase();
|
|
410
|
+
return rawText?.includes(`peer ${peerIp.toLowerCase()}`) && rawText?.includes('route-limit');
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Check if BGP peer has prefix filtering configured (ip-prefix or route-policy)
|
|
416
|
+
*/
|
|
417
|
+
export const hasBgpPeerPrefixFilter = (node: ConfigNode, peerIp: string): boolean => {
|
|
418
|
+
return node.children.some((child) => {
|
|
419
|
+
const rawText = child.rawText?.toLowerCase();
|
|
420
|
+
return (
|
|
421
|
+
rawText?.includes(`peer ${peerIp.toLowerCase()}`) &&
|
|
422
|
+
(rawText?.includes('ip-prefix') || rawText?.includes('route-policy') || rawText?.includes('filter-policy'))
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get all BGP peer IP addresses from config
|
|
429
|
+
*/
|
|
430
|
+
export const getBgpPeers = (node: ConfigNode): string[] => {
|
|
431
|
+
const peers: string[] = [];
|
|
432
|
+
for (const child of node.children) {
|
|
433
|
+
if (child.rawText) {
|
|
434
|
+
const match = child.rawText.match(/peer\s+([\d.]+)\s+as-number/i);
|
|
435
|
+
if (match?.[1]) {
|
|
436
|
+
peers.push(match[1]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return peers;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Check if BGP has graceful-restart enabled
|
|
445
|
+
*/
|
|
446
|
+
export const hasBgpGracefulRestart = (node: ConfigNode): boolean => {
|
|
447
|
+
return node.children.some((child) => {
|
|
448
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
449
|
+
return rawText === 'graceful-restart' || rawText?.startsWith('graceful-restart ');
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// OSPF/IS-IS Helper Functions
|
|
455
|
+
// ============================================================================
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check if OSPF area has authentication configured
|
|
459
|
+
*/
|
|
460
|
+
export const hasOspfAreaAuthentication = (node: ConfigNode): boolean => {
|
|
461
|
+
return node.children.some((child) => {
|
|
462
|
+
const rawText = child.rawText?.toLowerCase();
|
|
463
|
+
return rawText?.includes('authentication-mode');
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Check if interface has OSPF authentication configured
|
|
469
|
+
*/
|
|
470
|
+
export const hasInterfaceOspfAuth = (node: ConfigNode): boolean => {
|
|
471
|
+
return node.children.some((child) => {
|
|
472
|
+
const rawText = child.rawText?.toLowerCase();
|
|
473
|
+
return rawText?.includes('ospf authentication-mode');
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Check if IS-IS has area authentication configured
|
|
479
|
+
*/
|
|
480
|
+
export const hasIsisAreaAuth = (node: ConfigNode): boolean => {
|
|
481
|
+
return node.children.some((child) => {
|
|
482
|
+
const rawText = child.rawText?.toLowerCase();
|
|
483
|
+
return rawText?.includes('area-authentication-mode');
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Check if IS-IS has domain authentication configured
|
|
489
|
+
*/
|
|
490
|
+
export const hasIsisDomainAuth = (node: ConfigNode): boolean => {
|
|
491
|
+
return node.children.some((child) => {
|
|
492
|
+
const rawText = child.rawText?.toLowerCase();
|
|
493
|
+
return rawText?.includes('domain-authentication-mode');
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Check if interface has IS-IS authentication configured
|
|
499
|
+
*/
|
|
500
|
+
export const hasInterfaceIsisAuth = (node: ConfigNode): boolean => {
|
|
501
|
+
return node.children.some((child) => {
|
|
502
|
+
const rawText = child.rawText?.toLowerCase();
|
|
503
|
+
return rawText?.includes('isis authentication-mode');
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// ============================================================================
|
|
508
|
+
// VRRP Helper Functions
|
|
509
|
+
// ============================================================================
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Check if interface has VRRP configured
|
|
513
|
+
*/
|
|
514
|
+
export const hasVrrp = (node: ConfigNode): boolean => {
|
|
515
|
+
return node.children.some((child) => {
|
|
516
|
+
const rawText = child.rawText?.toLowerCase();
|
|
517
|
+
return rawText?.includes('vrrp vrid');
|
|
518
|
+
});
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Check if VRRP has authentication configured
|
|
523
|
+
* In Huawei VRP, authentication can be on a single line: "vrrp vrid 1 authentication-mode md5 <key>"
|
|
524
|
+
*/
|
|
525
|
+
export const hasVrrpAuthentication = (node: ConfigNode): boolean => {
|
|
526
|
+
return node.children.some((child) => {
|
|
527
|
+
const rawText = child.rawText?.toLowerCase();
|
|
528
|
+
// Check if line contains both vrrp vrid and authentication-mode
|
|
529
|
+
return rawText?.includes('vrrp vrid') && rawText?.includes('authentication-mode');
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get VRRP VRID from interface
|
|
535
|
+
*/
|
|
536
|
+
export const getVrrpVrid = (node: ConfigNode): string | undefined => {
|
|
537
|
+
const vrrpCmd = node.children.find((child) => {
|
|
538
|
+
return child.rawText?.toLowerCase().includes('vrrp vrid');
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (vrrpCmd?.rawText) {
|
|
542
|
+
const match = vrrpCmd.rawText.match(/vrrp\s+vrid\s+(\d+)/i);
|
|
543
|
+
if (match?.[1]) {
|
|
544
|
+
return match[1];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return undefined;
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// ============================================================================
|
|
551
|
+
// Interface Security Helper Functions
|
|
552
|
+
// ============================================================================
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Check if ICMP redirect is disabled on interface
|
|
556
|
+
*/
|
|
557
|
+
export const hasIcmpRedirectDisabled = (node: ConfigNode): boolean => {
|
|
558
|
+
return node.children.some((child) => {
|
|
559
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
560
|
+
return rawText === 'undo icmp redirect send';
|
|
561
|
+
});
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Check if directed broadcast is disabled on interface
|
|
566
|
+
*/
|
|
567
|
+
export const hasDirectedBroadcastDisabled = (node: ConfigNode): boolean => {
|
|
568
|
+
return node.children.some((child) => {
|
|
569
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
570
|
+
return rawText === 'undo ip directed-broadcast enable' || rawText === 'undo ip directed-broadcast';
|
|
571
|
+
});
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Check if ARP proxy is disabled on interface
|
|
576
|
+
*/
|
|
577
|
+
export const hasArpProxyDisabled = (node: ConfigNode): boolean => {
|
|
578
|
+
return node.children.some((child) => {
|
|
579
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
580
|
+
return rawText === 'undo arp proxy enable' || rawText === 'undo proxy-arp';
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Check if uRPF is enabled on interface
|
|
586
|
+
*/
|
|
587
|
+
export const hasUrpf = (node: ConfigNode): boolean => {
|
|
588
|
+
return node.children.some((child) => {
|
|
589
|
+
const rawText = child.rawText?.toLowerCase();
|
|
590
|
+
return rawText?.includes('urpf strict') || rawText?.includes('urpf loose');
|
|
591
|
+
});
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Get uRPF mode (strict or loose)
|
|
596
|
+
*/
|
|
597
|
+
export const getUrpfMode = (node: ConfigNode): 'strict' | 'loose' | undefined => {
|
|
598
|
+
const urpfCmd = node.children.find((child) => {
|
|
599
|
+
const rawText = child.rawText?.toLowerCase();
|
|
600
|
+
return rawText?.includes('urpf');
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
if (urpfCmd?.rawText) {
|
|
604
|
+
const rawText = urpfCmd.rawText.toLowerCase();
|
|
605
|
+
if (rawText.includes('urpf strict')) return 'strict';
|
|
606
|
+
if (rawText.includes('urpf loose')) return 'loose';
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Check if LLDP is disabled on interface
|
|
613
|
+
*/
|
|
614
|
+
export const hasLldpDisabled = (node: ConfigNode): boolean => {
|
|
615
|
+
return node.children.some((child) => {
|
|
616
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
617
|
+
return rawText === 'undo lldp enable';
|
|
618
|
+
});
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// NTP Helper Functions
|
|
623
|
+
// ============================================================================
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Check if NTP authentication is enabled
|
|
627
|
+
*/
|
|
628
|
+
export const hasNtpAuthentication = (node: ConfigNode): boolean => {
|
|
629
|
+
return node.children.some((child) => {
|
|
630
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
631
|
+
return rawText === 'authentication enable';
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Check if NTP has authentication key configured
|
|
637
|
+
*/
|
|
638
|
+
export const hasNtpAuthKey = (node: ConfigNode): boolean => {
|
|
639
|
+
return node.children.some((child) => {
|
|
640
|
+
const rawText = child.rawText?.toLowerCase();
|
|
641
|
+
return rawText?.includes('authentication-keyid');
|
|
642
|
+
});
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// ============================================================================
|
|
646
|
+
// SSH Server Helper Functions
|
|
647
|
+
// ============================================================================
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Check if SSH server has strong ciphers configured
|
|
651
|
+
*/
|
|
652
|
+
export const hasSshStrongCiphers = (node: ConfigNode): boolean => {
|
|
653
|
+
// Check global config for ssh server cipher settings
|
|
654
|
+
const rawText = node.rawText?.toLowerCase();
|
|
655
|
+
if (rawText?.includes('ssh server cipher')) {
|
|
656
|
+
// Check for strong ciphers (aes256, aes128)
|
|
657
|
+
return rawText.includes('aes256') || rawText.includes('aes128');
|
|
658
|
+
}
|
|
659
|
+
return false;
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Check for weak SSH algorithms
|
|
664
|
+
*/
|
|
665
|
+
export const hasWeakSshAlgorithms = (node: ConfigNode): boolean => {
|
|
666
|
+
const rawText = node.rawText?.toLowerCase();
|
|
667
|
+
// Check for weak algorithms like 3des, des, arcfour
|
|
668
|
+
return (
|
|
669
|
+
rawText?.includes('3des') ||
|
|
670
|
+
rawText?.includes('arcfour') ||
|
|
671
|
+
(rawText?.includes('des') && !rawText.includes('aes') && !rawText.includes('3des'))
|
|
672
|
+
) ?? false;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Check if SSH uses strong HMAC algorithms
|
|
677
|
+
*/
|
|
678
|
+
export const hasSshStrongHmac = (node: ConfigNode): boolean => {
|
|
679
|
+
const rawText = node.rawText?.toLowerCase();
|
|
680
|
+
if (rawText?.includes('ssh server hmac')) {
|
|
681
|
+
return rawText.includes('sha2-256') || rawText.includes('sha2-512');
|
|
682
|
+
}
|
|
683
|
+
return false;
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Check if SSH uses strong key exchange
|
|
688
|
+
*/
|
|
689
|
+
export const hasSshStrongKeyExchange = (node: ConfigNode): boolean => {
|
|
690
|
+
const rawText = node.rawText?.toLowerCase();
|
|
691
|
+
if (rawText?.includes('ssh server key-exchange')) {
|
|
692
|
+
return rawText.includes('dh-group14') || rawText.includes('dh-group16') || rawText.includes('dh-group18');
|
|
693
|
+
}
|
|
694
|
+
return false;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// ============================================================================
|
|
698
|
+
// CPU-Defend Helper Functions
|
|
699
|
+
// ============================================================================
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Check if CPU-defend policy is configured
|
|
703
|
+
*/
|
|
704
|
+
export const hasCpuDefendPolicy = (node: ConfigNode): boolean => {
|
|
705
|
+
return node.id.toLowerCase().startsWith('cpu-defend policy');
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Check if CPU-defend policy has auto-defend enabled
|
|
710
|
+
*/
|
|
711
|
+
export const hasCpuDefendAutoDefend = (node: ConfigNode): boolean => {
|
|
712
|
+
return node.children.some((child) => {
|
|
713
|
+
const rawText = child.rawText?.toLowerCase().trim();
|
|
714
|
+
return rawText === 'auto-defend enable';
|
|
715
|
+
});
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Check if CPU-defend policy is applied
|
|
720
|
+
*/
|
|
721
|
+
export const isCpuDefendPolicyApplied = (rawText: string): boolean => {
|
|
722
|
+
return rawText.toLowerCase().startsWith('cpu-defend-policy');
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// ============================================================================
|
|
726
|
+
// Login Banner Helper Functions
|
|
727
|
+
// ============================================================================
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Check if login banner is configured
|
|
731
|
+
*/
|
|
732
|
+
export const hasLoginBanner = (node: ConfigNode): boolean => {
|
|
733
|
+
return node.id.toLowerCase().startsWith('header login');
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// ============================================================================
|
|
737
|
+
// Service Status Helper Functions
|
|
738
|
+
// ============================================================================
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Check if FTP server is disabled
|
|
742
|
+
*/
|
|
743
|
+
export const isFtpDisabled = (rawText: string): boolean => {
|
|
744
|
+
return rawText.toLowerCase().trim() === 'undo ftp server enable';
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Check if HTTP server is disabled
|
|
749
|
+
*/
|
|
750
|
+
export const isHttpDisabled = (rawText: string): boolean => {
|
|
751
|
+
return rawText.toLowerCase().trim() === 'undo http server enable';
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Check if TFTP server is disabled
|
|
756
|
+
*/
|
|
757
|
+
export const isTftpDisabled = (rawText: string): boolean => {
|
|
758
|
+
return rawText.toLowerCase().trim() === 'undo tftp-server enable';
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Check if IP source route is disabled
|
|
763
|
+
*/
|
|
764
|
+
export const isIpSourceRouteDisabled = (rawText: string): boolean => {
|
|
765
|
+
return rawText.toLowerCase().trim() === 'undo ip source-route';
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
// ============================================================================
|
|
769
|
+
// HWTACACS Helper Functions
|
|
770
|
+
// ============================================================================
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Check if HWTACACS server template has shared-key configured
|
|
774
|
+
*/
|
|
775
|
+
export const hasHwtacacsSharedKey = (node: ConfigNode): boolean => {
|
|
776
|
+
return node.children.some((child) => {
|
|
777
|
+
const rawText = child.rawText?.toLowerCase();
|
|
778
|
+
return rawText?.includes('shared-key cipher');
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Check if HWTACACS has secondary server configured
|
|
784
|
+
*/
|
|
785
|
+
export const hasHwtacacsSecondary = (node: ConfigNode): boolean => {
|
|
786
|
+
return node.children.some((child) => {
|
|
787
|
+
const rawText = child.rawText?.toLowerCase();
|
|
788
|
+
return rawText?.includes('secondary');
|
|
789
|
+
});
|
|
790
|
+
};
|