@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,534 @@
|
|
|
1
|
+
// packages/rule-helpers/src/cisco/helpers.ts
|
|
2
|
+
// Cisco IOS/IOS-XE specific helper functions
|
|
3
|
+
// Based on Cisco Best Practices: docs/Cisco-best-practices.md
|
|
4
|
+
|
|
5
|
+
import type { ConfigNode } from '../../types/ConfigNode';
|
|
6
|
+
import {
|
|
7
|
+
hasChildCommand,
|
|
8
|
+
getChildCommand,
|
|
9
|
+
equalsIgnoreCase,
|
|
10
|
+
includesIgnoreCase,
|
|
11
|
+
startsWithIgnoreCase,
|
|
12
|
+
parseInteger,
|
|
13
|
+
} from '../common/helpers';
|
|
14
|
+
|
|
15
|
+
// Re-export common helpers for convenience
|
|
16
|
+
export { hasChildCommand, getChildCommand, getChildCommands } from '../common/helpers';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if interface is shutdown
|
|
20
|
+
*/
|
|
21
|
+
export const isShutdown = (node: ConfigNode): boolean => {
|
|
22
|
+
return node.children.some((child) => {
|
|
23
|
+
return equalsIgnoreCase(child.id.trim(), 'shutdown');
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if interface is a physical port (not Loopback, Vlan, Null, etc.)
|
|
29
|
+
*/
|
|
30
|
+
export const isPhysicalPort = (interfaceName: string): boolean => {
|
|
31
|
+
return (
|
|
32
|
+
!includesIgnoreCase(interfaceName, 'loopback') &&
|
|
33
|
+
!includesIgnoreCase(interfaceName, 'null') &&
|
|
34
|
+
!includesIgnoreCase(interfaceName, 'vlan') &&
|
|
35
|
+
!includesIgnoreCase(interfaceName, 'tunnel') &&
|
|
36
|
+
!includesIgnoreCase(interfaceName, 'port-channel') &&
|
|
37
|
+
!includesIgnoreCase(interfaceName, 'bvi') &&
|
|
38
|
+
!includesIgnoreCase(interfaceName, 'nve')
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if interface name suggests it's a trunk/uplink based on description
|
|
44
|
+
*/
|
|
45
|
+
export const isLikelyTrunk = (node: ConfigNode): boolean => {
|
|
46
|
+
const desc = getChildCommand(node, 'description');
|
|
47
|
+
if (desc) {
|
|
48
|
+
const descText = desc.rawText;
|
|
49
|
+
return (
|
|
50
|
+
includesIgnoreCase(descText, 'uplink') ||
|
|
51
|
+
includesIgnoreCase(descText, 'downlink') ||
|
|
52
|
+
includesIgnoreCase(descText, 'isl') ||
|
|
53
|
+
includesIgnoreCase(descText, 'trunk') ||
|
|
54
|
+
includesIgnoreCase(descText, 'po-member')
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return hasChildCommand(node, 'switchport mode trunk');
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if interface is configured as trunk
|
|
62
|
+
*/
|
|
63
|
+
export const isTrunkPort = (node: ConfigNode): boolean => {
|
|
64
|
+
return hasChildCommand(node, 'switchport mode trunk');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if interface is configured as access
|
|
69
|
+
*/
|
|
70
|
+
export const isAccessPort = (node: ConfigNode): boolean => {
|
|
71
|
+
return hasChildCommand(node, 'switchport mode access');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if description suggests external-facing interface
|
|
76
|
+
*/
|
|
77
|
+
export const isExternalFacing = (node: ConfigNode): boolean => {
|
|
78
|
+
const desc = getChildCommand(node, 'description');
|
|
79
|
+
if (desc) {
|
|
80
|
+
const descText = desc.rawText;
|
|
81
|
+
return (
|
|
82
|
+
includesIgnoreCase(descText, 'wan:') ||
|
|
83
|
+
includesIgnoreCase(descText, 'external') ||
|
|
84
|
+
includesIgnoreCase(descText, 'internet') ||
|
|
85
|
+
includesIgnoreCase(descText, 'isp') ||
|
|
86
|
+
includesIgnoreCase(descText, 'dmz') ||
|
|
87
|
+
includesIgnoreCase(descText, 'perimeter')
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if description suggests user endpoint port
|
|
95
|
+
*/
|
|
96
|
+
export const isEndpointPort = (node: ConfigNode): boolean => {
|
|
97
|
+
const desc = getChildCommand(node, 'description');
|
|
98
|
+
if (desc) {
|
|
99
|
+
const descText = desc.rawText;
|
|
100
|
+
return (
|
|
101
|
+
includesIgnoreCase(descText, 'endpoint:') ||
|
|
102
|
+
includesIgnoreCase(descText, 'user:') ||
|
|
103
|
+
includesIgnoreCase(descText, 'workstation') ||
|
|
104
|
+
includesIgnoreCase(descText, 'desktop') ||
|
|
105
|
+
includesIgnoreCase(descText, 'desk')
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// Also check if it's an access port without uplink indicators
|
|
109
|
+
return isAccessPort(node) && !isLikelyTrunk(node);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if description suggests phone or AP
|
|
114
|
+
*/
|
|
115
|
+
export const isPhoneOrAP = (node: ConfigNode): boolean => {
|
|
116
|
+
const desc = getChildCommand(node, 'description');
|
|
117
|
+
if (desc) {
|
|
118
|
+
const descText = desc.rawText;
|
|
119
|
+
return (
|
|
120
|
+
includesIgnoreCase(descText, 'phone') ||
|
|
121
|
+
includesIgnoreCase(descText, 'voice') ||
|
|
122
|
+
includesIgnoreCase(descText, 'cisco-ap') ||
|
|
123
|
+
includesIgnoreCase(descText, 'aruba-ap') ||
|
|
124
|
+
includesIgnoreCase(descText, 'ap-') ||
|
|
125
|
+
includesIgnoreCase(descText, '-ap')
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
// Check for voice vlan which indicates phone
|
|
129
|
+
return hasChildCommand(node, 'switchport voice vlan');
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if trunk is connected to a non-Cisco device (server, storage, non-Cisco switch)
|
|
134
|
+
* These require switchport nonegotiate since the other end doesn't speak DTP
|
|
135
|
+
*/
|
|
136
|
+
export const isTrunkToNonCisco = (node: ConfigNode): boolean => {
|
|
137
|
+
const desc = getChildCommand(node, 'description');
|
|
138
|
+
if (desc) {
|
|
139
|
+
const descText = desc.rawText;
|
|
140
|
+
// Non-Cisco endpoints that need nonegotiate
|
|
141
|
+
if (
|
|
142
|
+
includesIgnoreCase(descText, 'server:') ||
|
|
143
|
+
includesIgnoreCase(descText, 'storage:') ||
|
|
144
|
+
includesIgnoreCase(descText, 'esx') ||
|
|
145
|
+
includesIgnoreCase(descText, 'vmware') ||
|
|
146
|
+
includesIgnoreCase(descText, 'hyperv') ||
|
|
147
|
+
includesIgnoreCase(descText, 'hyper-v') ||
|
|
148
|
+
includesIgnoreCase(descText, 'linux') ||
|
|
149
|
+
includesIgnoreCase(descText, 'appliance') ||
|
|
150
|
+
includesIgnoreCase(descText, 'firewall') ||
|
|
151
|
+
includesIgnoreCase(descText, 'loadbalancer') ||
|
|
152
|
+
includesIgnoreCase(descText, 'lb:') ||
|
|
153
|
+
includesIgnoreCase(descText, 'nas:') ||
|
|
154
|
+
includesIgnoreCase(descText, 'san:')
|
|
155
|
+
) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
// Cisco switch connections - DTP is fine
|
|
159
|
+
if (
|
|
160
|
+
includesIgnoreCase(descText, 'uplink:') ||
|
|
161
|
+
includesIgnoreCase(descText, 'downlink:') ||
|
|
162
|
+
includesIgnoreCase(descText, 'isl:') ||
|
|
163
|
+
includesIgnoreCase(descText, 'po-member:')
|
|
164
|
+
) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// No description - can't determine, don't flag
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Management Plane Helpers
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if AAA new-model is configured (global command)
|
|
178
|
+
*/
|
|
179
|
+
export const isAaaNewModel = (node: ConfigNode): boolean => {
|
|
180
|
+
return equalsIgnoreCase(node.id.trim(), 'aaa new-model');
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if password uses strong encryption type (Type 8/9, scrypt, sha256)
|
|
185
|
+
* Type 7 is easily reversible, Type 5 (MD5) is deprecated
|
|
186
|
+
*/
|
|
187
|
+
export const hasStrongPasswordType = (node: ConfigNode): boolean => {
|
|
188
|
+
const rawText = node.rawText;
|
|
189
|
+
// Strong: algorithm-type sha256, algorithm-type scrypt, secret (type 5+)
|
|
190
|
+
// Weak: password (type 0 or 7)
|
|
191
|
+
if (includesIgnoreCase(rawText, 'algorithm-type sha256') || includesIgnoreCase(rawText, 'algorithm-type scrypt')) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (includesIgnoreCase(rawText, ' secret ')) {
|
|
195
|
+
// secret uses MD5 (type 5) minimum, better than password
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if username uses weak password type (Type 7 or plaintext)
|
|
203
|
+
*/
|
|
204
|
+
export const hasWeakUsernamePassword = (node: ConfigNode): boolean => {
|
|
205
|
+
const rawText = node.rawText;
|
|
206
|
+
// Check for "password 7" or just "password" without algorithm-type
|
|
207
|
+
if (includesIgnoreCase(rawText, ' password ')) {
|
|
208
|
+
if (includesIgnoreCase(rawText, 'algorithm-type sha256') || includesIgnoreCase(rawText, 'algorithm-type scrypt')) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get SSH version from configuration
|
|
218
|
+
*/
|
|
219
|
+
export const getSshVersion = (node: ConfigNode): number | null => {
|
|
220
|
+
if (includesIgnoreCase(node.rawText, 'ip ssh version')) {
|
|
221
|
+
const match = node.params.find((p) => p === '1' || p === '2');
|
|
222
|
+
if (match) {
|
|
223
|
+
return parseInteger(match);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if SNMP community is a well-known default
|
|
231
|
+
*/
|
|
232
|
+
export const isDefaultSnmpCommunity = (community: string): boolean => {
|
|
233
|
+
const defaultCommunities = [
|
|
234
|
+
'public',
|
|
235
|
+
'private',
|
|
236
|
+
'community',
|
|
237
|
+
'snmp',
|
|
238
|
+
'admin',
|
|
239
|
+
'cisco',
|
|
240
|
+
'secret',
|
|
241
|
+
'test',
|
|
242
|
+
'default',
|
|
243
|
+
];
|
|
244
|
+
return defaultCommunities.some((dc) => equalsIgnoreCase(community, dc));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if SNMP v3 is configured
|
|
249
|
+
*/
|
|
250
|
+
export const isSnmpV3User = (node: ConfigNode): boolean => {
|
|
251
|
+
return startsWithIgnoreCase(node.id, 'snmp-server user');
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if SNMP v3 uses auth-priv
|
|
256
|
+
*/
|
|
257
|
+
export const hasSnmpV3AuthPriv = (node: ConfigNode): boolean => {
|
|
258
|
+
return includesIgnoreCase(node.rawText, 'auth') && includesIgnoreCase(node.rawText, 'priv');
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get VTY line range from node
|
|
263
|
+
*/
|
|
264
|
+
export const getVtyLineRange = (node: ConfigNode): { start: number; end: number } | null => {
|
|
265
|
+
const params = node.params;
|
|
266
|
+
// line vty 0 15
|
|
267
|
+
if (params.length >= 4) {
|
|
268
|
+
const startStr = params[2];
|
|
269
|
+
const endStr = params[3];
|
|
270
|
+
const start = startStr ? parseInteger(startStr) : null;
|
|
271
|
+
const end = endStr ? parseInteger(endStr) : null;
|
|
272
|
+
if (start !== null && end !== null) {
|
|
273
|
+
return { start, end };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// line vty 0
|
|
277
|
+
if (params.length >= 3) {
|
|
278
|
+
const startStr = params[2];
|
|
279
|
+
const start = startStr ? parseInteger(startStr) : null;
|
|
280
|
+
if (start !== null) {
|
|
281
|
+
return { start, end: start };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if VTY has access-class configured
|
|
289
|
+
*/
|
|
290
|
+
export const hasVtyAccessClass = (node: ConfigNode): boolean => {
|
|
291
|
+
return hasChildCommand(node, 'access-class');
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if NTP authentication is enabled
|
|
296
|
+
*/
|
|
297
|
+
export const hasNtpAuthentication = (node: ConfigNode): boolean => {
|
|
298
|
+
return includesIgnoreCase(node.rawText, 'ntp authenticate') || includesIgnoreCase(node.rawText, 'ntp authentication-key');
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Control Plane Helpers
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if OSPF authentication is configured on interface
|
|
307
|
+
*/
|
|
308
|
+
export const hasOspfAuthentication = (node: ConfigNode): boolean => {
|
|
309
|
+
return hasChildCommand(node, 'ip ospf authentication') ||
|
|
310
|
+
hasChildCommand(node, 'ip ospf message-digest-key');
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check if EIGRP authentication is configured on interface
|
|
315
|
+
*/
|
|
316
|
+
export const hasEigrpAuthentication = (node: ConfigNode): boolean => {
|
|
317
|
+
return hasChildCommand(node, 'ip authentication mode eigrp') &&
|
|
318
|
+
hasChildCommand(node, 'ip authentication key-chain eigrp');
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Check if BGP neighbor has password configured
|
|
323
|
+
*/
|
|
324
|
+
export const hasBgpNeighborPassword = (neighborCommands: ConfigNode[]): boolean => {
|
|
325
|
+
return neighborCommands.some((cmd) =>
|
|
326
|
+
includesIgnoreCase(cmd.id, 'password')
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Check if BGP neighbor has TTL security (GTSM) configured
|
|
332
|
+
*/
|
|
333
|
+
export const hasBgpTtlSecurity = (neighborCommands: ConfigNode[]): boolean => {
|
|
334
|
+
return neighborCommands.some((cmd) =>
|
|
335
|
+
includesIgnoreCase(cmd.id, 'ttl-security')
|
|
336
|
+
);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Check if BGP neighbor has maximum-prefix configured
|
|
341
|
+
*/
|
|
342
|
+
export const hasBgpMaxPrefix = (neighborCommands: ConfigNode[]): boolean => {
|
|
343
|
+
return neighborCommands.some((cmd) =>
|
|
344
|
+
includesIgnoreCase(cmd.id, 'maximum-prefix')
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Check if BGP has log-neighbor-changes enabled
|
|
350
|
+
*/
|
|
351
|
+
export const hasBgpLogNeighborChanges = (node: ConfigNode): boolean => {
|
|
352
|
+
return hasChildCommand(node, 'bgp log-neighbor-changes');
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Get all BGP neighbors from router bgp section
|
|
357
|
+
*/
|
|
358
|
+
export const getBgpNeighbors = (node: ConfigNode): Map<string, ConfigNode[]> => {
|
|
359
|
+
const neighbors = new Map<string, ConfigNode[]>();
|
|
360
|
+
|
|
361
|
+
for (const child of node.children) {
|
|
362
|
+
if (startsWithIgnoreCase(child.id, 'neighbor')) {
|
|
363
|
+
const neighborIp = child.params[1];
|
|
364
|
+
if (neighborIp) {
|
|
365
|
+
const existing = neighbors.get(neighborIp) || [];
|
|
366
|
+
existing.push(child);
|
|
367
|
+
neighbors.set(neighborIp, existing);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return neighbors;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Check if HSRP has MD5 authentication
|
|
377
|
+
*/
|
|
378
|
+
export const hasHsrpMd5Auth = (node: ConfigNode): boolean => {
|
|
379
|
+
return node.children.some((child) => {
|
|
380
|
+
return startsWithIgnoreCase(child.id, 'standby') && includesIgnoreCase(child.id, 'authentication md5');
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check if VRRP has authentication
|
|
386
|
+
*/
|
|
387
|
+
export const hasVrrpAuthentication = (node: ConfigNode): boolean => {
|
|
388
|
+
return node.children.some((child) => {
|
|
389
|
+
return startsWithIgnoreCase(child.id, 'vrrp') && includesIgnoreCase(child.id, 'authentication');
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// ============================================================================
|
|
394
|
+
// Data Plane Helpers
|
|
395
|
+
// ============================================================================
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check if interface has uRPF (unicast RPF) enabled
|
|
399
|
+
*/
|
|
400
|
+
export const hasUrpf = (node: ConfigNode): boolean => {
|
|
401
|
+
return hasChildCommand(node, 'ip verify unicast source reachable-via');
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get uRPF mode (rx = strict, any = loose)
|
|
406
|
+
*/
|
|
407
|
+
export const getUrpfMode = (node: ConfigNode): 'strict' | 'loose' | null => {
|
|
408
|
+
const cmd = getChildCommand(node, 'ip verify unicast source reachable-via');
|
|
409
|
+
if (cmd) {
|
|
410
|
+
if (includesIgnoreCase(cmd.rawText, 'reachable-via rx')) {
|
|
411
|
+
return 'strict';
|
|
412
|
+
}
|
|
413
|
+
if (includesIgnoreCase(cmd.rawText, 'reachable-via any')) {
|
|
414
|
+
return 'loose';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Check if IP redirects are disabled
|
|
422
|
+
*/
|
|
423
|
+
export const hasNoIpRedirects = (node: ConfigNode): boolean => {
|
|
424
|
+
return hasChildCommand(node, 'no ip redirects');
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Check if IP unreachables are disabled
|
|
429
|
+
*/
|
|
430
|
+
export const hasNoIpUnreachables = (node: ConfigNode): boolean => {
|
|
431
|
+
return hasChildCommand(node, 'no ip unreachables');
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Check if IP proxy-arp is disabled
|
|
436
|
+
*/
|
|
437
|
+
export const hasNoProxyArp = (node: ConfigNode): boolean => {
|
|
438
|
+
return hasChildCommand(node, 'no ip proxy-arp');
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Check if IP directed-broadcast is disabled
|
|
443
|
+
*/
|
|
444
|
+
export const hasNoDirectedBroadcast = (node: ConfigNode): boolean => {
|
|
445
|
+
return hasChildCommand(node, 'no ip directed-broadcast');
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Check if interface is a WAN/Internet-facing interface
|
|
450
|
+
*/
|
|
451
|
+
export const isWanInterface = (node: ConfigNode): boolean => {
|
|
452
|
+
const desc = getChildCommand(node, 'description');
|
|
453
|
+
if (desc) {
|
|
454
|
+
const descText = desc.rawText;
|
|
455
|
+
return (
|
|
456
|
+
includesIgnoreCase(descText, 'wan') ||
|
|
457
|
+
includesIgnoreCase(descText, 'internet') ||
|
|
458
|
+
includesIgnoreCase(descText, 'isp') ||
|
|
459
|
+
includesIgnoreCase(descText, 'external') ||
|
|
460
|
+
includesIgnoreCase(descText, 'outside') ||
|
|
461
|
+
includesIgnoreCase(descText, 'border') ||
|
|
462
|
+
includesIgnoreCase(descText, 'edge')
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Check if interface is a loopback
|
|
470
|
+
*/
|
|
471
|
+
export const isLoopbackInterface = (interfaceName: string): boolean => {
|
|
472
|
+
return includesIgnoreCase(interfaceName, 'loopback');
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Check if interface is a tunnel
|
|
477
|
+
*/
|
|
478
|
+
export const isTunnelInterface = (interfaceName: string): boolean => {
|
|
479
|
+
return includesIgnoreCase(interfaceName, 'tunnel');
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Check if interface is a VLAN SVI
|
|
484
|
+
*/
|
|
485
|
+
export const isVlanInterface = (interfaceName: string): boolean => {
|
|
486
|
+
return startsWithIgnoreCase(interfaceName, 'interface vlan');
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// Service Hardening Helpers
|
|
491
|
+
// ============================================================================
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Check if service password-encryption is enabled
|
|
495
|
+
*/
|
|
496
|
+
export const hasPasswordEncryption = (node: ConfigNode): boolean => {
|
|
497
|
+
return equalsIgnoreCase(node.id.trim(), 'service password-encryption');
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check if TCP keepalives are enabled
|
|
502
|
+
*/
|
|
503
|
+
export const hasTcpKeepalives = (node: ConfigNode): boolean => {
|
|
504
|
+
const cmd = node.id.trim();
|
|
505
|
+
return equalsIgnoreCase(cmd, 'service tcp-keepalives-in') || equalsIgnoreCase(cmd, 'service tcp-keepalives-out');
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Check if service is a dangerous/unnecessary service that should be disabled
|
|
510
|
+
*/
|
|
511
|
+
export const isDangerousService = (node: ConfigNode): boolean => {
|
|
512
|
+
const cmd = node.id.trim();
|
|
513
|
+
const dangerousServices = [
|
|
514
|
+
'service tcp-small-servers',
|
|
515
|
+
'service udp-small-servers',
|
|
516
|
+
'ip finger',
|
|
517
|
+
'service finger',
|
|
518
|
+
'ip bootp server',
|
|
519
|
+
'service config',
|
|
520
|
+
'ip http server',
|
|
521
|
+
'service pad',
|
|
522
|
+
'boot network',
|
|
523
|
+
'service call-home',
|
|
524
|
+
];
|
|
525
|
+
return dangerousServices.some((svc) => equalsIgnoreCase(cmd, svc));
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Check if Smart Install (vstack) is enabled
|
|
530
|
+
*/
|
|
531
|
+
export const isSmartInstallEnabled = (node: ConfigNode): boolean => {
|
|
532
|
+
// "vstack" without "no" means it's enabled
|
|
533
|
+
return equalsIgnoreCase(node.id.trim(), 'vstack');
|
|
534
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// packages/rule-helpers/src/cisco/index.ts
|
|
2
|
+
// Re-export all Cisco helpers
|
|
3
|
+
|
|
4
|
+
export * from './helpers';
|
|
5
|
+
|
|
6
|
+
// Also re-export commonly used common helpers for convenience
|
|
7
|
+
export {
|
|
8
|
+
hasChildCommand,
|
|
9
|
+
getChildCommand,
|
|
10
|
+
getChildCommands,
|
|
11
|
+
} from '../common/helpers';
|