@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,12 @@
|
|
|
1
|
+
// packages/rule-helpers/src/arista/index.ts
|
|
2
|
+
// Re-export all Arista EOS 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
|
+
parseIp,
|
|
12
|
+
} from '../common/helpers';
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
// packages/rule-helpers/src/aruba/helpers.ts
|
|
2
|
+
// Aruba-specific helper functions used across Aruba rules
|
|
3
|
+
|
|
4
|
+
import type { ConfigNode } from '../../types/ConfigNode';
|
|
5
|
+
import { hasChildCommand, getChildCommand, getChildCommands } from '../common/helpers';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract the interface name from an interface stanza id.
|
|
9
|
+
* @param node The interface ConfigNode
|
|
10
|
+
* @returns The interface identifier without the leading keyword
|
|
11
|
+
*/
|
|
12
|
+
export const getInterfaceName = (node: ConfigNode): string | undefined => {
|
|
13
|
+
const match = node.id.match(/interface\s+(.+)/i);
|
|
14
|
+
const ifName = match?.[1]?.trim();
|
|
15
|
+
return ifName && ifName.length > 0 ? ifName : undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// AOS-CX Helpers
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if an AOS-CX interface is a physical port (slot/member/port format).
|
|
24
|
+
* @param interfaceName The interface identifier
|
|
25
|
+
* @returns true if it's a physical port (e.g., 1/1/1)
|
|
26
|
+
*/
|
|
27
|
+
export const isAosCxPhysicalPort = (interfaceName: string): boolean => {
|
|
28
|
+
return /^\d+\/\d+\/\d+$/.test(interfaceName.trim());
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if an AOS-CX interface is a LAG.
|
|
33
|
+
* @param interfaceName The interface identifier
|
|
34
|
+
* @returns true if it's a LAG interface
|
|
35
|
+
*/
|
|
36
|
+
export const isAosCxLag = (interfaceName: string): boolean => {
|
|
37
|
+
return /^lag\s*\d+$/i.test(interfaceName.trim());
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if an AOS-CX interface is a VLAN interface.
|
|
42
|
+
* @param interfaceName The interface identifier
|
|
43
|
+
* @returns true if it's a VLAN interface
|
|
44
|
+
*/
|
|
45
|
+
export const isAosCxVlanInterface = (interfaceName: string): boolean => {
|
|
46
|
+
return /^vlan\s*\d+$/i.test(interfaceName.trim());
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if an AOS-CX interface is configured as trunk mode.
|
|
51
|
+
* @param node The interface ConfigNode
|
|
52
|
+
* @returns true if the interface has trunk VLAN configuration
|
|
53
|
+
*/
|
|
54
|
+
export const isAosCxTrunk = (node: ConfigNode): boolean => {
|
|
55
|
+
return hasChildCommand(node, 'vlan trunk');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if an AOS-CX interface is configured as access mode.
|
|
60
|
+
* @param node The interface ConfigNode
|
|
61
|
+
* @returns true if the interface has access VLAN configuration
|
|
62
|
+
*/
|
|
63
|
+
export const isAosCxAccess = (node: ConfigNode): boolean => {
|
|
64
|
+
return hasChildCommand(node, 'vlan access');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the access VLAN ID from an AOS-CX interface.
|
|
69
|
+
* @param node The interface ConfigNode
|
|
70
|
+
* @returns The VLAN ID, or null if not configured
|
|
71
|
+
*/
|
|
72
|
+
export const getAosCxVlanAccess = (node: ConfigNode): number | null => {
|
|
73
|
+
const cmd = getChildCommand(node, 'vlan access');
|
|
74
|
+
if (!cmd) return null;
|
|
75
|
+
const match = cmd.id.match(/vlan\s+access\s+(\d+)/i);
|
|
76
|
+
const vlanId = match?.[1];
|
|
77
|
+
if (!vlanId) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return parseInt(vlanId, 10);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the native VLAN ID from an AOS-CX trunk interface.
|
|
85
|
+
* @param node The interface ConfigNode
|
|
86
|
+
* @returns The native VLAN ID, or null if not configured
|
|
87
|
+
*/
|
|
88
|
+
export const getAosCxTrunkNative = (node: ConfigNode): number | null => {
|
|
89
|
+
const cmd = getChildCommand(node, 'vlan trunk native');
|
|
90
|
+
if (!cmd) return null;
|
|
91
|
+
const match = cmd.id.match(/vlan\s+trunk\s+native\s+(\d+)/i);
|
|
92
|
+
const vlanId = match?.[1];
|
|
93
|
+
if (!vlanId) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return parseInt(vlanId, 10);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get allowed VLANs from an AOS-CX trunk interface.
|
|
101
|
+
* @param node The interface ConfigNode
|
|
102
|
+
* @returns Array of allowed VLAN IDs, or empty array if not configured
|
|
103
|
+
*/
|
|
104
|
+
export const getAosCxTrunkAllowed = (node: ConfigNode): number[] => {
|
|
105
|
+
const cmd = getChildCommand(node, 'vlan trunk allowed');
|
|
106
|
+
if (!cmd) return [];
|
|
107
|
+
const match = cmd.id.match(/vlan\s+trunk\s+allowed\s+([\d,]+)/i);
|
|
108
|
+
const vlanList = match?.[1];
|
|
109
|
+
if (!vlanList) return [];
|
|
110
|
+
return vlanList
|
|
111
|
+
.split(',')
|
|
112
|
+
.map((v) => parseInt(v.trim(), 10))
|
|
113
|
+
.filter((v) => !isNaN(v));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if an AOS-CX interface has BPDU guard enabled.
|
|
118
|
+
* @param node The interface ConfigNode
|
|
119
|
+
* @returns true if BPDU guard is configured
|
|
120
|
+
*/
|
|
121
|
+
export const hasAosCxBpduGuard = (node: ConfigNode): boolean => {
|
|
122
|
+
return hasChildCommand(node, 'spanning-tree bpdu-guard');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if an AOS-CX interface is an admin-edge port.
|
|
127
|
+
* @param node The interface ConfigNode
|
|
128
|
+
* @returns true if admin-edge is configured
|
|
129
|
+
*/
|
|
130
|
+
export const isAosCxEdgePort = (node: ConfigNode): boolean => {
|
|
131
|
+
return hasChildCommand(node, 'spanning-tree port-type admin-edge');
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if an AOS-CX interface has root-guard enabled.
|
|
136
|
+
* @param node The interface ConfigNode
|
|
137
|
+
* @returns true if root-guard is configured
|
|
138
|
+
*/
|
|
139
|
+
export const hasAosCxRootGuard = (node: ConfigNode): boolean => {
|
|
140
|
+
return hasChildCommand(node, 'spanning-tree root-guard');
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if an AOS-CX interface has loop-protect enabled.
|
|
145
|
+
* @param node The interface ConfigNode
|
|
146
|
+
* @returns true if loop-protect is configured
|
|
147
|
+
*/
|
|
148
|
+
export const hasAosCxLoopProtect = (node: ConfigNode): boolean => {
|
|
149
|
+
return hasChildCommand(node, 'loop-protect');
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if an AOS-CX interface has storm-control configured.
|
|
154
|
+
* @param node The interface ConfigNode
|
|
155
|
+
* @returns true if any storm-control setting is configured
|
|
156
|
+
*/
|
|
157
|
+
export const hasAosCxStormControl = (node: ConfigNode): boolean => {
|
|
158
|
+
return hasChildCommand(node, 'storm-control');
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if an AOS-CX interface has DHCP snooping trust configured.
|
|
163
|
+
* @param node The interface ConfigNode
|
|
164
|
+
* @returns true if dhcp-snooping trust is configured
|
|
165
|
+
*/
|
|
166
|
+
export const hasAosCxDhcpSnooping = (node: ConfigNode): boolean => {
|
|
167
|
+
return hasChildCommand(node, 'dhcp-snooping');
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if an AOS-CX interface has ARP inspection trust configured.
|
|
172
|
+
* @param node The interface ConfigNode
|
|
173
|
+
* @returns true if ip arp inspection trust is configured
|
|
174
|
+
*/
|
|
175
|
+
export const hasAosCxArpInspection = (node: ConfigNode): boolean => {
|
|
176
|
+
return hasChildCommand(node, 'ip arp inspection');
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check if an AOS-CX interface has IP source guard (source-binding) configured.
|
|
181
|
+
* @param node The interface ConfigNode
|
|
182
|
+
* @returns true if ip source-binding is configured
|
|
183
|
+
*/
|
|
184
|
+
export const hasAosCxIpSourceGuard = (node: ConfigNode): boolean => {
|
|
185
|
+
return hasChildCommand(node, 'ip source-binding');
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if an AOS-CX interface has port security configured.
|
|
190
|
+
* @param node The interface ConfigNode
|
|
191
|
+
* @returns true if port-access port-security is configured
|
|
192
|
+
*/
|
|
193
|
+
export const hasAosCxPortSecurity = (node: ConfigNode): boolean => {
|
|
194
|
+
return hasChildCommand(node, 'port-access port-security');
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if an AOS-CX interface has 802.1X authenticator configured.
|
|
199
|
+
* @param node The interface ConfigNode
|
|
200
|
+
* @returns true if dot1x authenticator is configured
|
|
201
|
+
*/
|
|
202
|
+
export const hasAosCxDot1x = (node: ConfigNode): boolean => {
|
|
203
|
+
return hasChildCommand(node, 'aaa authentication port-access dot1x');
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if an AOS-CX interface has MAC authentication configured.
|
|
208
|
+
* @param node The interface ConfigNode
|
|
209
|
+
* @returns true if mac-auth is configured
|
|
210
|
+
*/
|
|
211
|
+
export const hasAosCxMacAuth = (node: ConfigNode): boolean => {
|
|
212
|
+
return hasChildCommand(node, 'aaa authentication port-access mac-auth');
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get MSTP region name from global config.
|
|
217
|
+
* @param node The spanning-tree config-name node
|
|
218
|
+
* @returns The region name, or undefined
|
|
219
|
+
*/
|
|
220
|
+
export const getAosCxMstpRegionName = (node: ConfigNode): string | undefined => {
|
|
221
|
+
const match = node.id.match(/spanning-tree\s+config-name\s+(\S+)/i);
|
|
222
|
+
return match?.[1];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check if an AOS-CX interface has MACsec configured.
|
|
227
|
+
* @param node The interface ConfigNode
|
|
228
|
+
* @returns true if MACsec policy is applied
|
|
229
|
+
*/
|
|
230
|
+
export const hasAosCxMacsec = (node: ConfigNode): boolean => {
|
|
231
|
+
return hasChildCommand(node, 'apply macsec policy') || hasChildCommand(node, 'apply mka policy');
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// AOS-Switch Helpers
|
|
236
|
+
// =============================================================================
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parse port range string to array of port numbers.
|
|
240
|
+
* Handles formats like "1-24", "25,26,27", "1-24,48"
|
|
241
|
+
* @param portStr The port range string
|
|
242
|
+
* @returns Array of individual port numbers
|
|
243
|
+
*/
|
|
244
|
+
export const parsePortRange = (portStr: string): number[] => {
|
|
245
|
+
const ports: number[] = [];
|
|
246
|
+
const parts = portStr.split(',');
|
|
247
|
+
|
|
248
|
+
for (const part of parts) {
|
|
249
|
+
const trimmed = part.trim();
|
|
250
|
+
if (trimmed.includes('-')) {
|
|
251
|
+
const [startRaw, endRaw] = trimmed
|
|
252
|
+
.split('-')
|
|
253
|
+
.map((n) => parseInt(n.trim(), 10));
|
|
254
|
+
if (
|
|
255
|
+
startRaw === undefined ||
|
|
256
|
+
endRaw === undefined ||
|
|
257
|
+
isNaN(startRaw) ||
|
|
258
|
+
isNaN(endRaw)
|
|
259
|
+
) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
for (let i = startRaw; i <= endRaw; i++) {
|
|
263
|
+
ports.push(i);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
const num = parseInt(trimmed, 10);
|
|
267
|
+
if (!isNaN(num)) {
|
|
268
|
+
ports.push(num);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return ports;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get tagged ports from an AOS-Switch VLAN node.
|
|
278
|
+
* @param node The VLAN ConfigNode
|
|
279
|
+
* @returns Array of tagged port numbers
|
|
280
|
+
*/
|
|
281
|
+
export const getVlanTaggedPorts = (node: ConfigNode): (number | string)[] => {
|
|
282
|
+
const cmd = getChildCommand(node, 'tagged');
|
|
283
|
+
if (!cmd) return [];
|
|
284
|
+
const match = cmd.id.match(/tagged\s+(.*)/i);
|
|
285
|
+
const taggedList = match?.[1];
|
|
286
|
+
if (!taggedList) return [];
|
|
287
|
+
|
|
288
|
+
const result: (number | string)[] = [];
|
|
289
|
+
const parts = taggedList.split(',');
|
|
290
|
+
|
|
291
|
+
for (const part of parts) {
|
|
292
|
+
const trimmed = part.trim();
|
|
293
|
+
if (/^trk\d+$/i.test(trimmed)) {
|
|
294
|
+
result.push(trimmed.toLowerCase());
|
|
295
|
+
} else if (trimmed.includes('-')) {
|
|
296
|
+
result.push(...parsePortRange(trimmed));
|
|
297
|
+
} else {
|
|
298
|
+
const num = parseInt(trimmed, 10);
|
|
299
|
+
if (!isNaN(num)) {
|
|
300
|
+
result.push(num);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return result;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get untagged ports from an AOS-Switch VLAN node.
|
|
310
|
+
* @param node The VLAN ConfigNode
|
|
311
|
+
* @returns Array of untagged port numbers
|
|
312
|
+
*/
|
|
313
|
+
export const getVlanUntaggedPorts = (node: ConfigNode): (number | string)[] => {
|
|
314
|
+
const cmd = getChildCommand(node, 'untagged');
|
|
315
|
+
if (!cmd) return [];
|
|
316
|
+
const match = cmd.id.match(/untagged\s+(.*)/i);
|
|
317
|
+
const untaggedList = match?.[1];
|
|
318
|
+
if (!untaggedList) return [];
|
|
319
|
+
|
|
320
|
+
const result: (number | string)[] = [];
|
|
321
|
+
const parts = untaggedList.split(',');
|
|
322
|
+
|
|
323
|
+
for (const part of parts) {
|
|
324
|
+
const trimmed = part.trim();
|
|
325
|
+
if (/^trk\d+$/i.test(trimmed)) {
|
|
326
|
+
result.push(trimmed.toLowerCase());
|
|
327
|
+
} else if (trimmed.includes('-')) {
|
|
328
|
+
result.push(...parsePortRange(trimmed));
|
|
329
|
+
} else {
|
|
330
|
+
const num = parseInt(trimmed, 10);
|
|
331
|
+
if (!isNaN(num)) {
|
|
332
|
+
result.push(num);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return result;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get the VLAN name from an AOS-Switch VLAN node.
|
|
342
|
+
* @param node The VLAN ConfigNode
|
|
343
|
+
* @returns The VLAN name, or undefined if not set
|
|
344
|
+
*/
|
|
345
|
+
export const getAosSwitchVlanName = (node: ConfigNode): string | undefined => {
|
|
346
|
+
const cmd = getChildCommand(node, 'name');
|
|
347
|
+
if (!cmd) return undefined;
|
|
348
|
+
const match = cmd.id.match(/name\s+["']?([^"']+)["']?/i);
|
|
349
|
+
const name = match?.[1];
|
|
350
|
+
return name?.trim();
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Check if AOS-Switch has manager password configured.
|
|
355
|
+
* @param nodes Array of top-level ConfigNodes (AST children)
|
|
356
|
+
* @returns true if manager password is configured
|
|
357
|
+
*/
|
|
358
|
+
export const hasManagerPassword = (nodes: ConfigNode[]): boolean => {
|
|
359
|
+
return nodes.some((n) => n.id.toLowerCase().startsWith('password manager'));
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Check if AOS-Switch has operator password configured.
|
|
364
|
+
* @param nodes Array of top-level ConfigNodes (AST children)
|
|
365
|
+
* @returns true if operator password is configured
|
|
366
|
+
*/
|
|
367
|
+
export const hasOperatorPassword = (nodes: ConfigNode[]): boolean => {
|
|
368
|
+
return nodes.some((n) => n.id.toLowerCase().startsWith('password operator'));
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// =============================================================================
|
|
372
|
+
// WLC Helpers
|
|
373
|
+
// =============================================================================
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get the WLAN encryption mode from an SSID profile.
|
|
377
|
+
* @param node The SSID profile ConfigNode
|
|
378
|
+
* @returns The opmode value (e.g., 'wpa3-sae-aes', 'wpa2-aes', 'opensystem'), or null
|
|
379
|
+
*/
|
|
380
|
+
export const getWlanEncryption = (node: ConfigNode): string | null => {
|
|
381
|
+
const cmd = getChildCommand(node, 'opmode');
|
|
382
|
+
if (!cmd) return null;
|
|
383
|
+
const match = cmd.id.match(/opmode\s+(\S+)/i);
|
|
384
|
+
const mode = match?.[1];
|
|
385
|
+
return mode ? mode.toLowerCase() : null;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Check if a WLAN SSID profile has secure encryption (WPA2/WPA3).
|
|
390
|
+
* @param node The SSID profile ConfigNode
|
|
391
|
+
* @returns true if encryption is WPA2 or WPA3
|
|
392
|
+
*/
|
|
393
|
+
export const hasSecureEncryption = (node: ConfigNode): boolean => {
|
|
394
|
+
const opmode = getWlanEncryption(node);
|
|
395
|
+
if (!opmode) return false;
|
|
396
|
+
return opmode.includes('wpa2') || opmode.includes('wpa3') || opmode.includes('aes');
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Check if a WLAN SSID profile is open (no encryption).
|
|
401
|
+
* @param node The SSID profile ConfigNode
|
|
402
|
+
* @returns true if the SSID is open/unencrypted
|
|
403
|
+
*/
|
|
404
|
+
export const isOpenSsid = (node: ConfigNode): boolean => {
|
|
405
|
+
const opmode = getWlanEncryption(node);
|
|
406
|
+
return opmode === 'opensystem' || opmode === 'open';
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get the ESSID from a WLAN SSID profile.
|
|
411
|
+
* @param node The SSID profile ConfigNode
|
|
412
|
+
* @returns The ESSID value, or undefined
|
|
413
|
+
*/
|
|
414
|
+
export const getEssid = (node: ConfigNode): string | undefined => {
|
|
415
|
+
const cmd = getChildCommand(node, 'essid');
|
|
416
|
+
if (!cmd) return undefined;
|
|
417
|
+
const match = cmd.id.match(/essid\s+["']?([^"'\n]+)["']?/i);
|
|
418
|
+
const essid = match?.[1];
|
|
419
|
+
return essid?.trim();
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Get the AAA profile reference from a virtual-AP profile.
|
|
424
|
+
* @param node The virtual-AP ConfigNode
|
|
425
|
+
* @returns The AAA profile name, or undefined
|
|
426
|
+
*/
|
|
427
|
+
export const getVapAaaProfile = (node: ConfigNode): string | undefined => {
|
|
428
|
+
const cmd = getChildCommand(node, 'aaa-profile');
|
|
429
|
+
if (!cmd) return undefined;
|
|
430
|
+
const match = cmd.id.match(/aaa-profile\s+["']?([^"'\n]+)["']?/i);
|
|
431
|
+
const profile = match?.[1];
|
|
432
|
+
return profile?.trim();
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get the SSID profile reference from a virtual-AP profile.
|
|
437
|
+
* @param node The virtual-AP ConfigNode
|
|
438
|
+
* @returns The SSID profile name, or undefined
|
|
439
|
+
*/
|
|
440
|
+
export const getVapSsidProfile = (node: ConfigNode): string | undefined => {
|
|
441
|
+
const cmd = getChildCommand(node, 'ssid-profile');
|
|
442
|
+
if (!cmd) return undefined;
|
|
443
|
+
const match = cmd.id.match(/ssid-profile\s+["']?([^"'\n]+)["']?/i);
|
|
444
|
+
const profile = match?.[1];
|
|
445
|
+
return profile?.trim();
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get virtual-APs from an AP group.
|
|
450
|
+
* @param node The AP group ConfigNode
|
|
451
|
+
* @returns Array of virtual-AP names
|
|
452
|
+
*/
|
|
453
|
+
export const getApGroupVirtualAps = (node: ConfigNode): string[] => {
|
|
454
|
+
const vaps: string[] = [];
|
|
455
|
+
for (const child of node.children) {
|
|
456
|
+
const match = child.id.match(/virtual-ap\s+["']?([^"'\n]+)["']?/i);
|
|
457
|
+
const vapName = match?.[1];
|
|
458
|
+
if (vapName) {
|
|
459
|
+
vaps.push(vapName);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return vaps;
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Check if RADIUS server has a key configured.
|
|
467
|
+
* @param node The RADIUS server ConfigNode
|
|
468
|
+
* @returns true if a key is configured
|
|
469
|
+
*/
|
|
470
|
+
export const hasRadiusKey = (node: ConfigNode): boolean => {
|
|
471
|
+
return hasChildCommand(node, 'key');
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get the RADIUS server host address.
|
|
476
|
+
* @param node The RADIUS server ConfigNode
|
|
477
|
+
* @returns The host IP/hostname, or undefined
|
|
478
|
+
*/
|
|
479
|
+
export const getRadiusHost = (node: ConfigNode): string | undefined => {
|
|
480
|
+
const cmd = getChildCommand(node, 'host');
|
|
481
|
+
if (!cmd) return undefined;
|
|
482
|
+
const match = cmd.id.match(/host\s+(\S+)/i);
|
|
483
|
+
const host = match?.[1];
|
|
484
|
+
return host?.trim();
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Extract profile name from a profile definition node.
|
|
489
|
+
* Handles both quoted and unquoted names.
|
|
490
|
+
* @param nodeId The node identifier string
|
|
491
|
+
* @returns The profile name, or undefined
|
|
492
|
+
*/
|
|
493
|
+
export const extractProfileName = (nodeId: string): string | undefined => {
|
|
494
|
+
// Match patterns like: wlan ssid-profile "Name" or aaa profile "Name"
|
|
495
|
+
const match = nodeId.match(/(?:ssid-profile|virtual-ap|profile|server-group|ap-group|arm-profile)\s+["']?([^"'\n]+)["']?$/i);
|
|
496
|
+
const profile = match?.[1];
|
|
497
|
+
return profile ? profile.trim() : undefined;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check if a WLAN SSID profile uses WPA3.
|
|
502
|
+
* @param node The SSID profile ConfigNode
|
|
503
|
+
* @returns true if encryption is WPA3
|
|
504
|
+
*/
|
|
505
|
+
export const hasWpa3Encryption = (node: ConfigNode): boolean => {
|
|
506
|
+
const opmode = getWlanEncryption(node);
|
|
507
|
+
if (!opmode) return false;
|
|
508
|
+
return opmode.includes('wpa3');
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Check if a WLAN SSID profile uses WPA3-Enterprise.
|
|
513
|
+
* @param node The SSID profile ConfigNode
|
|
514
|
+
* @returns true if encryption is WPA3-Enterprise
|
|
515
|
+
*/
|
|
516
|
+
export const hasWpa3Enterprise = (node: ConfigNode): boolean => {
|
|
517
|
+
const opmode = getWlanEncryption(node);
|
|
518
|
+
if (!opmode) return false;
|
|
519
|
+
return opmode.includes('wpa3') && !opmode.includes('sae');
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Check if a WLAN SSID profile uses WPA3-SAE (Personal).
|
|
524
|
+
* @param node The SSID profile ConfigNode
|
|
525
|
+
* @returns true if encryption is WPA3-SAE
|
|
526
|
+
*/
|
|
527
|
+
export const hasWpa3Sae = (node: ConfigNode): boolean => {
|
|
528
|
+
const opmode = getWlanEncryption(node);
|
|
529
|
+
if (!opmode) return false;
|
|
530
|
+
return opmode.includes('wpa3') && opmode.includes('sae');
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if Protected Management Frames (PMF/802.11w) is enabled.
|
|
535
|
+
* @param node The SSID profile ConfigNode
|
|
536
|
+
* @returns 'required' | 'optional' | null
|
|
537
|
+
*/
|
|
538
|
+
export const getPmfMode = (node: ConfigNode): 'required' | 'optional' | null => {
|
|
539
|
+
const cmd = getChildCommand(node, 'mgmt-frame-protection');
|
|
540
|
+
if (!cmd) return null;
|
|
541
|
+
const id = cmd.id.toLowerCase();
|
|
542
|
+
if (id.includes('required')) return 'required';
|
|
543
|
+
if (id.includes('optional')) return 'optional';
|
|
544
|
+
return null;
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Check if SSID profile is configured for 6 GHz band.
|
|
549
|
+
* @param node The SSID profile ConfigNode
|
|
550
|
+
* @returns true if 6ghz band is configured
|
|
551
|
+
*/
|
|
552
|
+
export const is6GhzSsid = (node: ConfigNode): boolean => {
|
|
553
|
+
const cmd = getChildCommand(node, 'band');
|
|
554
|
+
if (!cmd) return false;
|
|
555
|
+
return cmd.id.toLowerCase().includes('6ghz');
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Get max clients limit from SSID profile.
|
|
560
|
+
* @param node The SSID profile ConfigNode
|
|
561
|
+
* @returns The max clients value, or null
|
|
562
|
+
*/
|
|
563
|
+
export const getMaxClients = (node: ConfigNode): number | null => {
|
|
564
|
+
const cmd = getChildCommand(node, 'max-clients');
|
|
565
|
+
if (!cmd) return null;
|
|
566
|
+
const match = cmd.id.match(/max-clients\s+(\d+)/i);
|
|
567
|
+
return match?.[1] ? parseInt(match[1], 10) : null;
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Check if CPsec (Control Plane Security) is enabled on WLC.
|
|
572
|
+
* @param node The control-plane-security ConfigNode
|
|
573
|
+
* @returns true if cpsec is enabled
|
|
574
|
+
*/
|
|
575
|
+
export const hasCpsecEnabled = (node: ConfigNode): boolean => {
|
|
576
|
+
return !node.id.toLowerCase().includes('disable');
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Check if whitelist-db is enabled for AP authorization.
|
|
581
|
+
* @param node The cpsec ConfigNode
|
|
582
|
+
* @returns true if whitelist-db is enabled
|
|
583
|
+
*/
|
|
584
|
+
export const hasWhitelistDb = (node: ConfigNode): boolean => {
|
|
585
|
+
return hasChildCommand(node, 'whitelist-db enable');
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// =============================================================================
|
|
589
|
+
// Common Aruba Helpers
|
|
590
|
+
// =============================================================================
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Find a child stanza by exact name match.
|
|
594
|
+
* @param node The parent ConfigNode
|
|
595
|
+
* @param stanzaName The stanza name to find
|
|
596
|
+
* @returns The matching child node, or undefined
|
|
597
|
+
*/
|
|
598
|
+
export const findStanza = (
|
|
599
|
+
node: ConfigNode,
|
|
600
|
+
stanzaName: string
|
|
601
|
+
): ConfigNode | undefined => {
|
|
602
|
+
return node.children.find(
|
|
603
|
+
(child) => child.id.toLowerCase() === stanzaName.toLowerCase()
|
|
604
|
+
);
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Find all stanzas matching a pattern within a node's children.
|
|
609
|
+
* @param node The parent ConfigNode
|
|
610
|
+
* @param pattern The regex pattern to match
|
|
611
|
+
* @returns Array of matching child nodes
|
|
612
|
+
*/
|
|
613
|
+
export const findStanzas = (node: ConfigNode, pattern: RegExp): ConfigNode[] => {
|
|
614
|
+
return node.children.filter((child) => pattern.test(child.id.toLowerCase()));
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Check if an interface/node has a description configured.
|
|
619
|
+
* @param node The ConfigNode
|
|
620
|
+
* @returns true if a description command exists
|
|
621
|
+
*/
|
|
622
|
+
export const hasDescription = (node: ConfigNode): boolean => {
|
|
623
|
+
return hasChildCommand(node, 'description');
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Get the description from a node.
|
|
628
|
+
* @param node The ConfigNode
|
|
629
|
+
* @returns The description text, or undefined
|
|
630
|
+
*/
|
|
631
|
+
export const getDescription = (node: ConfigNode): string | undefined => {
|
|
632
|
+
const cmd = getChildCommand(node, 'description');
|
|
633
|
+
if (!cmd) return undefined;
|
|
634
|
+
const match = cmd.id.match(/description\s+["']?(.+?)["']?$/i);
|
|
635
|
+
const description = match?.[1];
|
|
636
|
+
return description?.trim();
|
|
637
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// packages/rule-helpers/src/aruba/index.ts
|
|
2
|
+
// Re-export all Aruba 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
|
+
parseIp,
|
|
12
|
+
isShutdown,
|
|
13
|
+
} from '../common/helpers';
|