@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.
Files changed (71) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +86 -0
  3. package/package.json +60 -0
  4. package/src/constants.ts +77 -0
  5. package/src/engine/RuleExecutor.ts +256 -0
  6. package/src/engine/Runner.ts +312 -0
  7. package/src/engine/SandboxedExecutor.ts +208 -0
  8. package/src/errors.ts +88 -0
  9. package/src/helpers/arista/helpers.ts +1220 -0
  10. package/src/helpers/arista/index.ts +12 -0
  11. package/src/helpers/aruba/helpers.ts +637 -0
  12. package/src/helpers/aruba/index.ts +13 -0
  13. package/src/helpers/cisco/helpers.ts +534 -0
  14. package/src/helpers/cisco/index.ts +11 -0
  15. package/src/helpers/common/helpers.ts +265 -0
  16. package/src/helpers/common/index.ts +5 -0
  17. package/src/helpers/common/validation.ts +280 -0
  18. package/src/helpers/cumulus/helpers.ts +676 -0
  19. package/src/helpers/cumulus/index.ts +12 -0
  20. package/src/helpers/extreme/helpers.ts +422 -0
  21. package/src/helpers/extreme/index.ts +12 -0
  22. package/src/helpers/fortinet/helpers.ts +892 -0
  23. package/src/helpers/fortinet/index.ts +12 -0
  24. package/src/helpers/huawei/helpers.ts +790 -0
  25. package/src/helpers/huawei/index.ts +11 -0
  26. package/src/helpers/index.ts +53 -0
  27. package/src/helpers/juniper/helpers.ts +756 -0
  28. package/src/helpers/juniper/index.ts +12 -0
  29. package/src/helpers/mikrotik/helpers.ts +722 -0
  30. package/src/helpers/mikrotik/index.ts +12 -0
  31. package/src/helpers/nokia/helpers.ts +856 -0
  32. package/src/helpers/nokia/index.ts +11 -0
  33. package/src/helpers/paloalto/helpers.ts +939 -0
  34. package/src/helpers/paloalto/index.ts +12 -0
  35. package/src/helpers/vyos/helpers.ts +429 -0
  36. package/src/helpers/vyos/index.ts +12 -0
  37. package/src/index.ts +30 -0
  38. package/src/json-rules/ExpressionEvaluator.ts +292 -0
  39. package/src/json-rules/HelperRegistry.ts +177 -0
  40. package/src/json-rules/JsonRuleCompiler.ts +339 -0
  41. package/src/json-rules/JsonRuleValidator.ts +371 -0
  42. package/src/json-rules/index.ts +97 -0
  43. package/src/json-rules/schema.json +350 -0
  44. package/src/json-rules/types.ts +303 -0
  45. package/src/pack-loader/PackLoader.ts +332 -0
  46. package/src/pack-loader/index.ts +17 -0
  47. package/src/pack-loader/types.ts +135 -0
  48. package/src/parser/IncrementalParser.ts +527 -0
  49. package/src/parser/Sanitizer.ts +104 -0
  50. package/src/parser/SchemaAwareParser.ts +504 -0
  51. package/src/parser/VendorSchema.ts +72 -0
  52. package/src/parser/vendors/arista-eos.ts +206 -0
  53. package/src/parser/vendors/aruba-aoscx.ts +123 -0
  54. package/src/parser/vendors/aruba-aosswitch.ts +113 -0
  55. package/src/parser/vendors/aruba-wlc.ts +173 -0
  56. package/src/parser/vendors/cisco-ios.ts +110 -0
  57. package/src/parser/vendors/cisco-nxos.ts +107 -0
  58. package/src/parser/vendors/cumulus-linux.ts +161 -0
  59. package/src/parser/vendors/extreme-exos.ts +154 -0
  60. package/src/parser/vendors/extreme-voss.ts +167 -0
  61. package/src/parser/vendors/fortinet-fortigate.ts +217 -0
  62. package/src/parser/vendors/huawei-vrp.ts +192 -0
  63. package/src/parser/vendors/index.ts +1521 -0
  64. package/src/parser/vendors/juniper-junos.ts +230 -0
  65. package/src/parser/vendors/mikrotik-routeros.ts +274 -0
  66. package/src/parser/vendors/nokia-sros.ts +251 -0
  67. package/src/parser/vendors/paloalto-panos.ts +264 -0
  68. package/src/parser/vendors/vyos-vyos.ts +454 -0
  69. package/src/types/ConfigNode.ts +72 -0
  70. package/src/types/DeclarativeRule.ts +158 -0
  71. package/src/types/IRule.ts +270 -0
