@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,892 @@
|
|
|
1
|
+
// packages/rule-helpers/src/fortinet/helpers.ts
|
|
2
|
+
// Fortinet FortiGate (FortiOS) specific helper functions
|
|
3
|
+
|
|
4
|
+
import type { ConfigNode } from '../../types/ConfigNode';
|
|
5
|
+
import { parseIp } from '../common/helpers';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Find a config section by name within a node's children.
|
|
9
|
+
* FortiOS uses "config <section>" format.
|
|
10
|
+
* @param node The parent ConfigNode
|
|
11
|
+
* @param sectionName The section name (e.g., "system global", "firewall policy")
|
|
12
|
+
* @returns The matching child node, or undefined
|
|
13
|
+
*/
|
|
14
|
+
export const findConfigSection = (
|
|
15
|
+
node: ConfigNode,
|
|
16
|
+
sectionName: string
|
|
17
|
+
): ConfigNode | undefined => {
|
|
18
|
+
const normalizedName = sectionName.toLowerCase();
|
|
19
|
+
return node.children.find((child) => {
|
|
20
|
+
const childId = child.id.toLowerCase();
|
|
21
|
+
// Match "config <sectionName>" or just "<sectionName>"
|
|
22
|
+
return (
|
|
23
|
+
childId === `config ${normalizedName}` ||
|
|
24
|
+
childId === normalizedName ||
|
|
25
|
+
childId.startsWith(`config ${normalizedName} `) ||
|
|
26
|
+
childId.startsWith(`${normalizedName} `)
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find all config sections matching a pattern within a node's children
|
|
33
|
+
* @param node The parent ConfigNode
|
|
34
|
+
* @param pattern The regex pattern to match
|
|
35
|
+
* @returns Array of matching child nodes
|
|
36
|
+
*/
|
|
37
|
+
export const findConfigSections = (node: ConfigNode, pattern: RegExp): ConfigNode[] => {
|
|
38
|
+
return node.children.filter((child) => pattern.test(child.id.toLowerCase()));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Find an edit entry by name within a config section.
|
|
43
|
+
* FortiOS uses "edit <name>" for entries.
|
|
44
|
+
* @param configSection The config section ConfigNode
|
|
45
|
+
* @param entryName The entry name to find
|
|
46
|
+
* @returns The matching edit entry, or undefined
|
|
47
|
+
*/
|
|
48
|
+
export const findEditEntry = (
|
|
49
|
+
configSection: ConfigNode,
|
|
50
|
+
entryName: string
|
|
51
|
+
): ConfigNode | undefined => {
|
|
52
|
+
const normalizedName = entryName.toLowerCase().replace(/^["']|["']$/g, '');
|
|
53
|
+
return configSection.children.find((child) => {
|
|
54
|
+
const childId = child.id.toLowerCase();
|
|
55
|
+
// Match "edit <name>" with or without quotes
|
|
56
|
+
const editMatch = childId.match(/^edit\s+["']?([^"']+)["']?$/i);
|
|
57
|
+
const editName = editMatch?.[1];
|
|
58
|
+
if (editName) {
|
|
59
|
+
return editName.toLowerCase() === normalizedName;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get all edit entries within a config section
|
|
67
|
+
* @param configSection The config section ConfigNode
|
|
68
|
+
* @returns Array of edit entry nodes
|
|
69
|
+
*/
|
|
70
|
+
export const getEditEntries = (configSection: ConfigNode): ConfigNode[] => {
|
|
71
|
+
return configSection.children.filter((child) =>
|
|
72
|
+
child.id.toLowerCase().startsWith('edit ')
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract the name from an edit entry.
|
|
78
|
+
* FortiOS uses "edit <name>" format.
|
|
79
|
+
* @param editEntry The edit entry ConfigNode
|
|
80
|
+
* @returns The entry name
|
|
81
|
+
*/
|
|
82
|
+
export const getEditEntryName = (editEntry: ConfigNode): string => {
|
|
83
|
+
const match = editEntry.id.match(/^edit\s+["']?([^"']+)["']?$/i);
|
|
84
|
+
const entryName = match?.[1];
|
|
85
|
+
return entryName ?? editEntry.id;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get a "set" command value from a FortiOS config entry.
|
|
90
|
+
* FortiOS uses "set <param> <value>" format.
|
|
91
|
+
* @param node The ConfigNode
|
|
92
|
+
* @param paramName The parameter name
|
|
93
|
+
* @returns The value, or undefined
|
|
94
|
+
*/
|
|
95
|
+
export const getSetValue = (node: ConfigNode, paramName: string): string | undefined => {
|
|
96
|
+
const normalizedParam = paramName.toLowerCase();
|
|
97
|
+
for (const child of node.children) {
|
|
98
|
+
const childId = child.id.toLowerCase();
|
|
99
|
+
const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, 'i'));
|
|
100
|
+
const value = match?.[1];
|
|
101
|
+
if (value) {
|
|
102
|
+
// Remove quotes if present
|
|
103
|
+
return value.replace(/^["']|["']$/g, '').trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a "set" command exists for a parameter
|
|
111
|
+
* @param node The ConfigNode
|
|
112
|
+
* @param paramName The parameter name
|
|
113
|
+
* @returns true if the set command exists
|
|
114
|
+
*/
|
|
115
|
+
export const hasSetValue = (node: ConfigNode, paramName: string): boolean => {
|
|
116
|
+
return getSetValue(node, paramName) !== undefined;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get all "set" command values for a parameter that may appear multiple times
|
|
121
|
+
* (e.g., set member "obj1", set member "obj2")
|
|
122
|
+
* @param node The ConfigNode
|
|
123
|
+
* @param paramName The parameter name
|
|
124
|
+
* @returns Array of values
|
|
125
|
+
*/
|
|
126
|
+
export const getSetValues = (node: ConfigNode, paramName: string): string[] => {
|
|
127
|
+
const normalizedParam = paramName.toLowerCase();
|
|
128
|
+
const values: string[] = [];
|
|
129
|
+
for (const child of node.children) {
|
|
130
|
+
const childId = child.id.toLowerCase();
|
|
131
|
+
const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, 'i'));
|
|
132
|
+
const matchValue = match?.[1];
|
|
133
|
+
if (matchValue) {
|
|
134
|
+
// Handle space-separated values (e.g., set allowaccess ping https ssh)
|
|
135
|
+
const valueStr = matchValue.replace(/^["']|["']$/g, '').trim();
|
|
136
|
+
// Split by space, keeping quoted values together
|
|
137
|
+
const parts = valueStr.match(/["'][^"']+["']|\S+/g) || [];
|
|
138
|
+
values.push(...parts.map((p) => p.replace(/^["']|["']$/g, '')));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return values;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if a firewall policy action is "accept" (allow)
|
|
146
|
+
* @param policyNode The firewall policy ConfigNode
|
|
147
|
+
* @returns true if the action is accept
|
|
148
|
+
*/
|
|
149
|
+
export const isPolicyAccept = (policyNode: ConfigNode): boolean => {
|
|
150
|
+
const action = getSetValue(policyNode, 'action');
|
|
151
|
+
return action?.toLowerCase() === 'accept';
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if a firewall policy action is "deny" or "drop"
|
|
156
|
+
* @param policyNode The firewall policy ConfigNode
|
|
157
|
+
* @returns true if the action is deny
|
|
158
|
+
*/
|
|
159
|
+
export const isPolicyDeny = (policyNode: ConfigNode): boolean => {
|
|
160
|
+
const action = getSetValue(policyNode, 'action');
|
|
161
|
+
if (!action) return false;
|
|
162
|
+
const actionLower = action.toLowerCase();
|
|
163
|
+
return actionLower === 'deny' || actionLower === 'drop';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if a firewall policy is disabled (status disable)
|
|
168
|
+
* @param policyNode The firewall policy ConfigNode
|
|
169
|
+
* @returns true if the policy is disabled
|
|
170
|
+
*/
|
|
171
|
+
export const isPolicyDisabled = (policyNode: ConfigNode): boolean => {
|
|
172
|
+
const status = getSetValue(policyNode, 'status');
|
|
173
|
+
return status?.toLowerCase() === 'disable';
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if a firewall policy has logging enabled
|
|
178
|
+
* @param policyNode The firewall policy ConfigNode
|
|
179
|
+
* @returns Object indicating logtraffic status
|
|
180
|
+
*/
|
|
181
|
+
export const hasLogging = (policyNode: ConfigNode): { logtraffic: string | undefined; logtrafficStart: boolean } => {
|
|
182
|
+
const logtraffic = getSetValue(policyNode, 'logtraffic');
|
|
183
|
+
const logtrafficStart = getSetValue(policyNode, 'logtraffic-start');
|
|
184
|
+
return {
|
|
185
|
+
logtraffic,
|
|
186
|
+
logtrafficStart: logtrafficStart?.toLowerCase() === 'enable',
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if a policy uses "all" (any) source address
|
|
192
|
+
* @param policyNode The firewall policy ConfigNode
|
|
193
|
+
* @returns true if srcaddr includes "all"
|
|
194
|
+
*/
|
|
195
|
+
export const hasAnySrcAddr = (policyNode: ConfigNode): boolean => {
|
|
196
|
+
const srcaddr = getSetValues(policyNode, 'srcaddr');
|
|
197
|
+
return srcaddr.some((addr) => addr.toLowerCase() === 'all');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if a policy uses "all" (any) destination address
|
|
202
|
+
* @param policyNode The firewall policy ConfigNode
|
|
203
|
+
* @returns true if dstaddr includes "all"
|
|
204
|
+
*/
|
|
205
|
+
export const hasAnyDstAddr = (policyNode: ConfigNode): boolean => {
|
|
206
|
+
const dstaddr = getSetValues(policyNode, 'dstaddr');
|
|
207
|
+
return dstaddr.some((addr) => addr.toLowerCase() === 'all');
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if a policy uses "ALL" service (any service)
|
|
212
|
+
* @param policyNode The firewall policy ConfigNode
|
|
213
|
+
* @returns true if service includes "ALL"
|
|
214
|
+
*/
|
|
215
|
+
export const hasAnyService = (policyNode: ConfigNode): boolean => {
|
|
216
|
+
const service = getSetValues(policyNode, 'service');
|
|
217
|
+
return service.some((svc) => svc.toUpperCase() === 'ALL');
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if a policy has UTM/security profiles attached
|
|
222
|
+
* @param policyNode The firewall policy ConfigNode
|
|
223
|
+
* @returns Object with profile statuses
|
|
224
|
+
*/
|
|
225
|
+
export const getSecurityProfiles = (policyNode: ConfigNode): {
|
|
226
|
+
avProfile: string | undefined;
|
|
227
|
+
webfilterProfile: string | undefined;
|
|
228
|
+
ipsProfile: string | undefined;
|
|
229
|
+
applicationList: string | undefined;
|
|
230
|
+
dnsfilterProfile: string | undefined;
|
|
231
|
+
emailfilterProfile: string | undefined;
|
|
232
|
+
dlpSensor: string | undefined;
|
|
233
|
+
sslSshProfile: string | undefined;
|
|
234
|
+
profileProtocolOptions: string | undefined;
|
|
235
|
+
utmStatus: string | undefined;
|
|
236
|
+
inspectionMode: string | undefined;
|
|
237
|
+
} => {
|
|
238
|
+
return {
|
|
239
|
+
avProfile: getSetValue(policyNode, 'av-profile'),
|
|
240
|
+
webfilterProfile: getSetValue(policyNode, 'webfilter-profile'),
|
|
241
|
+
ipsProfile: getSetValue(policyNode, 'ips-sensor'),
|
|
242
|
+
applicationList: getSetValue(policyNode, 'application-list'),
|
|
243
|
+
dnsfilterProfile: getSetValue(policyNode, 'dnsfilter-profile'),
|
|
244
|
+
emailfilterProfile: getSetValue(policyNode, 'emailfilter-profile'),
|
|
245
|
+
dlpSensor: getSetValue(policyNode, 'dlp-sensor'),
|
|
246
|
+
sslSshProfile: getSetValue(policyNode, 'ssl-ssh-profile'),
|
|
247
|
+
profileProtocolOptions: getSetValue(policyNode, 'profile-protocol-options'),
|
|
248
|
+
utmStatus: getSetValue(policyNode, 'utm-status'),
|
|
249
|
+
inspectionMode: getSetValue(policyNode, 'inspection-mode'),
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check if a policy has any UTM profile attached
|
|
255
|
+
* @param policyNode The firewall policy ConfigNode
|
|
256
|
+
* @returns true if any security profile is configured
|
|
257
|
+
*/
|
|
258
|
+
export const hasSecurityProfile = (policyNode: ConfigNode): boolean => {
|
|
259
|
+
const profiles = getSecurityProfiles(policyNode);
|
|
260
|
+
return !!(
|
|
261
|
+
profiles.avProfile ||
|
|
262
|
+
profiles.webfilterProfile ||
|
|
263
|
+
profiles.ipsProfile ||
|
|
264
|
+
profiles.applicationList ||
|
|
265
|
+
profiles.dnsfilterProfile ||
|
|
266
|
+
profiles.emailfilterProfile ||
|
|
267
|
+
profiles.dlpSensor
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get the interface IP address and mask from a system interface entry
|
|
273
|
+
* @param interfaceNode The interface edit entry ConfigNode
|
|
274
|
+
* @returns Object with ip and mask, or undefined
|
|
275
|
+
*/
|
|
276
|
+
export const getInterfaceIp = (
|
|
277
|
+
interfaceNode: ConfigNode
|
|
278
|
+
): { ip: string; mask: string } | undefined => {
|
|
279
|
+
const ipValue = getSetValue(interfaceNode, 'ip');
|
|
280
|
+
if (!ipValue) return undefined;
|
|
281
|
+
|
|
282
|
+
// FortiOS format: "set ip 192.168.1.1 255.255.255.0" or "set ip 192.168.1.1/24"
|
|
283
|
+
const parts = ipValue.split(/\s+/);
|
|
284
|
+
if (parts.length >= 2) {
|
|
285
|
+
const [ip, mask] = parts;
|
|
286
|
+
if (!ip || !mask) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
return { ip, mask };
|
|
290
|
+
}
|
|
291
|
+
const firstPart = parts[0];
|
|
292
|
+
if (firstPart && firstPart.includes('/')) {
|
|
293
|
+
const [ip, prefix] = firstPart.split('/');
|
|
294
|
+
if (!ip || !prefix) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
return { ip, mask: prefix };
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get allowed access methods on an interface
|
|
304
|
+
* @param interfaceNode The interface edit entry ConfigNode
|
|
305
|
+
* @returns Array of allowed access methods
|
|
306
|
+
*/
|
|
307
|
+
export const getInterfaceAllowAccess = (interfaceNode: ConfigNode): string[] => {
|
|
308
|
+
return getSetValues(interfaceNode, 'allowaccess');
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if HTTP(S) management is allowed on an interface
|
|
313
|
+
* @param interfaceNode The interface edit entry ConfigNode
|
|
314
|
+
* @returns true if HTTP or HTTPS access is allowed
|
|
315
|
+
*/
|
|
316
|
+
export const hasHttpManagement = (interfaceNode: ConfigNode): boolean => {
|
|
317
|
+
const access = getInterfaceAllowAccess(interfaceNode);
|
|
318
|
+
return access.some((a) => a.toLowerCase() === 'http' || a.toLowerCase() === 'https');
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Check if SSH is allowed on an interface
|
|
323
|
+
* @param interfaceNode The interface edit entry ConfigNode
|
|
324
|
+
* @returns true if SSH access is allowed
|
|
325
|
+
*/
|
|
326
|
+
export const hasSshAccess = (interfaceNode: ConfigNode): boolean => {
|
|
327
|
+
const access = getInterfaceAllowAccess(interfaceNode);
|
|
328
|
+
return access.some((a) => a.toLowerCase() === 'ssh');
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Check if Telnet is allowed on an interface (insecure)
|
|
333
|
+
* @param interfaceNode The interface edit entry ConfigNode
|
|
334
|
+
* @returns true if Telnet access is allowed
|
|
335
|
+
*/
|
|
336
|
+
export const hasTelnetAccess = (interfaceNode: ConfigNode): boolean => {
|
|
337
|
+
const access = getInterfaceAllowAccess(interfaceNode);
|
|
338
|
+
return access.some((a) => a.toLowerCase() === 'telnet');
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get the schedule for a firewall policy
|
|
343
|
+
* @param policyNode The firewall policy ConfigNode
|
|
344
|
+
* @returns The schedule name
|
|
345
|
+
*/
|
|
346
|
+
export const getPolicySchedule = (policyNode: ConfigNode): string | undefined => {
|
|
347
|
+
return getSetValue(policyNode, 'schedule');
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if the schedule is "always" (always active)
|
|
352
|
+
* @param policyNode The firewall policy ConfigNode
|
|
353
|
+
* @returns true if schedule is "always"
|
|
354
|
+
*/
|
|
355
|
+
export const isAlwaysSchedule = (policyNode: ConfigNode): boolean => {
|
|
356
|
+
const schedule = getPolicySchedule(policyNode);
|
|
357
|
+
return schedule?.toLowerCase() === 'always';
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get NAT settings for a policy
|
|
362
|
+
* @param policyNode The firewall policy ConfigNode
|
|
363
|
+
* @returns Object with NAT settings
|
|
364
|
+
*/
|
|
365
|
+
export const getNatSettings = (policyNode: ConfigNode): {
|
|
366
|
+
nat: boolean;
|
|
367
|
+
ippool: boolean;
|
|
368
|
+
poolname: string[];
|
|
369
|
+
} => {
|
|
370
|
+
const nat = getSetValue(policyNode, 'nat');
|
|
371
|
+
const ippool = getSetValue(policyNode, 'ippool');
|
|
372
|
+
const poolname = getSetValues(policyNode, 'poolname');
|
|
373
|
+
return {
|
|
374
|
+
nat: nat?.toLowerCase() === 'enable',
|
|
375
|
+
ippool: ippool?.toLowerCase() === 'enable',
|
|
376
|
+
poolname,
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if HA (High Availability) is configured
|
|
382
|
+
* @param systemHaNode The system ha config section
|
|
383
|
+
* @returns true if HA is enabled
|
|
384
|
+
*/
|
|
385
|
+
export const isHAEnabled = (systemHaNode: ConfigNode): boolean => {
|
|
386
|
+
const mode = getSetValue(systemHaNode, 'mode');
|
|
387
|
+
return mode !== undefined && mode.toLowerCase() !== 'standalone';
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the HA mode
|
|
392
|
+
* @param systemHaNode The system ha config section
|
|
393
|
+
* @returns The HA mode (standalone, a-a, a-p, etc.)
|
|
394
|
+
*/
|
|
395
|
+
export const getHAMode = (systemHaNode: ConfigNode): string | undefined => {
|
|
396
|
+
return getSetValue(systemHaNode, 'mode');
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Check if admin user has strong password policy
|
|
401
|
+
* @param adminNode The admin user edit entry
|
|
402
|
+
* @returns Object with password policy info
|
|
403
|
+
*/
|
|
404
|
+
export const getAdminPasswordPolicy = (adminNode: ConfigNode): {
|
|
405
|
+
forcePasswordChange: boolean;
|
|
406
|
+
twoFactorAuth: string | undefined;
|
|
407
|
+
} => {
|
|
408
|
+
const forcePasswordChange = getSetValue(adminNode, 'force-password-change');
|
|
409
|
+
const twoFactorAuth = getSetValue(adminNode, 'two-factor');
|
|
410
|
+
return {
|
|
411
|
+
forcePasswordChange: forcePasswordChange?.toLowerCase() === 'enable',
|
|
412
|
+
twoFactorAuth,
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get the admin profile (permission level) for an admin user
|
|
418
|
+
* @param adminNode The admin user edit entry
|
|
419
|
+
* @returns The profile name
|
|
420
|
+
*/
|
|
421
|
+
export const getAdminProfile = (adminNode: ConfigNode): string | undefined => {
|
|
422
|
+
return getSetValue(adminNode, 'accprofile');
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Check if admin is a super_admin
|
|
427
|
+
* @param adminNode The admin user edit entry
|
|
428
|
+
* @returns true if super_admin profile
|
|
429
|
+
*/
|
|
430
|
+
export const isSuperAdmin = (adminNode: ConfigNode): boolean => {
|
|
431
|
+
const profile = getAdminProfile(adminNode);
|
|
432
|
+
return profile?.toLowerCase() === 'super_admin';
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get trusted hosts for admin access restriction
|
|
437
|
+
* @param adminNode The admin user edit entry
|
|
438
|
+
* @returns Array of trusted host entries
|
|
439
|
+
*/
|
|
440
|
+
export const getAdminTrustedHosts = (adminNode: ConfigNode): string[] => {
|
|
441
|
+
const trustedHosts: string[] = [];
|
|
442
|
+
// FortiOS uses trusthost1, trusthost2, ... trusthost10
|
|
443
|
+
for (let i = 1; i <= 10; i++) {
|
|
444
|
+
const host = getSetValue(adminNode, `trusthost${i}`);
|
|
445
|
+
if (host && host !== '0.0.0.0 0.0.0.0') {
|
|
446
|
+
trustedHosts.push(host);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return trustedHosts;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Check if admin has any trusted host restriction
|
|
454
|
+
* @param adminNode The admin user edit entry
|
|
455
|
+
* @returns true if trusted hosts are configured
|
|
456
|
+
*/
|
|
457
|
+
export const hasAdminTrustedHosts = (adminNode: ConfigNode): boolean => {
|
|
458
|
+
return getAdminTrustedHosts(adminNode).length > 0;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Parse FortiOS IP address format (e.g., "10.0.0.1 255.255.255.0" or "10.0.0.0/24")
|
|
463
|
+
* @param address The address string
|
|
464
|
+
* @returns Object with parsed address info, or null if invalid
|
|
465
|
+
*/
|
|
466
|
+
export const parseFortiAddress = (
|
|
467
|
+
address: string
|
|
468
|
+
): { ip: number; mask: string } | null => {
|
|
469
|
+
const parts = address.trim().split(/\s+/);
|
|
470
|
+
|
|
471
|
+
// IP + netmask format: "10.0.0.1 255.255.255.0"
|
|
472
|
+
if (parts.length === 2) {
|
|
473
|
+
const [ipStr, maskStr] = parts;
|
|
474
|
+
if (!ipStr || !maskStr) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
const ip = parseIp(ipStr);
|
|
478
|
+
if (ip === null) return null;
|
|
479
|
+
return { ip, mask: maskStr };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// CIDR format: "10.0.0.1/24"
|
|
483
|
+
const singlePart = parts[0];
|
|
484
|
+
if (parts.length === 1 && singlePart && singlePart.includes('/')) {
|
|
485
|
+
const [ipStr, prefix] = singlePart.split('/');
|
|
486
|
+
if (!ipStr || !prefix) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
const ip = parseIp(ipStr);
|
|
490
|
+
if (ip === null) return null;
|
|
491
|
+
return { ip, mask: `/${prefix}` };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Single IP
|
|
495
|
+
if (parts.length === 1 && singlePart) {
|
|
496
|
+
const ip = parseIp(singlePart);
|
|
497
|
+
if (ip === null) return null;
|
|
498
|
+
return { ip, mask: '255.255.255.255' };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return null;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// ============================================================================
|
|
505
|
+
// System Hardening Helpers (FGT-HARD-*)
|
|
506
|
+
// ============================================================================
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Check if USB auto-install is enabled (security risk)
|
|
510
|
+
* @param globalNode The system global config section
|
|
511
|
+
* @returns true if USB auto-install is enabled
|
|
512
|
+
*/
|
|
513
|
+
export const isUsbAutoInstallEnabled = (globalNode: ConfigNode): boolean => {
|
|
514
|
+
const usbAutoInstall = getSetValue(globalNode, 'usb-auto-install');
|
|
515
|
+
return usbAutoInstall?.toLowerCase() === 'enable';
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Check if admin-maintainer account is enabled
|
|
520
|
+
* @param globalNode The system global config section
|
|
521
|
+
* @returns true if maintainer account is enabled
|
|
522
|
+
*/
|
|
523
|
+
export const isAdminMaintainerEnabled = (globalNode: ConfigNode): boolean => {
|
|
524
|
+
const maintainer = getSetValue(globalNode, 'admin-maintainer');
|
|
525
|
+
// Default is enable, so if not explicitly disabled, it's enabled
|
|
526
|
+
return maintainer?.toLowerCase() !== 'disable';
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Check if private data encryption is enabled
|
|
531
|
+
* @param globalNode The system global config section
|
|
532
|
+
* @returns true if private data encryption is enabled
|
|
533
|
+
*/
|
|
534
|
+
export const isPrivateDataEncryptionEnabled = (globalNode: ConfigNode): boolean => {
|
|
535
|
+
const encryption = getSetValue(globalNode, 'private-data-encryption');
|
|
536
|
+
return encryption?.toLowerCase() === 'enable';
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get admin lockout threshold
|
|
541
|
+
* @param globalNode The system global config section
|
|
542
|
+
* @returns The lockout threshold number, or undefined
|
|
543
|
+
*/
|
|
544
|
+
export const getAdminLockoutThreshold = (globalNode: ConfigNode): number | undefined => {
|
|
545
|
+
const threshold = getSetValue(globalNode, 'admin-lockout-threshold');
|
|
546
|
+
return threshold ? parseInt(threshold, 10) : undefined;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get admin lockout duration
|
|
551
|
+
* @param globalNode The system global config section
|
|
552
|
+
* @returns The lockout duration in seconds, or undefined
|
|
553
|
+
*/
|
|
554
|
+
export const getAdminLockoutDuration = (globalNode: ConfigNode): number | undefined => {
|
|
555
|
+
const duration = getSetValue(globalNode, 'admin-lockout-duration');
|
|
556
|
+
return duration ? parseInt(duration, 10) : undefined;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// ============================================================================
|
|
560
|
+
// Password Policy Helpers (FGT-MGMT-004)
|
|
561
|
+
// ============================================================================
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Get password policy settings
|
|
565
|
+
* @param passwordPolicyNode The system password-policy config section
|
|
566
|
+
* @returns Object with password policy settings
|
|
567
|
+
*/
|
|
568
|
+
export const getPasswordPolicySettings = (passwordPolicyNode: ConfigNode): {
|
|
569
|
+
status: boolean;
|
|
570
|
+
minimumLength: number | undefined;
|
|
571
|
+
minLowerCase: number | undefined;
|
|
572
|
+
minUpperCase: number | undefined;
|
|
573
|
+
minNonAlphanumeric: number | undefined;
|
|
574
|
+
minNumber: number | undefined;
|
|
575
|
+
expireStatus: boolean;
|
|
576
|
+
expireDays: number | undefined;
|
|
577
|
+
reusePassword: boolean;
|
|
578
|
+
} => {
|
|
579
|
+
const status = getSetValue(passwordPolicyNode, 'status');
|
|
580
|
+
const minimumLength = getSetValue(passwordPolicyNode, 'minimum-length');
|
|
581
|
+
const minLowerCase = getSetValue(passwordPolicyNode, 'min-lower-case-letter');
|
|
582
|
+
const minUpperCase = getSetValue(passwordPolicyNode, 'min-upper-case-letter');
|
|
583
|
+
const minNonAlphanumeric = getSetValue(passwordPolicyNode, 'min-non-alphanumeric');
|
|
584
|
+
const minNumber = getSetValue(passwordPolicyNode, 'min-number');
|
|
585
|
+
const expireStatus = getSetValue(passwordPolicyNode, 'expire-status');
|
|
586
|
+
const expireDays = getSetValue(passwordPolicyNode, 'expire-day');
|
|
587
|
+
const reusePassword = getSetValue(passwordPolicyNode, 'reuse-password');
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
status: status?.toLowerCase() === 'enable',
|
|
591
|
+
minimumLength: minimumLength ? parseInt(minimumLength, 10) : undefined,
|
|
592
|
+
minLowerCase: minLowerCase ? parseInt(minLowerCase, 10) : undefined,
|
|
593
|
+
minUpperCase: minUpperCase ? parseInt(minUpperCase, 10) : undefined,
|
|
594
|
+
minNonAlphanumeric: minNonAlphanumeric ? parseInt(minNonAlphanumeric, 10) : undefined,
|
|
595
|
+
minNumber: minNumber ? parseInt(minNumber, 10) : undefined,
|
|
596
|
+
expireStatus: expireStatus?.toLowerCase() === 'enable',
|
|
597
|
+
expireDays: expireDays ? parseInt(expireDays, 10) : undefined,
|
|
598
|
+
reusePassword: reusePassword?.toLowerCase() !== 'disable',
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// ============================================================================
|
|
603
|
+
// SNMP Helpers (FGT-MGMT-009)
|
|
604
|
+
// ============================================================================
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Check if SNMP community has default/weak name
|
|
608
|
+
* @param communityNode The SNMP community edit entry
|
|
609
|
+
* @returns true if the community name is weak/default
|
|
610
|
+
*/
|
|
611
|
+
export const hasWeakSnmpCommunity = (communityNode: ConfigNode): boolean => {
|
|
612
|
+
const name = getSetValue(communityNode, 'name');
|
|
613
|
+
if (!name) return false;
|
|
614
|
+
const weakNames = ['public', 'private', 'community', 'snmp', 'default'];
|
|
615
|
+
return weakNames.includes(name.toLowerCase());
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Get SNMP user security level
|
|
620
|
+
* @param snmpUserNode The SNMP user edit entry
|
|
621
|
+
* @returns The security level (no-auth-no-priv, auth-no-priv, auth-priv)
|
|
622
|
+
*/
|
|
623
|
+
export const getSnmpSecurityLevel = (snmpUserNode: ConfigNode): string | undefined => {
|
|
624
|
+
return getSetValue(snmpUserNode, 'security-level');
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// ============================================================================
|
|
628
|
+
// SSL/SSH Profile Helpers (FGT-SSL-*)
|
|
629
|
+
// ============================================================================
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Get SSL inspection profile settings
|
|
633
|
+
* @param sslProfileNode The SSL-SSH profile edit entry
|
|
634
|
+
* @returns Object with SSL settings
|
|
635
|
+
*/
|
|
636
|
+
export const getSslProfileSettings = (sslProfileNode: ConfigNode): {
|
|
637
|
+
minSslVersion: string | undefined;
|
|
638
|
+
unsupportedSslVersion: string | undefined;
|
|
639
|
+
expiredServerCert: string | undefined;
|
|
640
|
+
revokedServerCert: string | undefined;
|
|
641
|
+
untrustedServerCert: string | undefined;
|
|
642
|
+
certValidationFailure: string | undefined;
|
|
643
|
+
} => {
|
|
644
|
+
// Find the ssl config section within the profile
|
|
645
|
+
const sslSection = findConfigSection(sslProfileNode, 'ssl');
|
|
646
|
+
if (!sslSection) {
|
|
647
|
+
return {
|
|
648
|
+
minSslVersion: undefined,
|
|
649
|
+
unsupportedSslVersion: undefined,
|
|
650
|
+
expiredServerCert: undefined,
|
|
651
|
+
revokedServerCert: undefined,
|
|
652
|
+
untrustedServerCert: undefined,
|
|
653
|
+
certValidationFailure: undefined,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
minSslVersion: getSetValue(sslSection, 'min-allowed-ssl-version'),
|
|
659
|
+
unsupportedSslVersion: getSetValue(sslSection, 'unsupported-ssl-version'),
|
|
660
|
+
expiredServerCert: getSetValue(sslSection, 'expired-server-cert'),
|
|
661
|
+
revokedServerCert: getSetValue(sslSection, 'revoked-server-cert'),
|
|
662
|
+
untrustedServerCert: getSetValue(sslSection, 'untrusted-server-cert'),
|
|
663
|
+
certValidationFailure: getSetValue(sslSection, 'cert-validation-failure'),
|
|
664
|
+
};
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Check if SSL profile uses weak SSL version
|
|
669
|
+
* @param minSslVersion The minimum SSL version string
|
|
670
|
+
* @returns true if the version is considered weak
|
|
671
|
+
*/
|
|
672
|
+
export const isWeakSslVersion = (minSslVersion: string | undefined): boolean => {
|
|
673
|
+
if (!minSslVersion) return false;
|
|
674
|
+
const weakVersions = ['ssl-3.0', 'tls-1.0', 'tls-1.1'];
|
|
675
|
+
return weakVersions.includes(minSslVersion.toLowerCase());
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// ============================================================================
|
|
679
|
+
// DoS Policy Helpers (FGT-DOS-*)
|
|
680
|
+
// ============================================================================
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Get DoS anomaly settings from a DoS policy
|
|
684
|
+
* @param dosPolicyNode The DoS policy edit entry
|
|
685
|
+
* @returns Array of anomaly configurations
|
|
686
|
+
*/
|
|
687
|
+
export const getDosAnomalySettings = (dosPolicyNode: ConfigNode): Array<{
|
|
688
|
+
name: string;
|
|
689
|
+
status: boolean;
|
|
690
|
+
action: string | undefined;
|
|
691
|
+
threshold: number | undefined;
|
|
692
|
+
log: boolean;
|
|
693
|
+
}> => {
|
|
694
|
+
const anomalySection = findConfigSection(dosPolicyNode, 'anomaly');
|
|
695
|
+
if (!anomalySection) return [];
|
|
696
|
+
|
|
697
|
+
const anomalies = getEditEntries(anomalySection);
|
|
698
|
+
return anomalies.map((anomaly) => {
|
|
699
|
+
const name = getEditEntryName(anomaly);
|
|
700
|
+
const status = getSetValue(anomaly, 'status');
|
|
701
|
+
const action = getSetValue(anomaly, 'action');
|
|
702
|
+
const threshold = getSetValue(anomaly, 'threshold');
|
|
703
|
+
const log = getSetValue(anomaly, 'log');
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
name,
|
|
707
|
+
status: status?.toLowerCase() === 'enable',
|
|
708
|
+
action,
|
|
709
|
+
threshold: threshold ? parseInt(threshold, 10) : undefined,
|
|
710
|
+
log: log?.toLowerCase() === 'enable',
|
|
711
|
+
};
|
|
712
|
+
});
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
// ============================================================================
|
|
716
|
+
// SD-WAN Helpers (FGT-SDW-*)
|
|
717
|
+
// ============================================================================
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Check if SD-WAN is enabled
|
|
721
|
+
* @param sdwanNode The system sdwan config section
|
|
722
|
+
* @returns true if SD-WAN is enabled
|
|
723
|
+
*/
|
|
724
|
+
export const isSdwanEnabled = (sdwanNode: ConfigNode): boolean => {
|
|
725
|
+
const status = getSetValue(sdwanNode, 'status');
|
|
726
|
+
return status?.toLowerCase() === 'enable';
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Get SD-WAN health check configurations
|
|
731
|
+
* @param sdwanNode The system sdwan config section
|
|
732
|
+
* @returns Array of health check names
|
|
733
|
+
*/
|
|
734
|
+
export const getSdwanHealthChecks = (sdwanNode: ConfigNode): ConfigNode[] => {
|
|
735
|
+
const healthCheckSection = findConfigSection(sdwanNode, 'health-check');
|
|
736
|
+
if (!healthCheckSection) return [];
|
|
737
|
+
return getEditEntries(healthCheckSection);
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Get SD-WAN members
|
|
742
|
+
* @param sdwanNode The system sdwan config section
|
|
743
|
+
* @returns Array of member configurations
|
|
744
|
+
*/
|
|
745
|
+
export const getSdwanMembers = (sdwanNode: ConfigNode): ConfigNode[] => {
|
|
746
|
+
const membersSection = findConfigSection(sdwanNode, 'members');
|
|
747
|
+
if (!membersSection) return [];
|
|
748
|
+
return getEditEntries(membersSection);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// ============================================================================
|
|
752
|
+
// VPN Helpers (Extended for FGT-VPN-*)
|
|
753
|
+
// ============================================================================
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Get IKE version from IPsec phase1
|
|
757
|
+
* @param phase1Node The IPsec phase1-interface edit entry
|
|
758
|
+
* @returns The IKE version (1 or 2), or undefined
|
|
759
|
+
*/
|
|
760
|
+
export const getIkeVersion = (phase1Node: ConfigNode): number | undefined => {
|
|
761
|
+
const version = getSetValue(phase1Node, 'ike-version');
|
|
762
|
+
return version ? parseInt(version, 10) : undefined;
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Get DH groups from IPsec configuration
|
|
767
|
+
* @param phaseNode The IPsec phase1 or phase2 edit entry
|
|
768
|
+
* @returns Array of DH group numbers
|
|
769
|
+
*/
|
|
770
|
+
export const getDhGroups = (phaseNode: ConfigNode): number[] => {
|
|
771
|
+
const dhgrp = getSetValues(phaseNode, 'dhgrp');
|
|
772
|
+
return dhgrp.map((g) => parseInt(g, 10)).filter((n) => !isNaN(n));
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Check if weak DH groups are used
|
|
777
|
+
* @param dhGroups Array of DH group numbers
|
|
778
|
+
* @returns true if any weak DH group is found
|
|
779
|
+
*/
|
|
780
|
+
export const hasWeakDhGroup = (dhGroups: number[]): boolean => {
|
|
781
|
+
const weakGroups = [1, 2, 5]; // DH groups 1, 2, 5 are considered weak
|
|
782
|
+
return dhGroups.some((g) => weakGroups.includes(g));
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Check if PFS is enabled in phase2
|
|
787
|
+
* @param phase2Node The IPsec phase2-interface edit entry
|
|
788
|
+
* @returns true if PFS is enabled
|
|
789
|
+
*/
|
|
790
|
+
export const isPfsEnabled = (phase2Node: ConfigNode): boolean => {
|
|
791
|
+
const pfs = getSetValue(phase2Node, 'pfs');
|
|
792
|
+
return pfs?.toLowerCase() === 'enable';
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Get key lifetime from IPsec phase2
|
|
797
|
+
* @param phase2Node The IPsec phase2-interface edit entry
|
|
798
|
+
* @returns Key lifetime in seconds, or undefined
|
|
799
|
+
*/
|
|
800
|
+
export const getKeyLifetime = (phase2Node: ConfigNode): number | undefined => {
|
|
801
|
+
const lifetime = getSetValue(phase2Node, 'keylifeseconds');
|
|
802
|
+
return lifetime ? parseInt(lifetime, 10) : undefined;
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// ============================================================================
|
|
806
|
+
// SSL VPN Helpers (FGT-VPN-006)
|
|
807
|
+
// ============================================================================
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Get SSL VPN settings
|
|
811
|
+
* @param sslSettingsNode The vpn ssl settings config section
|
|
812
|
+
* @returns Object with SSL VPN settings
|
|
813
|
+
*/
|
|
814
|
+
export const getSslVpnSettings = (sslSettingsNode: ConfigNode): {
|
|
815
|
+
sslMinProtoVer: string | undefined;
|
|
816
|
+
sslMaxProtoVer: string | undefined;
|
|
817
|
+
idleTimeout: number | undefined;
|
|
818
|
+
authTimeout: number | undefined;
|
|
819
|
+
loginAttemptLimit: number | undefined;
|
|
820
|
+
loginBlockTime: number | undefined;
|
|
821
|
+
reqClientCert: boolean;
|
|
822
|
+
checkReferer: boolean;
|
|
823
|
+
} => {
|
|
824
|
+
return {
|
|
825
|
+
sslMinProtoVer: getSetValue(sslSettingsNode, 'ssl-min-proto-ver'),
|
|
826
|
+
sslMaxProtoVer: getSetValue(sslSettingsNode, 'ssl-max-proto-ver'),
|
|
827
|
+
idleTimeout: parseInt(getSetValue(sslSettingsNode, 'idle-timeout') || '0', 10) || undefined,
|
|
828
|
+
authTimeout: parseInt(getSetValue(sslSettingsNode, 'auth-timeout') || '0', 10) || undefined,
|
|
829
|
+
loginAttemptLimit: parseInt(getSetValue(sslSettingsNode, 'login-attempt-limit') || '0', 10) || undefined,
|
|
830
|
+
loginBlockTime: parseInt(getSetValue(sslSettingsNode, 'login-block-time') || '0', 10) || undefined,
|
|
831
|
+
reqClientCert: getSetValue(sslSettingsNode, 'reqclientcert')?.toLowerCase() === 'enable',
|
|
832
|
+
checkReferer: getSetValue(sslSettingsNode, 'check-referer')?.toLowerCase() === 'enable',
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// ============================================================================
|
|
837
|
+
// Admin 2FA Helpers (FGT-MGMT-006)
|
|
838
|
+
// ============================================================================
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Check if admin has two-factor authentication enabled
|
|
842
|
+
* @param adminNode The admin user edit entry
|
|
843
|
+
* @returns true if 2FA is enabled
|
|
844
|
+
*/
|
|
845
|
+
export const hasAdmin2FA = (adminNode: ConfigNode): boolean => {
|
|
846
|
+
const twoFactor = getSetValue(adminNode, 'two-factor');
|
|
847
|
+
return twoFactor !== undefined && twoFactor.toLowerCase() !== 'disable';
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Get admin two-factor authentication type
|
|
852
|
+
* @param adminNode The admin user edit entry
|
|
853
|
+
* @returns The 2FA type (fortitoken, email, sms, etc.) or undefined
|
|
854
|
+
*/
|
|
855
|
+
export const getAdmin2FAType = (adminNode: ConfigNode): string | undefined => {
|
|
856
|
+
return getSetValue(adminNode, 'two-factor');
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// ============================================================================
|
|
860
|
+
// Interface Role Helpers (FGT-NET-003)
|
|
861
|
+
// ============================================================================
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Get interface role
|
|
865
|
+
* @param interfaceNode The interface edit entry
|
|
866
|
+
* @returns The interface role (wan, lan, dmz, undefined)
|
|
867
|
+
*/
|
|
868
|
+
export const getInterfaceRole = (interfaceNode: ConfigNode): string | undefined => {
|
|
869
|
+
return getSetValue(interfaceNode, 'role');
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Check if interface is WAN-facing
|
|
874
|
+
* @param interfaceNode The interface edit entry
|
|
875
|
+
* @returns true if interface has WAN role
|
|
876
|
+
*/
|
|
877
|
+
export const isWanInterface = (interfaceNode: ConfigNode): boolean => {
|
|
878
|
+
const role = getInterfaceRole(interfaceNode);
|
|
879
|
+
return role?.toLowerCase() === 'wan';
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Check if interface has management access on WAN
|
|
884
|
+
* @param interfaceNode The interface edit entry
|
|
885
|
+
* @returns true if WAN interface has management protocols enabled
|
|
886
|
+
*/
|
|
887
|
+
export const hasWanManagementAccess = (interfaceNode: ConfigNode): boolean => {
|
|
888
|
+
if (!isWanInterface(interfaceNode)) return false;
|
|
889
|
+
const access = getInterfaceAllowAccess(interfaceNode);
|
|
890
|
+
const mgmtProtocols = ['https', 'http', 'ssh', 'telnet', 'snmp'];
|
|
891
|
+
return access.some((a) => mgmtProtocols.includes(a.toLowerCase()));
|
|
892
|
+
};
|