@@ -0,0 +1,12 @@
1
+ // packages/rule-helpers/src/paloalto/index.ts
2
+ // Re-export all Palo Alto PAN-OS 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,429 @@
1
+ // packages/rule-helpers/src/vyos/helpers.ts
2
+ // VyOS/EdgeOS-specific helper functions
3
+
4
+ import type { ConfigNode } from '../../types/ConfigNode';
5
+ import { hasChildCommand, getChildCommand, parseIp, prefixToMask } from '../common/helpers';
6
+
7
+ // Re-export common helpers for convenience
8
+ export { hasChildCommand, getChildCommand, getChildCommands, parseIp } from '../common/helpers';
9
+
10
+ /**
11
+ * Check if a VyOS interface is disabled (has "disable" statement)
12
+ */
13
+ export const isDisabled = (node: ConfigNode): boolean => {
14
+ return node.children.some((child) => child.id.toLowerCase().trim() === 'disable');
15
+ };
16
+
17
+ /**
18
+ * Check if interface is a physical ethernet port (ethX)
19
+ */
20
+ export const isPhysicalVyosPort = (interfaceName: string): boolean => {
21
+ const name = interfaceName.toLowerCase();
22
+ // VyOS physical ethernet interfaces: ethernet ethX
23
+ // Match patterns like "ethernet eth0", "ethernet eth1", etc.
24
+ if (name.startsWith('ethernet eth')) {
25
+ return true;
26
+ }
27
+ // Also match just "ethX" if encountered directly
28
+ return /^eth\d+$/.test(name);
29
+ };
30
+
31
+ /**
32
+ * Check if interface is a loopback
33
+ */
34
+ export const isLoopback = (interfaceName: string): boolean => {
35
+ const name = interfaceName.toLowerCase();
36
+ return name.includes('loopback') || name === 'lo';
37
+ };
38
+
39
+ /**
40
+ * Check if interface is a bonding interface
41
+ */
42
+ export const isBondingInterface = (interfaceName: string): boolean => {
43
+ const name = interfaceName.toLowerCase();
44
+ return name.includes('bonding') || /^bond\d+$/.test(name);
45
+ };
46
+
47
+ /**
48
+ * Check if interface is a bridge interface
49
+ */
50
+ export const isBridgeInterface = (interfaceName: string): boolean => {
51
+ const name = interfaceName.toLowerCase();
52
+ return name.includes('bridge') || /^br\d+$/.test(name);
53
+ };
54
+
55
+ /**
56
+ * Check if interface is a WireGuard interface
57
+ */
58
+ export const isWireGuardInterface = (interfaceName: string): boolean => {
59
+ const name = interfaceName.toLowerCase();
60
+ return name.includes('wireguard') || /^wg\d+$/.test(name);
61
+ };
62
+
63
+ /**
64
+ * Check if interface is a tunnel interface
65
+ */
66
+ export const isTunnelInterface = (interfaceName: string): boolean => {
67
+ const name = interfaceName.toLowerCase();
68
+ return (
69
+ name.includes('tunnel') ||
70
+ name.includes('vti') ||
71
+ name.includes('vxlan') ||
72
+ /^(tun|vti|vxlan)\d+$/.test(name)
73
+ );
74
+ };
75
+
76
+ /**
77
+ * Parse VyOS address format (e.g., "10.0.0.1/24")
78
+ * @param address The address string with CIDR notation
79
+ * @returns Object with ip number, prefix length, and mask, or null if invalid
80
+ */
81
+ export const parseVyosAddress = (
82
+ address: string
83
+ ): { ip: number; prefix: number; mask: number } | null => {
84
+ // Remove quotes if present
85
+ const cleanAddress = address.replace(/['"]/g, '');
86
+ const parts = cleanAddress.split('/');
87
+ if (parts.length !== 2) return null;
88
+
89
+ const ipPart = parts[0];
90
+ const prefixPart = parts[1];
91
+ if (!ipPart || !prefixPart) return null;
92
+
93
+ const ip = parseIp(ipPart);
94
+ const prefix = parseInt(prefixPart, 10);
95
+
96
+ if (ip === null || isNaN(prefix) || prefix < 0 || prefix > 32) {
97
+ return null;
98
+ }
99
+
100
+ return {
101
+ ip,
102
+ prefix,
103
+ mask: prefixToMask(prefix),
104
+ };
105
+ };
106
+
107
+ /**
108
+ * Find a stanza by name within a node's children
109
+ * @param node The parent ConfigNode
110
+ * @param stanzaName The stanza name to find
111
+ * @returns The matching child node, or undefined
112
+ */
113
+ export const findStanza = (
114
+ node: ConfigNode,
115
+ stanzaName: string
116
+ ): ConfigNode | undefined => {
117
+ return node.children.find(
118
+ (child) => child.id.toLowerCase() === stanzaName.toLowerCase()
119
+ );
120
+ };
121
+
122
+ /**
123
+ * Find stanza by prefix (starts with)
124
+ * @param node The parent ConfigNode
125
+ * @param prefix The prefix to match
126
+ * @returns The matching child node, or undefined
127
+ */
128
+ export const findStanzaByPrefix = (
129
+ node: ConfigNode,
130
+ prefix: string
131
+ ): ConfigNode | undefined => {
132
+ return node.children.find((child) =>
133
+ child.id.toLowerCase().startsWith(prefix.toLowerCase())
134
+ );
135
+ };
136
+
137
+ /**
138
+ * Find all stanzas matching a pattern within a node's children
139
+ * @param node The parent ConfigNode
140
+ * @param pattern The regex pattern to match
141
+ * @returns Array of matching child nodes
142
+ */
143
+ export const findStanzas = (node: ConfigNode, pattern: RegExp): ConfigNode[] => {
144
+ return node.children.filter((child) => pattern.test(child.id.toLowerCase()));
145
+ };
146
+
147
+ /**
148
+ * Find all stanzas starting with a prefix
149
+ * @param node The parent ConfigNode
150
+ * @param prefix The prefix to match
151
+ * @returns Array of matching child nodes
152
+ */
153
+ export const findStanzasByPrefix = (node: ConfigNode, prefix: string): ConfigNode[] => {
154
+ return node.children.filter((child) =>
155
+ child.id.toLowerCase().startsWith(prefix.toLowerCase())
156
+ );
157
+ };
158
+
159
+ /**
160
+ * Get all ethernet interfaces from the interfaces node
161
+ * @param interfacesNode The interfaces ConfigNode
162
+ * @returns Array of ethernet interface nodes
163
+ */
164
+ export const getEthernetInterfaces = (interfacesNode: ConfigNode): ConfigNode[] => {
165
+ return interfacesNode.children.filter((child) =>
166
+ child.id.toLowerCase().startsWith('ethernet eth')
167
+ );
168
+ };
169
+
170
+ /**
171
+ * Get VIF (VLAN) subinterfaces from an interface node
172
+ * @param interfaceNode The interface ConfigNode
173
+ * @returns Array of vif nodes
174
+ */
175
+ export const getVifInterfaces = (interfaceNode: ConfigNode): ConfigNode[] => {
176
+ return interfaceNode.children.filter((child) =>
177
+ child.id.toLowerCase().startsWith('vif')
178
+ );
179
+ };
180
+
181
+ /**
182
+ * Check if a firewall ruleset has a default action set
183
+ * @param rulesetNode The firewall ruleset (name X) ConfigNode
184
+ * @returns The default action ('drop', 'accept', 'reject') or undefined
185
+ */
186
+ export const getFirewallDefaultAction = (
187
+ rulesetNode: ConfigNode
188
+ ): 'drop' | 'accept' | 'reject' | undefined => {
189
+ for (const child of rulesetNode.children) {
190
+ const id = child.id.toLowerCase().trim();
191
+ if (id.startsWith('default-action')) {
192
+ if (id.includes('drop')) return 'drop';
193
+ if (id.includes('accept')) return 'accept';
194
+ if (id.includes('reject')) return 'reject';
195
+ }
196
+ }
197
+ return undefined;
198
+ };
199
+
200
+ /**
201
+ * Get all firewall rules from a ruleset
202
+ * @param rulesetNode The firewall ruleset ConfigNode
203
+ * @returns Array of rule nodes
204
+ */
205
+ export const getFirewallRules = (rulesetNode: ConfigNode): ConfigNode[] => {
206
+ return rulesetNode.children.filter((child) =>
207
+ child.id.toLowerCase().startsWith('rule')
208
+ );
209
+ };
210
+
211
+ /**
212
+ * Get the action of a firewall rule
213
+ * @param ruleNode The firewall rule ConfigNode
214
+ * @returns The action ('drop', 'accept', 'reject') or undefined
215
+ */
216
+ export const getFirewallRuleAction = (
217
+ ruleNode: ConfigNode
218
+ ): 'drop' | 'accept' | 'reject' | undefined => {
219
+ for (const child of ruleNode.children) {
220
+ const id = child.id.toLowerCase().trim();
221
+ if (id.startsWith('action')) {
222
+ if (id.includes('drop')) return 'drop';
223
+ if (id.includes('accept')) return 'accept';
224
+ if (id.includes('reject')) return 'reject';
225
+ }
226
+ }
227
+ return undefined;
228
+ };
229
+
230
+ /**
231
+ * Check if a NAT rule has translation configured
232
+ * @param ruleNode The NAT rule ConfigNode
233
+ * @returns true if translation is configured
234
+ */
235
+ export const hasNatTranslation = (ruleNode: ConfigNode): boolean => {
236
+ return ruleNode.children.some((child) =>
237
+ child.id.toLowerCase().startsWith('translation')
238
+ );
239
+ };
240
+
241
+ /**
242
+ * Check if SSH service is configured in a service node
243
+ * @param serviceNode The service ConfigNode
244
+ * @returns true if SSH is configured
245
+ */
246
+ export const hasSshService = (serviceNode: ConfigNode): boolean => {
247
+ return serviceNode.children.some((child) =>
248
+ child.id.toLowerCase().startsWith('ssh')
249
+ );
250
+ };
251
+
252
+ /**
253
+ * Get SSH configuration from service node
254
+ * @param serviceNode The service ConfigNode
255
+ * @returns The SSH configuration node, or undefined
256
+ */
257
+ export const getSshConfig = (serviceNode: ConfigNode): ConfigNode | undefined => {
258
+ return serviceNode.children.find((child) =>
259
+ child.id.toLowerCase().startsWith('ssh')
260
+ );
261
+ };
262
+
263
+ /**
264
+ * Check if DHCP server is configured
265
+ * @param serviceNode The service ConfigNode
266
+ * @returns true if DHCP server is configured
267
+ */
268
+ export const hasDhcpServer = (serviceNode: ConfigNode): boolean => {
269
+ return serviceNode.children.some((child) =>
270
+ child.id.toLowerCase().startsWith('dhcp-server')
271
+ );
272
+ };
273
+
274
+ /**
275
+ * Get DNS forwarding configuration
276
+ * @param serviceNode The service ConfigNode
277
+ * @returns The DNS configuration node, or undefined
278
+ */
279
+ export const getDnsConfig = (serviceNode: ConfigNode): ConfigNode | undefined => {
280
+ return serviceNode.children.find((child) =>
281
+ child.id.toLowerCase().startsWith('dns')
282
+ );
283
+ };
284
+
285
+ /**
286
+ * Check if a system node has NTP configured
287
+ * @param systemNode The system ConfigNode
288
+ * @returns true if NTP is configured
289
+ */
290
+ export const hasNtpConfig = (systemNode: ConfigNode): boolean => {
291
+ return systemNode.children.some((child) =>
292
+ child.id.toLowerCase().startsWith('ntp')
293
+ );
294
+ };
295
+
296
+ /**
297
+ * Check if a system node has syslog configured
298
+ * @param systemNode The system ConfigNode
299
+ * @returns true if syslog is configured
300
+ */
301
+ export const hasSyslogConfig = (systemNode: ConfigNode): boolean => {
302
+ return systemNode.children.some((child) =>
303
+ child.id.toLowerCase().startsWith('syslog')
304
+ );
305
+ };
306
+
307
+ /**
308
+ * Get the login configuration from system node
309
+ * @param systemNode The system ConfigNode
310
+ * @returns The login configuration node, or undefined
311
+ */
312
+ export const getLoginConfig = (systemNode: ConfigNode): ConfigNode | undefined => {
313
+ return systemNode.children.find((child) =>
314
+ child.id.toLowerCase().startsWith('login')
315
+ );
316
+ };
317
+
318
+ /**
319
+ * Get all user configurations from login node
320
+ * @param loginNode The login ConfigNode
321
+ * @returns Array of user nodes
322
+ */
323
+ export const getUserConfigs = (loginNode: ConfigNode): ConfigNode[] => {
324
+ return loginNode.children.filter((child) =>
325
+ child.id.toLowerCase().startsWith('user')
326
+ );
327
+ };
328
+
329
+ /**
330
+ * Get all interfaces that are members of a switch (switch-port)
331
+ * These interfaces don't need individual IP addresses as the switch has the address
332
+ * @param interfacesNode The interfaces ConfigNode
333
+ * @returns Set of interface names (e.g., 'eth1', 'eth2') that are switch members
334
+ */
335
+ export const getSwitchPortMembers = (interfacesNode: ConfigNode): Set<string> => {
336
+ const members = new Set<string>();
337
+
338
+ // Find all switch interfaces (switch switchX)
339
+ const switches = interfacesNode.children.filter((child) =>
340
+ child.id.toLowerCase().startsWith('switch ')
341
+ );
342
+
343
+ for (const switchNode of switches) {
344
+ // Find switch-port section
345
+ const switchPort = switchNode.children.find((child) =>
346
+ child.id.toLowerCase() === 'switch-port'
347
+ );
348
+
349
+ if (switchPort) {
350
+ // Find all interface members (interface ethX)
351
+ for (const child of switchPort.children) {
352
+ const match = child.id.toLowerCase().match(/^interface\s+(eth\d+)$/);
353
+ if (match?.[1]) {
354
+ members.add(match[1]);
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ return members;
361
+ };
362
+
363
+ /**
364
+ * Get all interfaces that are members of a bridge
365
+ * These interfaces don't need individual IP addresses as the bridge has the address
366
+ * @param interfacesNode The interfaces ConfigNode
367
+ * @returns Set of interface names that are bridge members
368
+ */
369
+ export const getBridgeMembers = (interfacesNode: ConfigNode): Set<string> => {
370
+ const members = new Set<string>();
371
+
372
+ // Find all bridge interfaces (bridge brX)
373
+ const bridges = interfacesNode.children.filter((child) =>
374
+ child.id.toLowerCase().startsWith('bridge ')
375
+ );
376
+
377
+ for (const bridgeNode of bridges) {
378
+ // Find member section
379
+ const memberSection = bridgeNode.children.find((child) =>
380
+ child.id.toLowerCase() === 'member'
381
+ );
382
+
383
+ if (memberSection) {
384
+ // Find all interface members within the member section
385
+ for (const child of memberSection.children) {
386
+ const match = child.id.toLowerCase().match(/^interface\s+(\S+)$/);
387
+ if (match?.[1]) {
388
+ members.add(match[1]);
389
+ }
390
+ }
391
+ }
392
+ }
393
+
394
+ return members;
395
+ };
396
+
397
+ /**
398
+ * Get all interfaces that are members of a bonding group
399
+ * These interfaces don't need individual IP addresses as the bond has the address
400
+ * @param interfacesNode The interfaces ConfigNode
401
+ * @returns Set of interface names that are bonding members
402
+ */
403
+ export const getBondingMembers = (interfacesNode: ConfigNode): Set<string> => {
404
+ const members = new Set<string>();
405
+
406
+ // Find all bonding interfaces (bonding bondX)
407
+ const bonds = interfacesNode.children.filter((child) =>
408
+ child.id.toLowerCase().startsWith('bonding ')
409
+ );
410
+
411
+ for (const bondNode of bonds) {
412
+ // Find member section
413
+ const memberSection = bondNode.children.find((child) =>
414
+ child.id.toLowerCase() === 'member'
415
+ );
416
+
417
+ if (memberSection) {
418
+ // Find all interface members within the member section
419
+ for (const child of memberSection.children) {
420
+ const match = child.id.toLowerCase().match(/^interface\s+(\S+)$/);
421
+ if (match?.[1]) {
422
+ members.add(match[1]);
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ return members;
429
+ };
@@ -0,0 +1,12 @@
1
+ // packages/rule-helpers/src/vyos/index.ts
2
+ // Re-export all VyOS/EdgeOS 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';
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ // packages/core/src/index.ts
2
+
3
+ export * from './types/ConfigNode';
4
+ export * from './types/IRule';
5
+ export * from './parser/SchemaAwareParser';
6
+ export * from './parser/IncrementalParser';
7
+ export * from './parser/VendorSchema';
8
+ export * from './parser/vendors';
9
+ export * from './engine/Runner';
10
+ export * from './engine/RuleExecutor';
11
+ export * from './parser/Sanitizer';
12
+ export * from './constants';
13
+ export * from './errors';
14
+
15
+ // SEC-012: Encrypted rule pack loader
16
+ export * from './pack-loader';
17
+
18
+ // SEC-001: Declarative rules and sandboxed execution
19
+ export * from './types/DeclarativeRule';
20
+ export * from './engine/SandboxedExecutor';
21
+
22
+ // JSON Rules - third-party rule authoring without TypeScript
23
+ export * from './json-rules';
24
+
25
+ // Rule Helpers - vendor-specific and common helper functions
26
+ export * as helpers from './helpers';
27
+ export { VENDOR_NAMESPACES, type VendorNamespace, getAllVendorModules, getVendorModule } from './helpers';
28
+
29
+ // Re-export common helpers at top level for convenience
30
+ export * from './helpers/common';