@sentriflow/cli 0.3.0 → 0.4.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/README.md +35 -0
- package/dist/index.js +412 -608
- package/package.json +54 -54
package/dist/index.js
CHANGED
|
@@ -4738,6 +4738,7 @@ __export(helpers_exports, {
|
|
|
4738
4738
|
cumulus: () => cumulus_exports,
|
|
4739
4739
|
equalsIgnoreCase: () => equalsIgnoreCase,
|
|
4740
4740
|
extreme: () => extreme_exports,
|
|
4741
|
+
findParentSection: () => findParentSection,
|
|
4741
4742
|
fortinet: () => fortinet_exports,
|
|
4742
4743
|
getAllVendorModules: () => getAllVendorModules,
|
|
4743
4744
|
getChildCommand: () => getChildCommand,
|
|
@@ -4953,6 +4954,20 @@ var isShutdown = (node) => {
|
|
|
4953
4954
|
return id === "shutdown" || id === "disable";
|
|
4954
4955
|
});
|
|
4955
4956
|
};
|
|
4957
|
+
var findParentSection = (ast, targetNode) => {
|
|
4958
|
+
for (const node of ast) {
|
|
4959
|
+
if (node.type === "section") {
|
|
4960
|
+
if (node.children.some(
|
|
4961
|
+
(child) => child.loc.startLine === targetNode.loc.startLine
|
|
4962
|
+
)) {
|
|
4963
|
+
return node;
|
|
4964
|
+
}
|
|
4965
|
+
const found = findParentSection(node.children, targetNode);
|
|
4966
|
+
if (found) return found;
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
return void 0;
|
|
4970
|
+
};
|
|
4956
4971
|
var isInterfaceDefinition = (node) => {
|
|
4957
4972
|
if (!node?.id) return false;
|
|
4958
4973
|
const id = node.id.toLowerCase();
|
|
@@ -6109,6 +6124,7 @@ var getDescription = (node) => {
|
|
|
6109
6124
|
// ../core/src/helpers/cisco/index.ts
|
|
6110
6125
|
var cisco_exports = {};
|
|
6111
6126
|
__export(cisco_exports, {
|
|
6127
|
+
findParentSection: () => findParentSection,
|
|
6112
6128
|
getBgpNeighbors: () => getBgpNeighbors,
|
|
6113
6129
|
getChildCommand: () => getChildCommand,
|
|
6114
6130
|
getChildCommands: () => getChildCommands,
|
|
@@ -6143,6 +6159,7 @@ __export(cisco_exports, {
|
|
|
6143
6159
|
isEndpointPort: () => isEndpointPort,
|
|
6144
6160
|
isExternalFacing: () => isExternalFacing,
|
|
6145
6161
|
isLikelyTrunk: () => isLikelyTrunk,
|
|
6162
|
+
isLineConfigPassword: () => isLineConfigPassword,
|
|
6146
6163
|
isLoopbackInterface: () => isLoopbackInterface,
|
|
6147
6164
|
isPhoneOrAP: () => isPhoneOrAP,
|
|
6148
6165
|
isPhysicalPort: () => isPhysicalPort,
|
|
@@ -6240,6 +6257,12 @@ var hasWeakUsernamePassword = (node) => {
|
|
|
6240
6257
|
}
|
|
6241
6258
|
return false;
|
|
6242
6259
|
};
|
|
6260
|
+
var isLineConfigPassword = (ast, node) => {
|
|
6261
|
+
const parent = findParentSection(ast, node);
|
|
6262
|
+
if (!parent) return false;
|
|
6263
|
+
const parentId = parent.id.toLowerCase();
|
|
6264
|
+
return parentId.startsWith("line vty") || parentId.startsWith("line console") || parentId.startsWith("line aux");
|
|
6265
|
+
};
|
|
6243
6266
|
var getSshVersion = (node) => {
|
|
6244
6267
|
if (includesIgnoreCase(node.rawText, "ip ssh version")) {
|
|
6245
6268
|
const match = node.params.find((p) => p === "1" || p === "2");
|
|
@@ -10010,16 +10033,16 @@ var hasFloodProtection = (zppNode) => {
|
|
|
10010
10033
|
const udp = findStanza6(flood, "udp");
|
|
10011
10034
|
const icmp = findStanza6(flood, "icmp");
|
|
10012
10035
|
const otherIp = findStanza6(flood, "other-ip");
|
|
10013
|
-
const
|
|
10036
|
+
const isEnabled5 = (stanza) => {
|
|
10014
10037
|
if (!stanza) return false;
|
|
10015
10038
|
const enableCmd = getChildCommand(stanza, "enable");
|
|
10016
10039
|
return enableCmd?.id.toLowerCase().includes("yes") ?? false;
|
|
10017
10040
|
};
|
|
10018
10041
|
return {
|
|
10019
|
-
hasSyn:
|
|
10020
|
-
hasUdp:
|
|
10021
|
-
hasIcmp:
|
|
10022
|
-
hasOtherIp:
|
|
10042
|
+
hasSyn: isEnabled5(tcpSyn),
|
|
10043
|
+
hasUdp: isEnabled5(udp),
|
|
10044
|
+
hasIcmp: isEnabled5(icmp),
|
|
10045
|
+
hasOtherIp: isEnabled5(otherIp)
|
|
10023
10046
|
};
|
|
10024
10047
|
};
|
|
10025
10048
|
var hasReconProtection = (zppNode) => {
|
|
@@ -10702,8 +10725,10 @@ var GRX2_EXTENDED_FLAG = 1;
|
|
|
10702
10725
|
var GRX2_PORTABLE_FLAG = 2;
|
|
10703
10726
|
var GRX2_ALGORITHM_AES_256_GCM = 1;
|
|
10704
10727
|
var GRX2_KDF_PBKDF2 = 1;
|
|
10705
|
-
var
|
|
10706
|
-
var
|
|
10728
|
+
var SENTRIFLOW_HOME = join(homedir(), ".sentriflow");
|
|
10729
|
+
var DEFAULT_PACKS_DIRECTORY = join(SENTRIFLOW_HOME, "packs");
|
|
10730
|
+
var DEFAULT_RULES_DIRECTORY = join(SENTRIFLOW_HOME, "rules");
|
|
10731
|
+
var CACHE_DIRECTORY = join(SENTRIFLOW_HOME, "cache");
|
|
10707
10732
|
|
|
10708
10733
|
// ../core/src/grx2-loader/MachineId.ts
|
|
10709
10734
|
var import_node_machine_id = __toESM(require_dist(), 1);
|
|
@@ -10865,7 +10890,6 @@ function parseExtendedHeader(data) {
|
|
|
10865
10890
|
}
|
|
10866
10891
|
async function loadExtendedPack(filePath, licenseKey, machineId2, debug) {
|
|
10867
10892
|
debug?.(`[GRX2Loader] Loading pack: ${filePath}`);
|
|
10868
|
-
debug?.(`[GRX2Loader] License key length: ${licenseKey.length}, first 20 chars: ${licenseKey.substring(0, 20)}...`);
|
|
10869
10893
|
debug?.(`[GRX2Loader] Machine ID: "${machineId2}" (length: ${machineId2.length})`);
|
|
10870
10894
|
const data = await readFile(filePath);
|
|
10871
10895
|
debug?.(`[GRX2Loader] Pack file size: ${data.length} bytes`);
|
|
@@ -12129,6 +12153,178 @@ function extractIPSummary(content, options = {}) {
|
|
|
12129
12153
|
};
|
|
12130
12154
|
}
|
|
12131
12155
|
|
|
12156
|
+
// ../core/src/ip/classifier.ts
|
|
12157
|
+
var DEFAULT_FILTER_OPTIONS = {
|
|
12158
|
+
keepPublic: true,
|
|
12159
|
+
keepPrivate: true,
|
|
12160
|
+
keepLoopback: false,
|
|
12161
|
+
keepLinkLocal: false,
|
|
12162
|
+
keepMulticast: false,
|
|
12163
|
+
keepReserved: false,
|
|
12164
|
+
keepUnspecified: false,
|
|
12165
|
+
keepBroadcast: false,
|
|
12166
|
+
keepDocumentation: false,
|
|
12167
|
+
keepCgnat: true
|
|
12168
|
+
};
|
|
12169
|
+
function ipv4ToNumber2(ip) {
|
|
12170
|
+
const parts = ip.split(".");
|
|
12171
|
+
if (parts.length !== 4) return 0;
|
|
12172
|
+
let result = 0;
|
|
12173
|
+
for (let i = 0; i < 4; i++) {
|
|
12174
|
+
const octet = parseInt(parts[i] ?? "0", 10);
|
|
12175
|
+
if (isNaN(octet) || octet < 0 || octet > 255) return 0;
|
|
12176
|
+
result = (result << 8) + octet;
|
|
12177
|
+
}
|
|
12178
|
+
return result >>> 0;
|
|
12179
|
+
}
|
|
12180
|
+
function isInIPv4Range(ip, network, prefix) {
|
|
12181
|
+
const ipNum = ipv4ToNumber2(ip);
|
|
12182
|
+
const netNum = ipv4ToNumber2(network);
|
|
12183
|
+
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
12184
|
+
return (ipNum & mask) === (netNum & mask);
|
|
12185
|
+
}
|
|
12186
|
+
function classifyIPv4(ip) {
|
|
12187
|
+
if (ip === "0.0.0.0") return "unspecified";
|
|
12188
|
+
if (ip === "255.255.255.255") return "broadcast";
|
|
12189
|
+
if (isInIPv4Range(ip, "0.0.0.0", 8)) return "unspecified";
|
|
12190
|
+
if (isInIPv4Range(ip, "127.0.0.0", 8)) return "loopback";
|
|
12191
|
+
if (isInIPv4Range(ip, "169.254.0.0", 16)) return "link-local";
|
|
12192
|
+
if (isInIPv4Range(ip, "10.0.0.0", 8)) return "private";
|
|
12193
|
+
if (isInIPv4Range(ip, "172.16.0.0", 12)) return "private";
|
|
12194
|
+
if (isInIPv4Range(ip, "192.168.0.0", 16)) return "private";
|
|
12195
|
+
if (isInIPv4Range(ip, "100.64.0.0", 10)) return "cgnat";
|
|
12196
|
+
if (isInIPv4Range(ip, "192.0.2.0", 24)) return "documentation";
|
|
12197
|
+
if (isInIPv4Range(ip, "198.51.100.0", 24)) return "documentation";
|
|
12198
|
+
if (isInIPv4Range(ip, "203.0.113.0", 24)) return "documentation";
|
|
12199
|
+
if (isInIPv4Range(ip, "224.0.0.0", 4)) return "multicast";
|
|
12200
|
+
if (isInIPv4Range(ip, "240.0.0.0", 4)) return "reserved";
|
|
12201
|
+
return "public";
|
|
12202
|
+
}
|
|
12203
|
+
function classifyIPv4Subnet(subnet) {
|
|
12204
|
+
const slashIndex = subnet.lastIndexOf("/");
|
|
12205
|
+
if (slashIndex === -1) return classifyIPv4(subnet);
|
|
12206
|
+
const network = subnet.substring(0, slashIndex);
|
|
12207
|
+
return classifyIPv4(network);
|
|
12208
|
+
}
|
|
12209
|
+
function expandIPv62(ip) {
|
|
12210
|
+
const zoneIndex = ip.indexOf("%");
|
|
12211
|
+
const addr = zoneIndex !== -1 ? ip.substring(0, zoneIndex) : ip;
|
|
12212
|
+
const parts = addr.split(":");
|
|
12213
|
+
const result = [];
|
|
12214
|
+
for (let i = 0; i < parts.length; i++) {
|
|
12215
|
+
const part = parts[i] ?? "";
|
|
12216
|
+
if (part === "" && i > 0 && i < parts.length - 1) {
|
|
12217
|
+
const nonEmpty = parts.filter((p) => p !== "").length;
|
|
12218
|
+
const zeros = 8 - nonEmpty;
|
|
12219
|
+
for (let j = 0; j < zeros; j++) {
|
|
12220
|
+
result.push(0);
|
|
12221
|
+
}
|
|
12222
|
+
} else if (part !== "") {
|
|
12223
|
+
result.push(parseInt(part, 16) || 0);
|
|
12224
|
+
} else if (i === 0 && (parts[1] ?? "") === "") {
|
|
12225
|
+
const nonEmpty = parts.filter((p) => p !== "").length;
|
|
12226
|
+
const zeros = 8 - nonEmpty;
|
|
12227
|
+
for (let j = 0; j < zeros; j++) {
|
|
12228
|
+
result.push(0);
|
|
12229
|
+
}
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
while (result.length < 8) {
|
|
12233
|
+
result.push(0);
|
|
12234
|
+
}
|
|
12235
|
+
return result.slice(0, 8);
|
|
12236
|
+
}
|
|
12237
|
+
function classifyIPv6(ip) {
|
|
12238
|
+
const parts = expandIPv62(ip);
|
|
12239
|
+
if (parts.every((p) => p === 0)) return "unspecified";
|
|
12240
|
+
if (parts.slice(0, 7).every((p) => p === 0) && parts[7] === 1) return "loopback";
|
|
12241
|
+
if ((parts[0] ?? 0) >= 65152 && (parts[0] ?? 0) <= 65215) return "link-local";
|
|
12242
|
+
if (((parts[0] ?? 0) & 65280) === 65280) return "multicast";
|
|
12243
|
+
if (parts[0] === 8193 && parts[1] === 3512) return "documentation";
|
|
12244
|
+
if (((parts[0] ?? 0) & 65024) === 64512) return "private";
|
|
12245
|
+
return "public";
|
|
12246
|
+
}
|
|
12247
|
+
function classifyIPv6Subnet(subnet) {
|
|
12248
|
+
const slashIndex = subnet.lastIndexOf("/");
|
|
12249
|
+
if (slashIndex === -1) return classifyIPv6(subnet);
|
|
12250
|
+
const network = subnet.substring(0, slashIndex);
|
|
12251
|
+
return classifyIPv6(network);
|
|
12252
|
+
}
|
|
12253
|
+
function shouldKeepClassification(classification, options) {
|
|
12254
|
+
switch (classification) {
|
|
12255
|
+
case "public":
|
|
12256
|
+
return options.keepPublic;
|
|
12257
|
+
case "private":
|
|
12258
|
+
return options.keepPrivate;
|
|
12259
|
+
case "loopback":
|
|
12260
|
+
return options.keepLoopback;
|
|
12261
|
+
case "link-local":
|
|
12262
|
+
return options.keepLinkLocal;
|
|
12263
|
+
case "multicast":
|
|
12264
|
+
return options.keepMulticast;
|
|
12265
|
+
case "reserved":
|
|
12266
|
+
return options.keepReserved;
|
|
12267
|
+
case "unspecified":
|
|
12268
|
+
return options.keepUnspecified;
|
|
12269
|
+
case "broadcast":
|
|
12270
|
+
return options.keepBroadcast;
|
|
12271
|
+
case "documentation":
|
|
12272
|
+
return options.keepDocumentation;
|
|
12273
|
+
case "cgnat":
|
|
12274
|
+
return options.keepCgnat;
|
|
12275
|
+
default:
|
|
12276
|
+
return true;
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
function filterIPv4Addresses(addresses, options = {}) {
|
|
12280
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12281
|
+
return addresses.filter((ip) => {
|
|
12282
|
+
const classification = classifyIPv4(ip);
|
|
12283
|
+
return shouldKeepClassification(classification, opts);
|
|
12284
|
+
});
|
|
12285
|
+
}
|
|
12286
|
+
function filterIPv6Addresses(addresses, options = {}) {
|
|
12287
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12288
|
+
return addresses.filter((ip) => {
|
|
12289
|
+
const classification = classifyIPv6(ip);
|
|
12290
|
+
return shouldKeepClassification(classification, opts);
|
|
12291
|
+
});
|
|
12292
|
+
}
|
|
12293
|
+
function filterIPv4Subnets(subnets, options = {}) {
|
|
12294
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12295
|
+
return subnets.filter((subnet) => {
|
|
12296
|
+
const classification = classifyIPv4Subnet(subnet);
|
|
12297
|
+
return shouldKeepClassification(classification, opts);
|
|
12298
|
+
});
|
|
12299
|
+
}
|
|
12300
|
+
function filterIPv6Subnets(subnets, options = {}) {
|
|
12301
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12302
|
+
return subnets.filter((subnet) => {
|
|
12303
|
+
const classification = classifyIPv6Subnet(subnet);
|
|
12304
|
+
return shouldKeepClassification(classification, opts);
|
|
12305
|
+
});
|
|
12306
|
+
}
|
|
12307
|
+
function filterIPSummary(summary, options = {}) {
|
|
12308
|
+
const ipv4Addresses = filterIPv4Addresses(summary.ipv4Addresses, options);
|
|
12309
|
+
const ipv6Addresses = filterIPv6Addresses(summary.ipv6Addresses, options);
|
|
12310
|
+
const ipv4Subnets = filterIPv4Subnets(summary.ipv4Subnets, options);
|
|
12311
|
+
const ipv6Subnets = filterIPv6Subnets(summary.ipv6Subnets, options);
|
|
12312
|
+
const counts = {
|
|
12313
|
+
ipv4: ipv4Addresses.length,
|
|
12314
|
+
ipv6: ipv6Addresses.length,
|
|
12315
|
+
ipv4Subnets: ipv4Subnets.length,
|
|
12316
|
+
ipv6Subnets: ipv6Subnets.length,
|
|
12317
|
+
total: ipv4Addresses.length + ipv6Addresses.length + ipv4Subnets.length + ipv6Subnets.length
|
|
12318
|
+
};
|
|
12319
|
+
return {
|
|
12320
|
+
ipv4Addresses,
|
|
12321
|
+
ipv6Addresses,
|
|
12322
|
+
ipv4Subnets,
|
|
12323
|
+
ipv6Subnets,
|
|
12324
|
+
counts
|
|
12325
|
+
};
|
|
12326
|
+
}
|
|
12327
|
+
|
|
12132
12328
|
// ../core/src/validation/rule-validation.ts
|
|
12133
12329
|
function validateRule2(rule) {
|
|
12134
12330
|
if (typeof rule !== "object" || rule === null) {
|
|
@@ -12328,7 +12524,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
|
|
|
12328
12524
|
tool: {
|
|
12329
12525
|
driver: {
|
|
12330
12526
|
name: "Sentriflow",
|
|
12331
|
-
version: "0.
|
|
12527
|
+
version: "0.4.0",
|
|
12332
12528
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12333
12529
|
rules: sarifRules,
|
|
12334
12530
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12488,7 +12684,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
|
|
|
12488
12684
|
tool: {
|
|
12489
12685
|
driver: {
|
|
12490
12686
|
name: "Sentriflow",
|
|
12491
|
-
version: "0.
|
|
12687
|
+
version: "0.4.0",
|
|
12492
12688
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12493
12689
|
rules: sarifRules,
|
|
12494
12690
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12702,10 +12898,10 @@ var InterfaceDescriptionRequired = {
|
|
|
12702
12898
|
loc: node.loc
|
|
12703
12899
|
};
|
|
12704
12900
|
}
|
|
12705
|
-
const
|
|
12901
|
+
const hasDescription5 = node.children.some(
|
|
12706
12902
|
(child) => startsWithIgnoreCase(child.id, "description")
|
|
12707
12903
|
);
|
|
12708
|
-
if (!
|
|
12904
|
+
if (!hasDescription5) {
|
|
12709
12905
|
return {
|
|
12710
12906
|
passed: false,
|
|
12711
12907
|
message: `Interface "${node.params.slice(1).join(" ")}" is missing a description.`,
|
|
@@ -12730,59 +12926,6 @@ var allCommonRules = [
|
|
|
12730
12926
|
InterfaceDescriptionRequired
|
|
12731
12927
|
];
|
|
12732
12928
|
|
|
12733
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/validation.ts
|
|
12734
|
-
var equalsIgnoreCase2 = (a, b) => a != null && b != null && a.toLowerCase() === b.toLowerCase();
|
|
12735
|
-
var includesIgnoreCase2 = (haystack, needle) => haystack != null && needle != null && haystack.toLowerCase().includes(needle.toLowerCase());
|
|
12736
|
-
var startsWithIgnoreCase2 = (str, prefix) => str != null && prefix != null && str.toLowerCase().startsWith(prefix.toLowerCase());
|
|
12737
|
-
|
|
12738
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/helpers.ts
|
|
12739
|
-
var hasChildCommand5 = (node, prefix) => {
|
|
12740
|
-
if (!node?.children || !prefix) return false;
|
|
12741
|
-
return node.children.some(
|
|
12742
|
-
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12743
|
-
);
|
|
12744
|
-
};
|
|
12745
|
-
var getChildCommand5 = (node, prefix) => {
|
|
12746
|
-
if (!node?.children || !prefix) return void 0;
|
|
12747
|
-
return node.children.find(
|
|
12748
|
-
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12749
|
-
);
|
|
12750
|
-
};
|
|
12751
|
-
var isShutdown6 = (node) => {
|
|
12752
|
-
if (!node?.children) return false;
|
|
12753
|
-
return node.children.some((child) => {
|
|
12754
|
-
const id = child?.id?.toLowerCase().trim();
|
|
12755
|
-
return id === "shutdown" || id === "disable";
|
|
12756
|
-
});
|
|
12757
|
-
};
|
|
12758
|
-
|
|
12759
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cisco/helpers.ts
|
|
12760
|
-
var isShutdown7 = (node) => {
|
|
12761
|
-
if (!node?.children) return false;
|
|
12762
|
-
return node.children.some((child) => {
|
|
12763
|
-
return child?.id && equalsIgnoreCase2(child.id.trim(), "shutdown");
|
|
12764
|
-
});
|
|
12765
|
-
};
|
|
12766
|
-
var isPhysicalPort4 = (interfaceName) => {
|
|
12767
|
-
return !includesIgnoreCase2(interfaceName, "loopback") && !includesIgnoreCase2(interfaceName, "null") && !includesIgnoreCase2(interfaceName, "vlan") && !includesIgnoreCase2(interfaceName, "tunnel") && !includesIgnoreCase2(interfaceName, "port-channel") && !includesIgnoreCase2(interfaceName, "bvi") && !includesIgnoreCase2(interfaceName, "nve");
|
|
12768
|
-
};
|
|
12769
|
-
var isTrunkPort4 = (node) => {
|
|
12770
|
-
return hasChildCommand5(node, "switchport mode trunk");
|
|
12771
|
-
};
|
|
12772
|
-
var isTrunkToNonCisco2 = (node) => {
|
|
12773
|
-
const desc = getChildCommand5(node, "description");
|
|
12774
|
-
if (desc) {
|
|
12775
|
-
const descText = desc.rawText;
|
|
12776
|
-
if (includesIgnoreCase2(descText, "server:") || includesIgnoreCase2(descText, "storage:") || includesIgnoreCase2(descText, "esx") || includesIgnoreCase2(descText, "vmware") || includesIgnoreCase2(descText, "hyperv") || includesIgnoreCase2(descText, "hyper-v") || includesIgnoreCase2(descText, "linux") || includesIgnoreCase2(descText, "appliance") || includesIgnoreCase2(descText, "firewall") || includesIgnoreCase2(descText, "loadbalancer") || includesIgnoreCase2(descText, "lb:") || includesIgnoreCase2(descText, "nas:") || includesIgnoreCase2(descText, "san:")) {
|
|
12777
|
-
return true;
|
|
12778
|
-
}
|
|
12779
|
-
if (includesIgnoreCase2(descText, "uplink:") || includesIgnoreCase2(descText, "downlink:") || includesIgnoreCase2(descText, "isl:") || includesIgnoreCase2(descText, "po-member:")) {
|
|
12780
|
-
return false;
|
|
12781
|
-
}
|
|
12782
|
-
}
|
|
12783
|
-
return false;
|
|
12784
|
-
};
|
|
12785
|
-
|
|
12786
12929
|
// ../rules-default/src/cisco/ios-rules.ts
|
|
12787
12930
|
var TrunkNoDTP = {
|
|
12788
12931
|
id: "NET-TRUNK-001",
|
|
@@ -12796,16 +12939,16 @@ var TrunkNoDTP = {
|
|
|
12796
12939
|
remediation: 'Add "switchport nonegotiate" to disable DTP on trunk ports connected to non-Cisco devices.'
|
|
12797
12940
|
},
|
|
12798
12941
|
check: (node) => {
|
|
12799
|
-
if (!
|
|
12942
|
+
if (!isPhysicalPort(node.id) || isShutdown3(node)) {
|
|
12800
12943
|
return { passed: true, message: "Not applicable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12801
12944
|
}
|
|
12802
|
-
if (!
|
|
12945
|
+
if (!isTrunkPort2(node)) {
|
|
12803
12946
|
return { passed: true, message: "Not a trunk port.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12804
12947
|
}
|
|
12805
|
-
if (!
|
|
12948
|
+
if (!isTrunkToNonCisco(node)) {
|
|
12806
12949
|
return { passed: true, message: "Trunk to Cisco device - DTP acceptable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12807
12950
|
}
|
|
12808
|
-
if (!
|
|
12951
|
+
if (!hasChildCommand(node, "switchport nonegotiate")) {
|
|
12809
12952
|
return {
|
|
12810
12953
|
passed: false,
|
|
12811
12954
|
message: `Trunk port "${node.params.slice(1).join(" ")}" connected to non-Cisco device needs "switchport nonegotiate".`,
|
|
@@ -12830,11 +12973,11 @@ var AccessExplicitMode = {
|
|
|
12830
12973
|
remediation: 'Add "switchport mode access" to explicitly configure access mode.'
|
|
12831
12974
|
},
|
|
12832
12975
|
check: (node) => {
|
|
12833
|
-
if (!
|
|
12976
|
+
if (!isPhysicalPort(node.id) || isShutdown3(node)) {
|
|
12834
12977
|
return { passed: true, message: "Not applicable.", ruleId: "NET-ACCESS-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12835
12978
|
}
|
|
12836
|
-
const hasAccessVlan =
|
|
12837
|
-
const hasExplicitMode =
|
|
12979
|
+
const hasAccessVlan = hasChildCommand(node, "switchport access vlan");
|
|
12980
|
+
const hasExplicitMode = hasChildCommand(node, "switchport mode");
|
|
12838
12981
|
if (hasAccessVlan && !hasExplicitMode) {
|
|
12839
12982
|
return {
|
|
12840
12983
|
passed: false,
|
|
@@ -12894,15 +13037,26 @@ var CiscoNoPlaintextPasswords = {
|
|
|
12894
13037
|
level: "error",
|
|
12895
13038
|
obu: "Security",
|
|
12896
13039
|
owner: "SecOps",
|
|
12897
|
-
remediation: 'Use "secret" instead of "password", or
|
|
13040
|
+
remediation: 'Use "secret" instead of "password", or encrypt with type 7/8/9.'
|
|
12898
13041
|
},
|
|
12899
|
-
check: (node) => {
|
|
13042
|
+
check: (node, context) => {
|
|
12900
13043
|
const params = node.params;
|
|
12901
|
-
const nodeId = node.id;
|
|
12902
|
-
if (
|
|
13044
|
+
const nodeId = node.id.toLowerCase();
|
|
13045
|
+
if (nodeId.includes("encryption") || nodeId.includes("service")) {
|
|
12903
13046
|
return {
|
|
12904
13047
|
passed: true,
|
|
12905
|
-
message: "Global password configuration
|
|
13048
|
+
message: "Global password configuration.",
|
|
13049
|
+
ruleId: "NET-SEC-001",
|
|
13050
|
+
nodeId: node.id,
|
|
13051
|
+
level: "info",
|
|
13052
|
+
loc: node.loc
|
|
13053
|
+
};
|
|
13054
|
+
}
|
|
13055
|
+
const ast = context.getAst?.();
|
|
13056
|
+
if (ast && isLineConfigPassword(ast, node)) {
|
|
13057
|
+
return {
|
|
13058
|
+
passed: true,
|
|
13059
|
+
message: "Line password - use AAA authentication for security (line passwords cannot be encrypted).",
|
|
12906
13060
|
ruleId: "NET-SEC-001",
|
|
12907
13061
|
nodeId: node.id,
|
|
12908
13062
|
level: "info",
|
|
@@ -12911,20 +13065,10 @@ var CiscoNoPlaintextPasswords = {
|
|
|
12911
13065
|
}
|
|
12912
13066
|
if (params.length >= 2) {
|
|
12913
13067
|
const typeOrValue = params[1];
|
|
12914
|
-
if (
|
|
12915
|
-
return {
|
|
12916
|
-
passed: false,
|
|
12917
|
-
message: 'Possible plaintext password detected. Use encryption type 7 or "secret" command.',
|
|
12918
|
-
ruleId: "NET-SEC-001",
|
|
12919
|
-
nodeId: node.id,
|
|
12920
|
-
level: "error",
|
|
12921
|
-
loc: node.loc
|
|
12922
|
-
};
|
|
12923
|
-
}
|
|
12924
|
-
if (typeOrValue === "7" || typeOrValue === "5" || typeOrValue === "8" || typeOrValue === "9") {
|
|
13068
|
+
if (typeOrValue && ["5", "7", "8", "9"].includes(typeOrValue)) {
|
|
12925
13069
|
return {
|
|
12926
13070
|
passed: true,
|
|
12927
|
-
message:
|
|
13071
|
+
message: `Password is encrypted (type ${typeOrValue}).`,
|
|
12928
13072
|
ruleId: "NET-SEC-001",
|
|
12929
13073
|
nodeId: node.id,
|
|
12930
13074
|
level: "info",
|
|
@@ -12934,17 +13078,17 @@ var CiscoNoPlaintextPasswords = {
|
|
|
12934
13078
|
if (typeOrValue === "0") {
|
|
12935
13079
|
return {
|
|
12936
13080
|
passed: false,
|
|
12937
|
-
message:
|
|
13081
|
+
message: 'Plaintext password detected (type 0). Use "secret" or encrypt with type 7.',
|
|
12938
13082
|
ruleId: "NET-SEC-001",
|
|
12939
13083
|
nodeId: node.id,
|
|
12940
13084
|
level: "error",
|
|
12941
13085
|
loc: node.loc
|
|
12942
13086
|
};
|
|
12943
13087
|
}
|
|
12944
|
-
if (!/^\d+$/.test(typeOrValue)) {
|
|
13088
|
+
if (typeOrValue && !/^\d+$/.test(typeOrValue)) {
|
|
12945
13089
|
return {
|
|
12946
13090
|
passed: false,
|
|
12947
|
-
message: '
|
|
13091
|
+
message: 'Plaintext password detected. Use "secret" or encrypt with type 7.',
|
|
12948
13092
|
ruleId: "NET-SEC-001",
|
|
12949
13093
|
nodeId: node.id,
|
|
12950
13094
|
level: "error",
|
|
@@ -12972,38 +13116,6 @@ var allCiscoRules = [
|
|
|
12972
13116
|
EnableSecretStrong
|
|
12973
13117
|
];
|
|
12974
13118
|
|
|
12975
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/juniper/helpers.ts
|
|
12976
|
-
var findStanza8 = (node, stanzaName) => {
|
|
12977
|
-
if (!node?.children) return void 0;
|
|
12978
|
-
return node.children.find(
|
|
12979
|
-
(child) => child?.id && equalsIgnoreCase2(child.id, stanzaName)
|
|
12980
|
-
);
|
|
12981
|
-
};
|
|
12982
|
-
var findStanzas7 = (node, pattern) => {
|
|
12983
|
-
if (!node?.children) return [];
|
|
12984
|
-
return node.children.filter((child) => child?.id && pattern.test(child.id));
|
|
12985
|
-
};
|
|
12986
|
-
var isFilterTermDrop2 = (termNode) => {
|
|
12987
|
-
if (!termNode?.children) return false;
|
|
12988
|
-
for (const child of termNode.children) {
|
|
12989
|
-
const id = child?.id?.trim();
|
|
12990
|
-
if (!id) continue;
|
|
12991
|
-
if (equalsIgnoreCase2(id, "then discard") || equalsIgnoreCase2(id, "then discard;") || equalsIgnoreCase2(id, "then reject") || equalsIgnoreCase2(id, "then reject;")) {
|
|
12992
|
-
return true;
|
|
12993
|
-
}
|
|
12994
|
-
}
|
|
12995
|
-
const thenStanza = findStanza8(termNode, "then");
|
|
12996
|
-
if (!thenStanza?.children) return false;
|
|
12997
|
-
for (const child of thenStanza.children) {
|
|
12998
|
-
const id = child?.id?.trim();
|
|
12999
|
-
if (!id) continue;
|
|
13000
|
-
if (equalsIgnoreCase2(id, "discard") || equalsIgnoreCase2(id, "discard;") || equalsIgnoreCase2(id, "reject") || equalsIgnoreCase2(id, "reject;")) {
|
|
13001
|
-
return true;
|
|
13002
|
-
}
|
|
13003
|
-
}
|
|
13004
|
-
return false;
|
|
13005
|
-
};
|
|
13006
|
-
|
|
13007
13119
|
// ../rules-default/src/juniper/junos-rules.ts
|
|
13008
13120
|
var RootAuthRequired = {
|
|
13009
13121
|
id: "JUN-SYS-001",
|
|
@@ -13017,7 +13129,7 @@ var RootAuthRequired = {
|
|
|
13017
13129
|
remediation: 'Configure "root-authentication" under system stanza with encrypted-password or ssh-rsa.'
|
|
13018
13130
|
},
|
|
13019
13131
|
check: (node) => {
|
|
13020
|
-
const rootAuth =
|
|
13132
|
+
const rootAuth = findStanza4(node, "root-authentication");
|
|
13021
13133
|
if (!rootAuth) {
|
|
13022
13134
|
return {
|
|
13023
13135
|
passed: false,
|
|
@@ -13028,8 +13140,8 @@ var RootAuthRequired = {
|
|
|
13028
13140
|
loc: node.loc
|
|
13029
13141
|
};
|
|
13030
13142
|
}
|
|
13031
|
-
const hasPassword =
|
|
13032
|
-
const hasSshKey =
|
|
13143
|
+
const hasPassword = hasChildCommand(rootAuth, "encrypted-password");
|
|
13144
|
+
const hasSshKey = hasChildCommand(rootAuth, "ssh-rsa") || hasChildCommand(rootAuth, "ssh-ecdsa");
|
|
13033
13145
|
if (!hasPassword && !hasSshKey) {
|
|
13034
13146
|
return {
|
|
13035
13147
|
passed: false,
|
|
@@ -13062,7 +13174,7 @@ var JunosBgpRouterId = {
|
|
|
13062
13174
|
remediation: 'Configure "router-id" under routing-options stanza.'
|
|
13063
13175
|
},
|
|
13064
13176
|
check: (node) => {
|
|
13065
|
-
const hasRouterId =
|
|
13177
|
+
const hasRouterId = hasChildCommand(node, "router-id");
|
|
13066
13178
|
if (!hasRouterId) {
|
|
13067
13179
|
return {
|
|
13068
13180
|
passed: false,
|
|
@@ -13113,7 +13225,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
13113
13225
|
}
|
|
13114
13226
|
const issues = [];
|
|
13115
13227
|
for (const filter of filters) {
|
|
13116
|
-
const terms =
|
|
13228
|
+
const terms = findStanzas3(filter, /^term/i);
|
|
13117
13229
|
if (terms.length === 0) {
|
|
13118
13230
|
continue;
|
|
13119
13231
|
}
|
|
@@ -13121,7 +13233,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
13121
13233
|
if (!lastTerm) {
|
|
13122
13234
|
continue;
|
|
13123
13235
|
}
|
|
13124
|
-
if (!
|
|
13236
|
+
if (!isFilterTermDrop(lastTerm)) {
|
|
13125
13237
|
issues.push(`Filter "${filter.id}" does not end with a deny term.`);
|
|
13126
13238
|
}
|
|
13127
13239
|
}
|
|
@@ -13154,69 +13266,6 @@ var allJuniperRules = [
|
|
|
13154
13266
|
JunosFirewallDefaultDeny
|
|
13155
13267
|
];
|
|
13156
13268
|
|
|
13157
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/aruba/helpers.ts
|
|
13158
|
-
var getInterfaceName4 = (node) => {
|
|
13159
|
-
if (!node?.id) return void 0;
|
|
13160
|
-
const match = node.id.match(/interface\s+(.+)/i);
|
|
13161
|
-
const ifName = match?.[1]?.trim();
|
|
13162
|
-
return ifName && ifName.length > 0 ? ifName : void 0;
|
|
13163
|
-
};
|
|
13164
|
-
var isAosCxPhysicalPort2 = (interfaceName) => {
|
|
13165
|
-
return /^\d+\/\d+\/\d+$/.test(interfaceName.trim());
|
|
13166
|
-
};
|
|
13167
|
-
var isAosCxLag2 = (interfaceName) => {
|
|
13168
|
-
return /^lag\s*\d+$/i.test(interfaceName.trim());
|
|
13169
|
-
};
|
|
13170
|
-
var isAosCxTrunk2 = (node) => {
|
|
13171
|
-
return hasChildCommand5(node, "vlan trunk");
|
|
13172
|
-
};
|
|
13173
|
-
var getAosCxTrunkAllowed2 = (node) => {
|
|
13174
|
-
const cmd = getChildCommand5(node, "vlan trunk allowed");
|
|
13175
|
-
if (!cmd) return [];
|
|
13176
|
-
const match = cmd.id.match(/vlan\s+trunk\s+allowed\s+([\d,]+)/i);
|
|
13177
|
-
const vlanList = match?.[1];
|
|
13178
|
-
if (!vlanList) return [];
|
|
13179
|
-
return vlanList.split(",").map((v) => parseInt(v.trim(), 10)).filter((v) => !isNaN(v));
|
|
13180
|
-
};
|
|
13181
|
-
var getAosSwitchVlanName2 = (node) => {
|
|
13182
|
-
const cmd = getChildCommand5(node, "name");
|
|
13183
|
-
if (!cmd) return void 0;
|
|
13184
|
-
const match = cmd.id.match(/name\s+["']?([^"']+)["']?/i);
|
|
13185
|
-
const name = match?.[1];
|
|
13186
|
-
return name?.trim();
|
|
13187
|
-
};
|
|
13188
|
-
var getWlanEncryption2 = (node) => {
|
|
13189
|
-
const cmd = getChildCommand5(node, "opmode");
|
|
13190
|
-
if (!cmd) return null;
|
|
13191
|
-
const match = cmd.id.match(/opmode\s+(\S+)/i);
|
|
13192
|
-
const mode = match?.[1];
|
|
13193
|
-
return mode ? mode.toLowerCase() : null;
|
|
13194
|
-
};
|
|
13195
|
-
var hasSecureEncryption2 = (node) => {
|
|
13196
|
-
const opmode = getWlanEncryption2(node);
|
|
13197
|
-
if (!opmode) return false;
|
|
13198
|
-
return opmode.includes("wpa2") || opmode.includes("wpa3") || opmode.includes("aes");
|
|
13199
|
-
};
|
|
13200
|
-
var isOpenSsid2 = (node) => {
|
|
13201
|
-
const opmode = getWlanEncryption2(node);
|
|
13202
|
-
return opmode === "opensystem" || opmode === "open";
|
|
13203
|
-
};
|
|
13204
|
-
var getRadiusHost2 = (node) => {
|
|
13205
|
-
const cmd = getChildCommand5(node, "host");
|
|
13206
|
-
if (!cmd) return void 0;
|
|
13207
|
-
const match = cmd.id.match(/host\s+(\S+)/i);
|
|
13208
|
-
const host = match?.[1];
|
|
13209
|
-
return host?.trim();
|
|
13210
|
-
};
|
|
13211
|
-
var extractProfileName2 = (nodeId) => {
|
|
13212
|
-
const match = nodeId.match(/(?:ssid-profile|virtual-ap|profile|server-group|ap-group|arm-profile)\s+["']?([^"'\n]+)["']?$/i);
|
|
13213
|
-
const profile = match?.[1];
|
|
13214
|
-
return profile ? profile.trim() : void 0;
|
|
13215
|
-
};
|
|
13216
|
-
var hasDescription5 = (node) => {
|
|
13217
|
-
return hasChildCommand5(node, "description");
|
|
13218
|
-
};
|
|
13219
|
-
|
|
13220
13269
|
// ../rules-default/src/aruba/common-rules.ts
|
|
13221
13270
|
var SshEnabled = {
|
|
13222
13271
|
id: "ARU-SEC-001",
|
|
@@ -13304,17 +13353,17 @@ var AosCxInterfaceDescription = {
|
|
|
13304
13353
|
remediation: "Add a description to physical interfaces for documentation."
|
|
13305
13354
|
},
|
|
13306
13355
|
check: (node) => {
|
|
13307
|
-
const ifName =
|
|
13356
|
+
const ifName = getInterfaceName(node);
|
|
13308
13357
|
if (!ifName) {
|
|
13309
13358
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13310
13359
|
}
|
|
13311
|
-
if (!
|
|
13360
|
+
if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
|
|
13312
13361
|
return { passed: true, message: "Not a physical interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13313
13362
|
}
|
|
13314
|
-
if (
|
|
13363
|
+
if (isShutdown(node)) {
|
|
13315
13364
|
return { passed: true, message: "Interface is shutdown.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13316
13365
|
}
|
|
13317
|
-
if (!
|
|
13366
|
+
if (!hasDescription(node)) {
|
|
13318
13367
|
return {
|
|
13319
13368
|
passed: false,
|
|
13320
13369
|
message: `Interface ${ifName} missing description.`,
|
|
@@ -13346,17 +13395,17 @@ var AosCxTrunkAllowedVlans = {
|
|
|
13346
13395
|
remediation: 'Configure "vlan trunk allowed <vlans>" on trunk interfaces.'
|
|
13347
13396
|
},
|
|
13348
13397
|
check: (node) => {
|
|
13349
|
-
const ifName =
|
|
13398
|
+
const ifName = getInterfaceName(node);
|
|
13350
13399
|
if (!ifName) {
|
|
13351
13400
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13352
13401
|
}
|
|
13353
|
-
if (!
|
|
13402
|
+
if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
|
|
13354
13403
|
return { passed: true, message: "Not a switchport interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13355
13404
|
}
|
|
13356
|
-
if (!
|
|
13405
|
+
if (!isAosCxTrunk(node)) {
|
|
13357
13406
|
return { passed: true, message: "Not a trunk port.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13358
13407
|
}
|
|
13359
|
-
const allowedVlans =
|
|
13408
|
+
const allowedVlans = getAosCxTrunkAllowed(node);
|
|
13360
13409
|
if (allowedVlans.length === 0) {
|
|
13361
13410
|
return {
|
|
13362
13411
|
passed: false,
|
|
@@ -13406,7 +13455,7 @@ var AosSwitchVlanName = {
|
|
|
13406
13455
|
if (vlanId === null || isDefaultVlan(vlanId)) {
|
|
13407
13456
|
return { passed: true, message: "Default VLAN 1.", ruleId: "AOSSW-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13408
13457
|
}
|
|
13409
|
-
const vlanName =
|
|
13458
|
+
const vlanName = getAosSwitchVlanName(node);
|
|
13410
13459
|
if (!vlanName) {
|
|
13411
13460
|
return {
|
|
13412
13461
|
passed: false,
|
|
@@ -13480,8 +13529,8 @@ var WlcSsidEncryption = {
|
|
|
13480
13529
|
remediation: 'Configure "opmode wpa2-aes" or "opmode wpa3-sae-aes" for secure encryption.'
|
|
13481
13530
|
},
|
|
13482
13531
|
check: (node) => {
|
|
13483
|
-
const profileName =
|
|
13484
|
-
const opmode =
|
|
13532
|
+
const profileName = extractProfileName(node.id);
|
|
13533
|
+
const opmode = getWlanEncryption(node);
|
|
13485
13534
|
if (!opmode) {
|
|
13486
13535
|
return {
|
|
13487
13536
|
passed: false,
|
|
@@ -13492,7 +13541,7 @@ var WlcSsidEncryption = {
|
|
|
13492
13541
|
loc: node.loc
|
|
13493
13542
|
};
|
|
13494
13543
|
}
|
|
13495
|
-
if (
|
|
13544
|
+
if (isOpenSsid(node)) {
|
|
13496
13545
|
return {
|
|
13497
13546
|
passed: false,
|
|
13498
13547
|
message: `SSID profile "${profileName}" is open/unencrypted. Use WPA2 or WPA3.`,
|
|
@@ -13502,7 +13551,7 @@ var WlcSsidEncryption = {
|
|
|
13502
13551
|
loc: node.loc
|
|
13503
13552
|
};
|
|
13504
13553
|
}
|
|
13505
|
-
if (!
|
|
13554
|
+
if (!hasSecureEncryption(node)) {
|
|
13506
13555
|
return {
|
|
13507
13556
|
passed: false,
|
|
13508
13557
|
message: `SSID profile "${profileName}" uses weak encryption (${opmode}). Use WPA2 or WPA3.`,
|
|
@@ -13534,8 +13583,8 @@ var WlcRadiusHost = {
|
|
|
13534
13583
|
remediation: 'Configure "host <ip-address>" for the RADIUS server.'
|
|
13535
13584
|
},
|
|
13536
13585
|
check: (node) => {
|
|
13537
|
-
const profileName =
|
|
13538
|
-
const host =
|
|
13586
|
+
const profileName = extractProfileName(node.id);
|
|
13587
|
+
const host = getRadiusHost(node);
|
|
13539
13588
|
if (!host) {
|
|
13540
13589
|
return {
|
|
13541
13590
|
passed: false,
|
|
@@ -13583,34 +13632,6 @@ function getRulesByArubaVendor(vendorId) {
|
|
|
13583
13632
|
}
|
|
13584
13633
|
}
|
|
13585
13634
|
|
|
13586
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/paloalto/helpers.ts
|
|
13587
|
-
var findStanza9 = (node, stanzaName) => {
|
|
13588
|
-
if (!node?.children) return void 0;
|
|
13589
|
-
return node.children.find(
|
|
13590
|
-
(child) => child?.id?.toLowerCase() === stanzaName.toLowerCase()
|
|
13591
|
-
);
|
|
13592
|
-
};
|
|
13593
|
-
var hasLogging3 = (ruleNode) => {
|
|
13594
|
-
const logStart = hasChildCommand5(ruleNode, "log-start");
|
|
13595
|
-
const logEnd = hasChildCommand5(ruleNode, "log-end");
|
|
13596
|
-
return { logStart, logEnd };
|
|
13597
|
-
};
|
|
13598
|
-
var isRuleDisabled2 = (ruleNode) => {
|
|
13599
|
-
const disabled = getChildCommand5(ruleNode, "disabled");
|
|
13600
|
-
if (!disabled) return false;
|
|
13601
|
-
return disabled.id.toLowerCase().includes("yes") || disabled.id.toLowerCase().includes("true");
|
|
13602
|
-
};
|
|
13603
|
-
var getSecurityRules2 = (rulebaseNode) => {
|
|
13604
|
-
const security = findStanza9(rulebaseNode, "security");
|
|
13605
|
-
if (!security) return [];
|
|
13606
|
-
const rules = findStanza9(security, "rules");
|
|
13607
|
-
if (!rules?.children) return [];
|
|
13608
|
-
return rules.children;
|
|
13609
|
-
};
|
|
13610
|
-
var hasZoneProtectionProfile2 = (zoneNode) => {
|
|
13611
|
-
return hasChildCommand5(zoneNode, "zone-protection-profile");
|
|
13612
|
-
};
|
|
13613
|
-
|
|
13614
13635
|
// ../rules-default/src/paloalto/panos-rules.ts
|
|
13615
13636
|
var HostnameRequired = {
|
|
13616
13637
|
id: "PAN-SYS-001",
|
|
@@ -13624,7 +13645,7 @@ var HostnameRequired = {
|
|
|
13624
13645
|
remediation: "Configure hostname under deviceconfig > system."
|
|
13625
13646
|
},
|
|
13626
13647
|
check: (node) => {
|
|
13627
|
-
const system =
|
|
13648
|
+
const system = findStanza6(node, "system");
|
|
13628
13649
|
if (!system) {
|
|
13629
13650
|
return {
|
|
13630
13651
|
passed: false,
|
|
@@ -13635,7 +13656,7 @@ var HostnameRequired = {
|
|
|
13635
13656
|
loc: node.loc
|
|
13636
13657
|
};
|
|
13637
13658
|
}
|
|
13638
|
-
const hasHostname =
|
|
13659
|
+
const hasHostname = hasChildCommand(system, "hostname");
|
|
13639
13660
|
if (!hasHostname) {
|
|
13640
13661
|
return {
|
|
13641
13662
|
passed: false,
|
|
@@ -13668,7 +13689,7 @@ var SecurityRuleLogging = {
|
|
|
13668
13689
|
remediation: "Enable log-end (and optionally log-start) on all security rules."
|
|
13669
13690
|
},
|
|
13670
13691
|
check: (node) => {
|
|
13671
|
-
const rules =
|
|
13692
|
+
const rules = getSecurityRules(node);
|
|
13672
13693
|
if (rules.length === 0) {
|
|
13673
13694
|
return {
|
|
13674
13695
|
passed: true,
|
|
@@ -13681,8 +13702,8 @@ var SecurityRuleLogging = {
|
|
|
13681
13702
|
}
|
|
13682
13703
|
const issues = [];
|
|
13683
13704
|
for (const rule of rules) {
|
|
13684
|
-
if (
|
|
13685
|
-
const logging =
|
|
13705
|
+
if (isRuleDisabled(rule)) continue;
|
|
13706
|
+
const logging = hasLogging2(rule);
|
|
13686
13707
|
if (!logging.logEnd) {
|
|
13687
13708
|
issues.push(`Rule "${rule.id}" does not have log-end enabled.`);
|
|
13688
13709
|
}
|
|
@@ -13721,7 +13742,7 @@ var ZoneProtectionRequired = {
|
|
|
13721
13742
|
check: (node) => {
|
|
13722
13743
|
const issues = [];
|
|
13723
13744
|
for (const zone of node.children) {
|
|
13724
|
-
if (!
|
|
13745
|
+
if (!hasZoneProtectionProfile(zone)) {
|
|
13725
13746
|
issues.push(`Zone "${zone.id}" does not have a Zone Protection Profile applied.`);
|
|
13726
13747
|
}
|
|
13727
13748
|
}
|
|
@@ -13769,34 +13790,6 @@ function getRulesByPaloAltoVendor() {
|
|
|
13769
13790
|
return [...allCommonRules, ...allPaloAltoRules];
|
|
13770
13791
|
}
|
|
13771
13792
|
|
|
13772
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/arista/helpers.ts
|
|
13773
|
-
var checkMlagRequirements2 = (mlagNode) => {
|
|
13774
|
-
return {
|
|
13775
|
-
hasDomainId: hasChildCommand5(mlagNode, "domain-id"),
|
|
13776
|
-
hasPeerLink: hasChildCommand5(mlagNode, "peer-link"),
|
|
13777
|
-
hasPeerAddress: hasChildCommand5(mlagNode, "peer-address"),
|
|
13778
|
-
hasLocalInterface: hasChildCommand5(mlagNode, "local-interface")
|
|
13779
|
-
};
|
|
13780
|
-
};
|
|
13781
|
-
var isShutdown8 = (interfaceNode) => {
|
|
13782
|
-
if (!interfaceNode?.children) return false;
|
|
13783
|
-
const hasShutdown = interfaceNode.children.some(
|
|
13784
|
-
(child) => child?.id && equalsIgnoreCase2(child.id, "shutdown")
|
|
13785
|
-
);
|
|
13786
|
-
const hasNoShutdown = interfaceNode.children.some(
|
|
13787
|
-
(child) => child?.id && equalsIgnoreCase2(child.id, "no shutdown")
|
|
13788
|
-
);
|
|
13789
|
-
return hasShutdown && !hasNoShutdown;
|
|
13790
|
-
};
|
|
13791
|
-
var getInterfaceDescription2 = (interfaceNode) => {
|
|
13792
|
-
if (!interfaceNode?.children) return void 0;
|
|
13793
|
-
const descCmd = interfaceNode.children.find(
|
|
13794
|
-
(child) => child?.id && startsWithIgnoreCase2(child.id, "description ")
|
|
13795
|
-
);
|
|
13796
|
-
if (!descCmd) return void 0;
|
|
13797
|
-
return descCmd.id.replace(/^description\s+/i, "").trim();
|
|
13798
|
-
};
|
|
13799
|
-
|
|
13800
13793
|
// ../rules-default/src/arista/eos-rules.ts
|
|
13801
13794
|
var HostnameRequired2 = {
|
|
13802
13795
|
id: "ARI-SYS-001",
|
|
@@ -13845,7 +13838,7 @@ var MlagConfigComplete = {
|
|
|
13845
13838
|
remediation: "MLAG configuration requires: domain-id, peer-link, peer-address, and local-interface."
|
|
13846
13839
|
},
|
|
13847
13840
|
check: (node, context) => {
|
|
13848
|
-
const requirements =
|
|
13841
|
+
const requirements = checkMlagRequirements(node);
|
|
13849
13842
|
const issues = [];
|
|
13850
13843
|
if (!requirements.hasDomainId) {
|
|
13851
13844
|
issues.push("domain-id");
|
|
@@ -13891,7 +13884,7 @@ var InterfaceDescription = {
|
|
|
13891
13884
|
remediation: "Add description to interface: description <text>"
|
|
13892
13885
|
},
|
|
13893
13886
|
check: (node, context) => {
|
|
13894
|
-
if (
|
|
13887
|
+
if (isShutdown2(node)) {
|
|
13895
13888
|
return {
|
|
13896
13889
|
passed: true,
|
|
13897
13890
|
message: "Interface is shutdown, description not required.",
|
|
@@ -13901,7 +13894,7 @@ var InterfaceDescription = {
|
|
|
13901
13894
|
loc: node.loc
|
|
13902
13895
|
};
|
|
13903
13896
|
}
|
|
13904
|
-
const description =
|
|
13897
|
+
const description = getInterfaceDescription(node);
|
|
13905
13898
|
if (!description) {
|
|
13906
13899
|
return {
|
|
13907
13900
|
passed: false,
|
|
@@ -13936,37 +13929,6 @@ function getRulesByAristaVendor() {
|
|
|
13936
13929
|
return [...allCommonRules, ...allAristaRules];
|
|
13937
13930
|
}
|
|
13938
13931
|
|
|
13939
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/vyos/helpers.ts
|
|
13940
|
-
var isDisabled4 = (node) => {
|
|
13941
|
-
if (!node?.children) return false;
|
|
13942
|
-
return node.children.some((child) => child?.id?.toLowerCase().trim() === "disable");
|
|
13943
|
-
};
|
|
13944
|
-
var findStanzasByPrefix3 = (node, prefix) => {
|
|
13945
|
-
if (!node?.children) return [];
|
|
13946
|
-
return node.children.filter(
|
|
13947
|
-
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase())
|
|
13948
|
-
);
|
|
13949
|
-
};
|
|
13950
|
-
var getEthernetInterfaces2 = (interfacesNode) => {
|
|
13951
|
-
if (!interfacesNode?.children) return [];
|
|
13952
|
-
return interfacesNode.children.filter(
|
|
13953
|
-
(child) => child?.id?.toLowerCase().startsWith("ethernet eth")
|
|
13954
|
-
);
|
|
13955
|
-
};
|
|
13956
|
-
var getFirewallDefaultAction2 = (rulesetNode) => {
|
|
13957
|
-
if (!rulesetNode?.children) return void 0;
|
|
13958
|
-
for (const child of rulesetNode.children) {
|
|
13959
|
-
const id = child?.id?.toLowerCase().trim();
|
|
13960
|
-
if (!id) continue;
|
|
13961
|
-
if (id.startsWith("default-action")) {
|
|
13962
|
-
if (id.includes("drop")) return "drop";
|
|
13963
|
-
if (id.includes("accept")) return "accept";
|
|
13964
|
-
if (id.includes("reject")) return "reject";
|
|
13965
|
-
}
|
|
13966
|
-
}
|
|
13967
|
-
return void 0;
|
|
13968
|
-
};
|
|
13969
|
-
|
|
13970
13932
|
// ../rules-default/src/vyos/vyos-rules.ts
|
|
13971
13933
|
var VyosHostnameRequired = {
|
|
13972
13934
|
id: "VYOS-SYS-001",
|
|
@@ -13980,7 +13942,7 @@ var VyosHostnameRequired = {
|
|
|
13980
13942
|
remediation: 'Configure "host-name" under system stanza.'
|
|
13981
13943
|
},
|
|
13982
13944
|
check: (node) => {
|
|
13983
|
-
const hasHostname =
|
|
13945
|
+
const hasHostname = hasChildCommand(node, "host-name");
|
|
13984
13946
|
if (!hasHostname) {
|
|
13985
13947
|
return {
|
|
13986
13948
|
passed: false,
|
|
@@ -14049,12 +14011,12 @@ var VyosInterfaceDescription = {
|
|
|
14049
14011
|
},
|
|
14050
14012
|
check: (node) => {
|
|
14051
14013
|
const issues = [];
|
|
14052
|
-
const ethernetInterfaces =
|
|
14014
|
+
const ethernetInterfaces = getEthernetInterfaces(node);
|
|
14053
14015
|
for (const iface of ethernetInterfaces) {
|
|
14054
|
-
if (
|
|
14016
|
+
if (isDisabled2(iface)) {
|
|
14055
14017
|
continue;
|
|
14056
14018
|
}
|
|
14057
|
-
const hasDesc =
|
|
14019
|
+
const hasDesc = hasChildCommand(iface, "description");
|
|
14058
14020
|
if (!hasDesc) {
|
|
14059
14021
|
const ifaceName = iface.id.split(/\s+/).pop() || iface.id;
|
|
14060
14022
|
issues.push(`Interface "${ifaceName}" missing description.`);
|
|
@@ -14092,7 +14054,7 @@ var VyosFirewallDefaultAction = {
|
|
|
14092
14054
|
remediation: 'Set "default-action drop" or "default-action reject" for each firewall ruleset.'
|
|
14093
14055
|
},
|
|
14094
14056
|
check: (node) => {
|
|
14095
|
-
const rulesets =
|
|
14057
|
+
const rulesets = findStanzasByPrefix2(node, "name");
|
|
14096
14058
|
if (rulesets.length === 0) {
|
|
14097
14059
|
return {
|
|
14098
14060
|
passed: true,
|
|
@@ -14105,7 +14067,7 @@ var VyosFirewallDefaultAction = {
|
|
|
14105
14067
|
}
|
|
14106
14068
|
const issues = [];
|
|
14107
14069
|
for (const ruleset of rulesets) {
|
|
14108
|
-
const defaultAction =
|
|
14070
|
+
const defaultAction = getFirewallDefaultAction(ruleset);
|
|
14109
14071
|
if (!defaultAction) {
|
|
14110
14072
|
const rulesetName = ruleset.id.split(/\s+/)[1] || ruleset.id;
|
|
14111
14073
|
issues.push(`Firewall ruleset "${rulesetName}" has no default-action configured.`);
|
|
@@ -14147,65 +14109,6 @@ function getRulesByVyosVendor() {
|
|
|
14147
14109
|
return [...allCommonRules, ...allVyosRules];
|
|
14148
14110
|
}
|
|
14149
14111
|
|
|
14150
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/fortinet/helpers.ts
|
|
14151
|
-
var getEditEntries2 = (configSection) => {
|
|
14152
|
-
if (!configSection?.children) return [];
|
|
14153
|
-
return configSection.children.filter(
|
|
14154
|
-
(child) => child?.id?.toLowerCase().startsWith("edit ")
|
|
14155
|
-
);
|
|
14156
|
-
};
|
|
14157
|
-
var getEditEntryName2 = (editEntry) => {
|
|
14158
|
-
const match = editEntry.id.match(/^edit\s+["']?([^"']+)["']?$/i);
|
|
14159
|
-
const entryName = match?.[1];
|
|
14160
|
-
return entryName ?? editEntry.id;
|
|
14161
|
-
};
|
|
14162
|
-
var getSetValue2 = (node, paramName) => {
|
|
14163
|
-
if (!node?.children) return void 0;
|
|
14164
|
-
const normalizedParam = paramName.toLowerCase();
|
|
14165
|
-
for (const child of node.children) {
|
|
14166
|
-
const childId = child?.id?.toLowerCase();
|
|
14167
|
-
if (!childId) continue;
|
|
14168
|
-
const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, "i"));
|
|
14169
|
-
const value = match?.[1];
|
|
14170
|
-
if (value) {
|
|
14171
|
-
return value.replace(/^["']|["']$/g, "").trim();
|
|
14172
|
-
}
|
|
14173
|
-
}
|
|
14174
|
-
return void 0;
|
|
14175
|
-
};
|
|
14176
|
-
var isPolicyDisabled2 = (policyNode) => {
|
|
14177
|
-
const status = getSetValue2(policyNode, "status");
|
|
14178
|
-
return status?.toLowerCase() === "disable";
|
|
14179
|
-
};
|
|
14180
|
-
var hasLogging4 = (policyNode) => {
|
|
14181
|
-
const logtraffic = getSetValue2(policyNode, "logtraffic");
|
|
14182
|
-
const logtrafficStart = getSetValue2(policyNode, "logtraffic-start");
|
|
14183
|
-
return {
|
|
14184
|
-
logtraffic,
|
|
14185
|
-
logtrafficStart: logtrafficStart?.toLowerCase() === "enable"
|
|
14186
|
-
};
|
|
14187
|
-
};
|
|
14188
|
-
var getAdminProfile2 = (adminNode) => {
|
|
14189
|
-
return getSetValue2(adminNode, "accprofile");
|
|
14190
|
-
};
|
|
14191
|
-
var isSuperAdmin2 = (adminNode) => {
|
|
14192
|
-
const profile = getAdminProfile2(adminNode);
|
|
14193
|
-
return profile?.toLowerCase() === "super_admin";
|
|
14194
|
-
};
|
|
14195
|
-
var getAdminTrustedHosts2 = (adminNode) => {
|
|
14196
|
-
const trustedHosts = [];
|
|
14197
|
-
for (let i = 1; i <= 10; i++) {
|
|
14198
|
-
const host = getSetValue2(adminNode, `trusthost${i}`);
|
|
14199
|
-
if (host && host !== "0.0.0.0 0.0.0.0") {
|
|
14200
|
-
trustedHosts.push(host);
|
|
14201
|
-
}
|
|
14202
|
-
}
|
|
14203
|
-
return trustedHosts;
|
|
14204
|
-
};
|
|
14205
|
-
var hasAdminTrustedHosts2 = (adminNode) => {
|
|
14206
|
-
return getAdminTrustedHosts2(adminNode).length > 0;
|
|
14207
|
-
};
|
|
14208
|
-
|
|
14209
14112
|
// ../rules-default/src/fortinet/fortigate-rules.ts
|
|
14210
14113
|
var HostnameRequired3 = {
|
|
14211
14114
|
id: "FGT-SYS-001",
|
|
@@ -14219,7 +14122,7 @@ var HostnameRequired3 = {
|
|
|
14219
14122
|
remediation: 'Configure hostname under "config system global" using "set hostname <name>".'
|
|
14220
14123
|
},
|
|
14221
14124
|
check: (node) => {
|
|
14222
|
-
const hostname =
|
|
14125
|
+
const hostname = getSetValue(node, "hostname");
|
|
14223
14126
|
if (!hostname) {
|
|
14224
14127
|
return {
|
|
14225
14128
|
passed: false,
|
|
@@ -14252,7 +14155,7 @@ var AdminTrustedHostRequired = {
|
|
|
14252
14155
|
remediation: 'Configure trusted hosts for each admin user using "set trusthost1", "set trusthost2", etc.'
|
|
14253
14156
|
},
|
|
14254
14157
|
check: (node) => {
|
|
14255
|
-
const admins =
|
|
14158
|
+
const admins = getEditEntries(node);
|
|
14256
14159
|
if (admins.length === 0) {
|
|
14257
14160
|
return {
|
|
14258
14161
|
passed: true,
|
|
@@ -14265,11 +14168,11 @@ var AdminTrustedHostRequired = {
|
|
|
14265
14168
|
}
|
|
14266
14169
|
const issues = [];
|
|
14267
14170
|
for (const admin of admins) {
|
|
14268
|
-
const adminName =
|
|
14269
|
-
if (
|
|
14171
|
+
const adminName = getEditEntryName(admin);
|
|
14172
|
+
if (hasAdminTrustedHosts(admin)) {
|
|
14270
14173
|
continue;
|
|
14271
14174
|
}
|
|
14272
|
-
if (
|
|
14175
|
+
if (isSuperAdmin(admin)) {
|
|
14273
14176
|
issues.push(`Super admin "${adminName}" has no trusted host restrictions. This is a critical security issue.`);
|
|
14274
14177
|
} else {
|
|
14275
14178
|
issues.push(`Admin "${adminName}" has no trusted host restrictions.`);
|
|
@@ -14307,7 +14210,7 @@ var PolicyLoggingRequired = {
|
|
|
14307
14210
|
remediation: 'Enable logging on all firewall policies using "set logtraffic all" or "set logtraffic utm".'
|
|
14308
14211
|
},
|
|
14309
14212
|
check: (node) => {
|
|
14310
|
-
const policies =
|
|
14213
|
+
const policies = getEditEntries(node);
|
|
14311
14214
|
if (policies.length === 0) {
|
|
14312
14215
|
return {
|
|
14313
14216
|
passed: true,
|
|
@@ -14320,10 +14223,10 @@ var PolicyLoggingRequired = {
|
|
|
14320
14223
|
}
|
|
14321
14224
|
const issues = [];
|
|
14322
14225
|
for (const policy of policies) {
|
|
14323
|
-
if (
|
|
14324
|
-
const logging =
|
|
14226
|
+
if (isPolicyDisabled(policy)) continue;
|
|
14227
|
+
const logging = hasLogging(policy);
|
|
14325
14228
|
if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
|
|
14326
|
-
const policyId =
|
|
14229
|
+
const policyId = getEditEntryName(policy);
|
|
14327
14230
|
issues.push(`Policy ${policyId} does not have logging enabled.`);
|
|
14328
14231
|
}
|
|
14329
14232
|
}
|
|
@@ -14361,44 +14264,6 @@ function getRulesByFortinetVendor() {
|
|
|
14361
14264
|
return [...allCommonRules, ...allFortinetRules];
|
|
14362
14265
|
}
|
|
14363
14266
|
|
|
14364
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/extreme/helpers.ts
|
|
14365
|
-
var getExosVlanName2 = (node) => {
|
|
14366
|
-
const match = node.id.match(/^(?:create|configure)\s+vlan\s+["']?(\w+)["']?/i);
|
|
14367
|
-
const vlanName = match?.[1];
|
|
14368
|
-
return vlanName?.trim();
|
|
14369
|
-
};
|
|
14370
|
-
var isVossVlanCreate2 = (node) => {
|
|
14371
|
-
return /^vlan\s+create\s+\d+/i.test(node.id);
|
|
14372
|
-
};
|
|
14373
|
-
var getVossVlanId2 = (node) => {
|
|
14374
|
-
const match = node.id.match(/^vlan\s+(?:create|members|i-sid)\s+(\d+)/i);
|
|
14375
|
-
const vlanId = match?.[1];
|
|
14376
|
-
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14377
|
-
};
|
|
14378
|
-
var isVossGigabitEthernet2 = (node) => {
|
|
14379
|
-
return /^interface\s+GigabitEthernet\s+\d+\/\d+/i.test(node.id);
|
|
14380
|
-
};
|
|
14381
|
-
var isVossShutdown2 = (node) => {
|
|
14382
|
-
if (!node?.children) return false;
|
|
14383
|
-
const hasShutdown = node.children.some(
|
|
14384
|
-
(child) => child?.id?.toLowerCase() === "shutdown"
|
|
14385
|
-
);
|
|
14386
|
-
const hasNoShutdown = node.children.some(
|
|
14387
|
-
(child) => child?.id?.toLowerCase() === "no shutdown"
|
|
14388
|
-
);
|
|
14389
|
-
return hasShutdown && !hasNoShutdown;
|
|
14390
|
-
};
|
|
14391
|
-
var getVossDefaultVlan2 = (node) => {
|
|
14392
|
-
if (!node?.children) return void 0;
|
|
14393
|
-
const defaultVlan = node.children.find(
|
|
14394
|
-
(child) => child?.id && /^default-vlan-id\s+\d+/i.test(child.id)
|
|
14395
|
-
);
|
|
14396
|
-
if (!defaultVlan?.id) return void 0;
|
|
14397
|
-
const match = defaultVlan.id.match(/default-vlan-id\s+(\d+)/i);
|
|
14398
|
-
const vlanId = match?.[1];
|
|
14399
|
-
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14400
|
-
};
|
|
14401
|
-
|
|
14402
14267
|
// ../rules-default/src/extreme/exos-rules.ts
|
|
14403
14268
|
var ExosSysnameRequired = {
|
|
14404
14269
|
id: "EXOS-SYS-001",
|
|
@@ -14478,7 +14343,7 @@ var ExosVlanNaming = {
|
|
|
14478
14343
|
remediation: 'Use descriptive VLAN names: create vlan "<meaningful-name>" tag <id>'
|
|
14479
14344
|
},
|
|
14480
14345
|
check: (node, context) => {
|
|
14481
|
-
const vlanName =
|
|
14346
|
+
const vlanName = getExosVlanName(node);
|
|
14482
14347
|
if (!vlanName) {
|
|
14483
14348
|
return {
|
|
14484
14349
|
passed: false,
|
|
@@ -14569,7 +14434,7 @@ var VossVlanIsidRequired = {
|
|
|
14569
14434
|
remediation: "Configure I-SID for VLAN: vlan i-sid <vlan-id> <isid>"
|
|
14570
14435
|
},
|
|
14571
14436
|
check: (node, context) => {
|
|
14572
|
-
if (!
|
|
14437
|
+
if (!isVossVlanCreate(node)) {
|
|
14573
14438
|
return {
|
|
14574
14439
|
passed: true,
|
|
14575
14440
|
message: "Not a VLAN create command.",
|
|
@@ -14579,7 +14444,7 @@ var VossVlanIsidRequired = {
|
|
|
14579
14444
|
loc: node.loc
|
|
14580
14445
|
};
|
|
14581
14446
|
}
|
|
14582
|
-
const vlanId =
|
|
14447
|
+
const vlanId = getVossVlanId(node);
|
|
14583
14448
|
if (!vlanId) {
|
|
14584
14449
|
return {
|
|
14585
14450
|
passed: false,
|
|
@@ -14622,7 +14487,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14622
14487
|
remediation: "Configure default VLAN: default-vlan-id <vlan-id>"
|
|
14623
14488
|
},
|
|
14624
14489
|
check: (node, context) => {
|
|
14625
|
-
if (!
|
|
14490
|
+
if (!isVossGigabitEthernet(node)) {
|
|
14626
14491
|
return {
|
|
14627
14492
|
passed: true,
|
|
14628
14493
|
message: "Not a GigabitEthernet interface.",
|
|
@@ -14632,7 +14497,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14632
14497
|
loc: node.loc
|
|
14633
14498
|
};
|
|
14634
14499
|
}
|
|
14635
|
-
if (
|
|
14500
|
+
if (isVossShutdown(node)) {
|
|
14636
14501
|
return {
|
|
14637
14502
|
passed: true,
|
|
14638
14503
|
message: "Interface is shutdown.",
|
|
@@ -14642,7 +14507,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14642
14507
|
loc: node.loc
|
|
14643
14508
|
};
|
|
14644
14509
|
}
|
|
14645
|
-
const defaultVlan =
|
|
14510
|
+
const defaultVlan = getVossDefaultVlan(node);
|
|
14646
14511
|
if (!defaultVlan) {
|
|
14647
14512
|
return {
|
|
14648
14513
|
passed: false,
|
|
@@ -14694,22 +14559,6 @@ function getRulesByExtremeVendor(vendorId) {
|
|
|
14694
14559
|
}
|
|
14695
14560
|
}
|
|
14696
14561
|
|
|
14697
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/huawei/helpers.ts
|
|
14698
|
-
var isEnabled4 = (node) => {
|
|
14699
|
-
if (!node?.children) return false;
|
|
14700
|
-
return node.children.some((child) => {
|
|
14701
|
-
const rawText = child?.rawText?.toLowerCase().trim();
|
|
14702
|
-
return rawText === "undo shutdown";
|
|
14703
|
-
});
|
|
14704
|
-
};
|
|
14705
|
-
var isPhysicalPort5 = (interfaceName) => {
|
|
14706
|
-
const name = interfaceName.toLowerCase();
|
|
14707
|
-
return !name.includes("vlanif") && !name.includes("loopback") && !name.includes("null") && !name.includes("tunnel") && !name.includes("eth-trunk") && !name.includes("nve") && !name.includes("vbdif");
|
|
14708
|
-
};
|
|
14709
|
-
var hasDescription6 = (node) => {
|
|
14710
|
-
return hasChildCommand5(node, "description");
|
|
14711
|
-
};
|
|
14712
|
-
|
|
14713
14562
|
// ../rules-default/src/huawei/vrp-rules.ts
|
|
14714
14563
|
var SysnameRequired = {
|
|
14715
14564
|
id: "HUAWEI-SYS-001",
|
|
@@ -14757,7 +14606,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14757
14606
|
},
|
|
14758
14607
|
check: (node) => {
|
|
14759
14608
|
const interfaceName = node.id.replace(/^interface\s+/i, "").trim();
|
|
14760
|
-
if (!
|
|
14609
|
+
if (!isPhysicalPort2(interfaceName)) {
|
|
14761
14610
|
return {
|
|
14762
14611
|
passed: true,
|
|
14763
14612
|
message: "Non-physical interface, description optional.",
|
|
@@ -14767,7 +14616,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14767
14616
|
loc: node.loc
|
|
14768
14617
|
};
|
|
14769
14618
|
}
|
|
14770
|
-
if (!
|
|
14619
|
+
if (!isEnabled(node)) {
|
|
14771
14620
|
return {
|
|
14772
14621
|
passed: true,
|
|
14773
14622
|
message: "Interface is shutdown, description optional.",
|
|
@@ -14777,7 +14626,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14777
14626
|
loc: node.loc
|
|
14778
14627
|
};
|
|
14779
14628
|
}
|
|
14780
|
-
if (!
|
|
14629
|
+
if (!hasDescription3(node)) {
|
|
14781
14630
|
return {
|
|
14782
14631
|
passed: false,
|
|
14783
14632
|
message: `Interface ${interfaceName} is enabled but has no description.`,
|
|
@@ -14861,55 +14710,6 @@ function getRulesByHuaweiVendor() {
|
|
|
14861
14710
|
return [...allCommonRules, ...allHuaweiRules];
|
|
14862
14711
|
}
|
|
14863
14712
|
|
|
14864
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/mikrotik/helpers.ts
|
|
14865
|
-
var parseProperty2 = (commandStr, propertyName) => {
|
|
14866
|
-
const regex = new RegExp(`\\b${propertyName}=(?:"([^"]+)"|'([^']+)'|(\\S+))`, "i");
|
|
14867
|
-
const match = commandStr.match(regex);
|
|
14868
|
-
if (match) {
|
|
14869
|
-
return match[1] || match[2] || match[3];
|
|
14870
|
-
}
|
|
14871
|
-
return void 0;
|
|
14872
|
-
};
|
|
14873
|
-
var getFirewallChain2 = (nodeOrCommand) => {
|
|
14874
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14875
|
-
return parseProperty2(str, "chain");
|
|
14876
|
-
};
|
|
14877
|
-
var getFirewallAction2 = (nodeOrCommand) => {
|
|
14878
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14879
|
-
return parseProperty2(str, "action");
|
|
14880
|
-
};
|
|
14881
|
-
var getName2 = (nodeOrCommand) => {
|
|
14882
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14883
|
-
return parseProperty2(str, "name");
|
|
14884
|
-
};
|
|
14885
|
-
var isAddCommand2 = (nodeOrCommand) => {
|
|
14886
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14887
|
-
return /^add\s+/i.test(str.trim());
|
|
14888
|
-
};
|
|
14889
|
-
var isSetCommand2 = (nodeOrCommand) => {
|
|
14890
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14891
|
-
return /^set\s+/i.test(str.trim());
|
|
14892
|
-
};
|
|
14893
|
-
var getAddCommands2 = (node) => {
|
|
14894
|
-
if (!node?.children) return [];
|
|
14895
|
-
return node.children.filter((child) => isAddCommand2(child));
|
|
14896
|
-
};
|
|
14897
|
-
var isServiceDisabled2 = (nodeOrCommand) => {
|
|
14898
|
-
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14899
|
-
const disabled = parseProperty2(str, "disabled");
|
|
14900
|
-
return disabled?.toLowerCase() === "yes";
|
|
14901
|
-
};
|
|
14902
|
-
var getSystemIdentity2 = (node) => {
|
|
14903
|
-
if (!node?.children) return void 0;
|
|
14904
|
-
for (const child of node.children) {
|
|
14905
|
-
if (isSetCommand2(child)) {
|
|
14906
|
-
const name = getName2(child);
|
|
14907
|
-
if (name) return name;
|
|
14908
|
-
}
|
|
14909
|
-
}
|
|
14910
|
-
return void 0;
|
|
14911
|
-
};
|
|
14912
|
-
|
|
14913
14713
|
// ../rules-default/src/mikrotik/routeros-rules.ts
|
|
14914
14714
|
var MikrotikSystemIdentity = {
|
|
14915
14715
|
id: "MIK-SYS-001",
|
|
@@ -14923,7 +14723,7 @@ var MikrotikSystemIdentity = {
|
|
|
14923
14723
|
remediation: "Configure system identity: /system identity set name=MyRouter"
|
|
14924
14724
|
},
|
|
14925
14725
|
check: (node) => {
|
|
14926
|
-
const identity =
|
|
14726
|
+
const identity = getSystemIdentity(node);
|
|
14927
14727
|
if (!identity || equalsIgnoreCase(identity, "mikrotik") || equalsIgnoreCase(identity, "routerboard")) {
|
|
14928
14728
|
return {
|
|
14929
14729
|
passed: false,
|
|
@@ -14961,8 +14761,8 @@ var MikrotikDisableUnusedServices = {
|
|
|
14961
14761
|
for (const child of node.children) {
|
|
14962
14762
|
const childId = child.id.toLowerCase();
|
|
14963
14763
|
for (const service of dangerousServices) {
|
|
14964
|
-
if (childId.includes(service) && !
|
|
14965
|
-
const disabled =
|
|
14764
|
+
if (childId.includes(service) && !isServiceDisabled(child)) {
|
|
14765
|
+
const disabled = parseProperty(child.id, "disabled");
|
|
14966
14766
|
if (!disabled || !equalsIgnoreCase(disabled, "yes")) {
|
|
14967
14767
|
issues.push(`Service '${service}' is enabled. Consider disabling it.`);
|
|
14968
14768
|
}
|
|
@@ -15001,11 +14801,11 @@ var MikrotikInputChainDrop = {
|
|
|
15001
14801
|
remediation: "Add drop rule for input chain: add chain=input action=drop"
|
|
15002
14802
|
},
|
|
15003
14803
|
check: (node) => {
|
|
15004
|
-
const addCommands =
|
|
14804
|
+
const addCommands = getAddCommands(node);
|
|
15005
14805
|
let hasInputDrop = false;
|
|
15006
14806
|
for (const cmd of addCommands) {
|
|
15007
|
-
const chain =
|
|
15008
|
-
const action =
|
|
14807
|
+
const chain = getFirewallChain(cmd);
|
|
14808
|
+
const action = getFirewallAction(cmd);
|
|
15009
14809
|
if (chain && equalsIgnoreCase(chain, "input") && action && equalsIgnoreCase(action, "drop")) {
|
|
15010
14810
|
hasInputDrop = true;
|
|
15011
14811
|
break;
|
|
@@ -15042,60 +14842,6 @@ function getRulesByMikroTikVendor() {
|
|
|
15042
14842
|
return [...allCommonRules, ...allMikroTikRules];
|
|
15043
14843
|
}
|
|
15044
14844
|
|
|
15045
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/nokia/helpers.ts
|
|
15046
|
-
var isAdminStateDisabled2 = (node) => {
|
|
15047
|
-
if (!node?.children) return false;
|
|
15048
|
-
const directCheck = node.children.some((child) => {
|
|
15049
|
-
const rawText = child?.rawText?.toLowerCase().trim();
|
|
15050
|
-
return rawText === "admin-state disable";
|
|
15051
|
-
});
|
|
15052
|
-
if (directCheck) return true;
|
|
15053
|
-
return node?.rawText?.toLowerCase().includes("admin-state disable") ?? false;
|
|
15054
|
-
};
|
|
15055
|
-
var isPhysicalPort6 = (portName) => {
|
|
15056
|
-
const name = portName.toLowerCase();
|
|
15057
|
-
return /^\d+\/\d+\/\d+/.test(name);
|
|
15058
|
-
};
|
|
15059
|
-
var hasDescription7 = (node) => {
|
|
15060
|
-
return hasChildCommand5(node, "description");
|
|
15061
|
-
};
|
|
15062
|
-
var getDescription4 = (node) => {
|
|
15063
|
-
const descCmd = getChildCommand5(node, "description");
|
|
15064
|
-
if (descCmd?.rawText) {
|
|
15065
|
-
const match = descCmd.rawText.match(/description\s+"([^"]+)"|description\s+(\S+)/i);
|
|
15066
|
-
if (match) {
|
|
15067
|
-
return match[1] || match[2];
|
|
15068
|
-
}
|
|
15069
|
-
}
|
|
15070
|
-
return void 0;
|
|
15071
|
-
};
|
|
15072
|
-
var getSystemName2 = (node) => {
|
|
15073
|
-
if (!node?.children) return void 0;
|
|
15074
|
-
const nameCmd = node.children.find((child) => {
|
|
15075
|
-
return child?.id?.toLowerCase().startsWith("name");
|
|
15076
|
-
});
|
|
15077
|
-
if (nameCmd?.rawText) {
|
|
15078
|
-
const match = nameCmd.rawText.match(/name\s+"([^"]+)"/i);
|
|
15079
|
-
if (match) {
|
|
15080
|
-
return match[1];
|
|
15081
|
-
}
|
|
15082
|
-
}
|
|
15083
|
-
return void 0;
|
|
15084
|
-
};
|
|
15085
|
-
var hasBgpRouterId3 = (node) => {
|
|
15086
|
-
return hasChildCommand5(node, "router-id");
|
|
15087
|
-
};
|
|
15088
|
-
var getBgpRouterId2 = (node) => {
|
|
15089
|
-
const routerIdCmd = getChildCommand5(node, "router-id");
|
|
15090
|
-
if (routerIdCmd?.rawText) {
|
|
15091
|
-
const match = routerIdCmd.rawText.match(/router-id\s+([\d.]+)/i);
|
|
15092
|
-
if (match) {
|
|
15093
|
-
return match[1];
|
|
15094
|
-
}
|
|
15095
|
-
}
|
|
15096
|
-
return void 0;
|
|
15097
|
-
};
|
|
15098
|
-
|
|
15099
14845
|
// ../rules-default/src/nokia/sros-rules.ts
|
|
15100
14846
|
var SystemNameRequired = {
|
|
15101
14847
|
id: "NOKIA-SYS-001",
|
|
@@ -15109,7 +14855,7 @@ var SystemNameRequired = {
|
|
|
15109
14855
|
remediation: 'Configure system name using: system > name "<hostname>"'
|
|
15110
14856
|
},
|
|
15111
14857
|
check: (node) => {
|
|
15112
|
-
const name =
|
|
14858
|
+
const name = getSystemName(node);
|
|
15113
14859
|
if (!name || name.length === 0) {
|
|
15114
14860
|
return {
|
|
15115
14861
|
passed: false,
|
|
@@ -15143,7 +14889,7 @@ var PortDescriptionRequired = {
|
|
|
15143
14889
|
},
|
|
15144
14890
|
check: (node) => {
|
|
15145
14891
|
const portName = node.id.replace(/^port\s+/i, "").trim();
|
|
15146
|
-
if (!
|
|
14892
|
+
if (!isPhysicalPort3(portName)) {
|
|
15147
14893
|
return {
|
|
15148
14894
|
passed: true,
|
|
15149
14895
|
message: "Not a physical port, description optional.",
|
|
@@ -15153,7 +14899,7 @@ var PortDescriptionRequired = {
|
|
|
15153
14899
|
loc: node.loc
|
|
15154
14900
|
};
|
|
15155
14901
|
}
|
|
15156
|
-
if (
|
|
14902
|
+
if (isAdminStateDisabled(node)) {
|
|
15157
14903
|
return {
|
|
15158
14904
|
passed: true,
|
|
15159
14905
|
message: "Port is disabled, description optional.",
|
|
@@ -15163,7 +14909,7 @@ var PortDescriptionRequired = {
|
|
|
15163
14909
|
loc: node.loc
|
|
15164
14910
|
};
|
|
15165
14911
|
}
|
|
15166
|
-
if (!
|
|
14912
|
+
if (!hasDescription4(node)) {
|
|
15167
14913
|
return {
|
|
15168
14914
|
passed: false,
|
|
15169
14915
|
message: `Port ${portName} is enabled but has no description.`,
|
|
@@ -15175,7 +14921,7 @@ var PortDescriptionRequired = {
|
|
|
15175
14921
|
}
|
|
15176
14922
|
return {
|
|
15177
14923
|
passed: true,
|
|
15178
|
-
message: `Port ${portName} has description: ${
|
|
14924
|
+
message: `Port ${portName} has description: ${getDescription3(node)}`,
|
|
15179
14925
|
ruleId: "NOKIA-PORT-001",
|
|
15180
14926
|
nodeId: node.id,
|
|
15181
14927
|
level: "info",
|
|
@@ -15195,7 +14941,7 @@ var BgpRouterIdRequired = {
|
|
|
15195
14941
|
remediation: "Configure BGP router-id: bgp > router-id <ip-address>"
|
|
15196
14942
|
},
|
|
15197
14943
|
check: (node) => {
|
|
15198
|
-
if (
|
|
14944
|
+
if (isAdminStateDisabled(node)) {
|
|
15199
14945
|
return {
|
|
15200
14946
|
passed: true,
|
|
15201
14947
|
message: "BGP is disabled.",
|
|
@@ -15205,7 +14951,7 @@ var BgpRouterIdRequired = {
|
|
|
15205
14951
|
loc: node.loc
|
|
15206
14952
|
};
|
|
15207
14953
|
}
|
|
15208
|
-
if (!
|
|
14954
|
+
if (!hasBgpRouterId2(node)) {
|
|
15209
14955
|
return {
|
|
15210
14956
|
passed: false,
|
|
15211
14957
|
message: "BGP router-id is not configured. Configure a stable router-id.",
|
|
@@ -15217,7 +14963,7 @@ var BgpRouterIdRequired = {
|
|
|
15217
14963
|
}
|
|
15218
14964
|
return {
|
|
15219
14965
|
passed: true,
|
|
15220
|
-
message: `BGP router-id is configured: ${
|
|
14966
|
+
message: `BGP router-id is configured: ${getBgpRouterId(node)}`,
|
|
15221
14967
|
ruleId: "NOKIA-BGP-001",
|
|
15222
14968
|
nodeId: node.id,
|
|
15223
14969
|
level: "info",
|
|
@@ -15239,40 +14985,6 @@ function getRulesByNokiaVendor() {
|
|
|
15239
14985
|
return [...allCommonRules, ...allNokiaRules];
|
|
15240
14986
|
}
|
|
15241
14987
|
|
|
15242
|
-
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cumulus/helpers.ts
|
|
15243
|
-
var isSwitchPort2 = (interfaceName) => {
|
|
15244
|
-
const name = interfaceName.toLowerCase();
|
|
15245
|
-
return /swp\d+/.test(name);
|
|
15246
|
-
};
|
|
15247
|
-
var isBridgeInterface4 = (interfaceName) => {
|
|
15248
|
-
const name = interfaceName.toLowerCase();
|
|
15249
|
-
return name.includes("bridge") || name === "br_default" || /^br\d+$/.test(name);
|
|
15250
|
-
};
|
|
15251
|
-
var isVlanAwareBridge2 = (node) => {
|
|
15252
|
-
return node.children.some(
|
|
15253
|
-
(child) => child.id.toLowerCase().includes("bridge-vlan-aware") && child.id.toLowerCase().includes("yes")
|
|
15254
|
-
);
|
|
15255
|
-
};
|
|
15256
|
-
var getInterfaceName6 = (node) => {
|
|
15257
|
-
const parts = node.id.split(/\s+/);
|
|
15258
|
-
return parts[1] || node.id;
|
|
15259
|
-
};
|
|
15260
|
-
var hasDescription8 = (node) => {
|
|
15261
|
-
return node.children.some(
|
|
15262
|
-
(child) => child.id.toLowerCase().startsWith("alias ")
|
|
15263
|
-
);
|
|
15264
|
-
};
|
|
15265
|
-
var hasBridgeVids2 = (node) => {
|
|
15266
|
-
return node.children.some(
|
|
15267
|
-
(child) => child.id.toLowerCase().startsWith("bridge-vids ")
|
|
15268
|
-
);
|
|
15269
|
-
};
|
|
15270
|
-
var hasBgpRouterId4 = (node) => {
|
|
15271
|
-
return node.children.some(
|
|
15272
|
-
(child) => child.id.toLowerCase().startsWith("bgp router-id ")
|
|
15273
|
-
);
|
|
15274
|
-
};
|
|
15275
|
-
|
|
15276
14988
|
// ../rules-default/src/cumulus/cumulus-rules.ts
|
|
15277
14989
|
var CumulusInterfaceDescription = {
|
|
15278
14990
|
id: "CUM-IF-001",
|
|
@@ -15286,8 +14998,8 @@ var CumulusInterfaceDescription = {
|
|
|
15286
14998
|
remediation: 'Add "alias <description>" under the interface stanza.'
|
|
15287
14999
|
},
|
|
15288
15000
|
check: (node) => {
|
|
15289
|
-
const ifaceName =
|
|
15290
|
-
if (!
|
|
15001
|
+
const ifaceName = getInterfaceName2(node);
|
|
15002
|
+
if (!isSwitchPort(ifaceName)) {
|
|
15291
15003
|
return {
|
|
15292
15004
|
passed: true,
|
|
15293
15005
|
message: "Not a switch port interface.",
|
|
@@ -15297,7 +15009,7 @@ var CumulusInterfaceDescription = {
|
|
|
15297
15009
|
loc: node.loc
|
|
15298
15010
|
};
|
|
15299
15011
|
}
|
|
15300
|
-
if (!
|
|
15012
|
+
if (!hasDescription2(node)) {
|
|
15301
15013
|
return {
|
|
15302
15014
|
passed: false,
|
|
15303
15015
|
message: `Switch port "${ifaceName}" missing description (alias).`,
|
|
@@ -15329,8 +15041,8 @@ var CumulusBridgeVlans = {
|
|
|
15329
15041
|
remediation: 'Add "bridge-vids <vlan-ids>" to define allowed VLANs on the bridge.'
|
|
15330
15042
|
},
|
|
15331
15043
|
check: (node) => {
|
|
15332
|
-
const ifaceName =
|
|
15333
|
-
if (!
|
|
15044
|
+
const ifaceName = getInterfaceName2(node);
|
|
15045
|
+
if (!isBridgeInterface(ifaceName)) {
|
|
15334
15046
|
return {
|
|
15335
15047
|
passed: true,
|
|
15336
15048
|
message: "Not a bridge interface.",
|
|
@@ -15340,7 +15052,7 @@ var CumulusBridgeVlans = {
|
|
|
15340
15052
|
loc: node.loc
|
|
15341
15053
|
};
|
|
15342
15054
|
}
|
|
15343
|
-
if (!
|
|
15055
|
+
if (!isVlanAwareBridge(node)) {
|
|
15344
15056
|
return {
|
|
15345
15057
|
passed: true,
|
|
15346
15058
|
message: "Not a VLAN-aware bridge.",
|
|
@@ -15350,7 +15062,7 @@ var CumulusBridgeVlans = {
|
|
|
15350
15062
|
loc: node.loc
|
|
15351
15063
|
};
|
|
15352
15064
|
}
|
|
15353
|
-
if (!
|
|
15065
|
+
if (!hasBridgeVids(node)) {
|
|
15354
15066
|
return {
|
|
15355
15067
|
passed: false,
|
|
15356
15068
|
message: `VLAN-aware bridge "${ifaceName}" has no VLANs (bridge-vids) configured.`,
|
|
@@ -15382,7 +15094,7 @@ var CumulusBgpRouterId = {
|
|
|
15382
15094
|
remediation: 'Add "bgp router-id <ip>" to explicitly set router ID.'
|
|
15383
15095
|
},
|
|
15384
15096
|
check: (node) => {
|
|
15385
|
-
if (!
|
|
15097
|
+
if (!hasBgpRouterId(node)) {
|
|
15386
15098
|
return {
|
|
15387
15099
|
passed: false,
|
|
15388
15100
|
message: "BGP missing explicit router-id configuration.",
|
|
@@ -15418,7 +15130,7 @@ function getRulesByCumulusVendor() {
|
|
|
15418
15130
|
|
|
15419
15131
|
// ../rules-default/src/json/cisco-json-rules.json
|
|
15420
15132
|
var cisco_json_rules_default = {
|
|
15421
|
-
$schema: "https://sentriflow.
|
|
15133
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15422
15134
|
version: "1.0",
|
|
15423
15135
|
meta: {
|
|
15424
15136
|
name: "Cisco IOS JSON Rules",
|
|
@@ -15615,7 +15327,7 @@ var cisco_json_rules_default = {
|
|
|
15615
15327
|
|
|
15616
15328
|
// ../rules-default/src/json/common-json-rules.json
|
|
15617
15329
|
var common_json_rules_default = {
|
|
15618
|
-
$schema: "https://sentriflow.
|
|
15330
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15619
15331
|
version: "1.0",
|
|
15620
15332
|
meta: {
|
|
15621
15333
|
name: "Common JSON Rules",
|
|
@@ -15663,7 +15375,7 @@ var common_json_rules_default = {
|
|
|
15663
15375
|
|
|
15664
15376
|
// ../rules-default/src/json/juniper-json-rules.json
|
|
15665
15377
|
var juniper_json_rules_default = {
|
|
15666
|
-
$schema: "https://sentriflow.
|
|
15378
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15667
15379
|
version: "1.0",
|
|
15668
15380
|
meta: {
|
|
15669
15381
|
name: "Juniper JunOS JSON Rules",
|
|
@@ -16172,6 +15884,9 @@ function isValidSentriflowConfig(config) {
|
|
|
16172
15884
|
return false;
|
|
16173
15885
|
}
|
|
16174
15886
|
}
|
|
15887
|
+
if (obj.filterSpecialIps !== void 0 && typeof obj.filterSpecialIps !== "boolean") {
|
|
15888
|
+
return false;
|
|
15889
|
+
}
|
|
16175
15890
|
return true;
|
|
16176
15891
|
}
|
|
16177
15892
|
function isValidDirectoryConfig(config) {
|
|
@@ -16967,7 +16682,7 @@ function enrichResultsWithRuleMetadata(results, rules) {
|
|
|
16967
16682
|
});
|
|
16968
16683
|
}
|
|
16969
16684
|
var program = new Command();
|
|
16970
|
-
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.
|
|
16685
|
+
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.4.0").argument("[files...]", "Path(s) to configuration file(s) (supports multiple files)").option("--ast", "Output the AST instead of rule results").option("-f, --format <format>", "Output format (json, sarif)", "json").option("-q, --quiet", "Only output failures (suppress passed results)").option("-c, --config <path>", "Path to config file (default: auto-detect)").option("--no-config", "Ignore config file").option("-r, --rules <path>", "Additional rules file to load (legacy)").option(
|
|
16971
16686
|
"--pack <path...>",
|
|
16972
16687
|
"Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
|
|
16973
16688
|
).option(
|
|
@@ -17025,7 +16740,10 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
|
|
|
17025
16740
|
"--max-depth <number>",
|
|
17026
16741
|
"Maximum recursion depth for directory scanning (use with -R)",
|
|
17027
16742
|
(val) => parseInt(val, 10)
|
|
17028
|
-
).option("--progress", "Show progress during directory scanning").
|
|
16743
|
+
).option("--progress", "Show progress during directory scanning").option(
|
|
16744
|
+
"--filter-special-ips",
|
|
16745
|
+
"Filter out special IP ranges (loopback, multicast, reserved, broadcast) from IP summary"
|
|
16746
|
+
).action(async (files, options) => {
|
|
17029
16747
|
try {
|
|
17030
16748
|
if (options.showMachineId) {
|
|
17031
16749
|
try {
|
|
@@ -17099,6 +16817,19 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
17099
16817
|
allowedBaseDirs
|
|
17100
16818
|
// SEC-011: Pass allowed base dirs for rule file validation
|
|
17101
16819
|
});
|
|
16820
|
+
let filterSpecialIps = options.filterSpecialIps ?? false;
|
|
16821
|
+
if (!options.filterSpecialIps && options.config !== false) {
|
|
16822
|
+
const configPath = options.config ?? findConfigFile(configSearchDir);
|
|
16823
|
+
if (configPath) {
|
|
16824
|
+
try {
|
|
16825
|
+
const config = await loadConfigFile(configPath, allowedBaseDirs);
|
|
16826
|
+
if (config.filterSpecialIps) {
|
|
16827
|
+
filterSpecialIps = true;
|
|
16828
|
+
}
|
|
16829
|
+
} catch {
|
|
16830
|
+
}
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
17102
16833
|
if (options.listCategories) {
|
|
17103
16834
|
const counts = /* @__PURE__ */ new Map();
|
|
17104
16835
|
for (const rule of rules) {
|
|
@@ -17338,7 +17069,21 @@ Parsing complete: ${allAsts.length} files`);
|
|
|
17338
17069
|
const passed = results2.filter((r) => r.passed).length;
|
|
17339
17070
|
totalFailures += failures;
|
|
17340
17071
|
totalPassed += passed;
|
|
17341
|
-
|
|
17072
|
+
let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17073
|
+
if (filterSpecialIps) {
|
|
17074
|
+
fileIpSummary = filterIPSummary(fileIpSummary, {
|
|
17075
|
+
keepPublic: true,
|
|
17076
|
+
keepPrivate: true,
|
|
17077
|
+
keepCgnat: true,
|
|
17078
|
+
keepLoopback: false,
|
|
17079
|
+
keepLinkLocal: false,
|
|
17080
|
+
keepMulticast: false,
|
|
17081
|
+
keepReserved: false,
|
|
17082
|
+
keepUnspecified: false,
|
|
17083
|
+
keepBroadcast: false,
|
|
17084
|
+
keepDocumentation: false
|
|
17085
|
+
});
|
|
17086
|
+
}
|
|
17342
17087
|
allFileResults.push({
|
|
17343
17088
|
filePath: filePath2,
|
|
17344
17089
|
results: results2,
|
|
@@ -17454,6 +17199,20 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17454
17199
|
let stdinIpSummary;
|
|
17455
17200
|
try {
|
|
17456
17201
|
stdinIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17202
|
+
if (filterSpecialIps) {
|
|
17203
|
+
stdinIpSummary = filterIPSummary(stdinIpSummary, {
|
|
17204
|
+
keepPublic: true,
|
|
17205
|
+
keepPrivate: true,
|
|
17206
|
+
keepCgnat: true,
|
|
17207
|
+
keepLoopback: false,
|
|
17208
|
+
keepLinkLocal: false,
|
|
17209
|
+
keepMulticast: false,
|
|
17210
|
+
keepReserved: false,
|
|
17211
|
+
keepUnspecified: false,
|
|
17212
|
+
keepBroadcast: false,
|
|
17213
|
+
keepDocumentation: false
|
|
17214
|
+
});
|
|
17215
|
+
}
|
|
17457
17216
|
} catch (error) {
|
|
17458
17217
|
if (error instanceof InputValidationError) {
|
|
17459
17218
|
console.error(`Input validation error: ${error.message}`);
|
|
@@ -17531,7 +17290,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17531
17290
|
const passed = results2.filter((r) => r.passed).length;
|
|
17532
17291
|
totalFailures += failures;
|
|
17533
17292
|
totalPassed += passed;
|
|
17534
|
-
|
|
17293
|
+
let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17294
|
+
if (filterSpecialIps) {
|
|
17295
|
+
fileIpSummary = filterIPSummary(fileIpSummary, {
|
|
17296
|
+
keepPublic: true,
|
|
17297
|
+
keepPrivate: true,
|
|
17298
|
+
keepCgnat: true,
|
|
17299
|
+
keepLoopback: false,
|
|
17300
|
+
keepLinkLocal: false,
|
|
17301
|
+
keepMulticast: false,
|
|
17302
|
+
keepReserved: false,
|
|
17303
|
+
keepUnspecified: false,
|
|
17304
|
+
keepBroadcast: false,
|
|
17305
|
+
keepDocumentation: false
|
|
17306
|
+
});
|
|
17307
|
+
}
|
|
17535
17308
|
allFileResults.push({
|
|
17536
17309
|
filePath: filePath2,
|
|
17537
17310
|
results: results2,
|
|
@@ -17647,7 +17420,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17647
17420
|
if (options.quiet) {
|
|
17648
17421
|
results = results.filter((r) => !r.passed);
|
|
17649
17422
|
}
|
|
17650
|
-
|
|
17423
|
+
let ipSummary = extractIPSummary(content, { includeSubnetNetworks: true });
|
|
17424
|
+
if (filterSpecialIps) {
|
|
17425
|
+
ipSummary = filterIPSummary(ipSummary, {
|
|
17426
|
+
keepPublic: true,
|
|
17427
|
+
keepPrivate: true,
|
|
17428
|
+
keepCgnat: true,
|
|
17429
|
+
keepLoopback: false,
|
|
17430
|
+
keepLinkLocal: false,
|
|
17431
|
+
keepMulticast: false,
|
|
17432
|
+
keepReserved: false,
|
|
17433
|
+
keepUnspecified: false,
|
|
17434
|
+
keepBroadcast: false,
|
|
17435
|
+
keepDocumentation: false
|
|
17436
|
+
});
|
|
17437
|
+
}
|
|
17651
17438
|
if (options.format === "sarif") {
|
|
17652
17439
|
const sarifOptions = {
|
|
17653
17440
|
relativePaths: options.relativePaths,
|
|
@@ -17693,6 +17480,23 @@ async function loadLicensingExtension() {
|
|
|
17693
17480
|
licensing.registerCommands(program);
|
|
17694
17481
|
}
|
|
17695
17482
|
} catch {
|
|
17483
|
+
const licensingMessage = `
|
|
17484
|
+
Cloud licensing features require the @sentriflow/licensing package.
|
|
17485
|
+
|
|
17486
|
+
This package is provided to customers after purchasing a license.
|
|
17487
|
+
Visit https://sentriflow.com.au/pricing for more information.
|
|
17488
|
+
|
|
17489
|
+
Once you have a license, you'll receive access to the private package
|
|
17490
|
+
and can enable cloud features like pack downloads and license activation.
|
|
17491
|
+
`.trim();
|
|
17492
|
+
const fallbackAction = () => {
|
|
17493
|
+
console.log(licensingMessage);
|
|
17494
|
+
process.exit(0);
|
|
17495
|
+
};
|
|
17496
|
+
program.command("activate").description("Activate your SentriFlow license (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17497
|
+
program.command("update").description("Check and download pack updates (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17498
|
+
program.command("offline").description("Manage offline bundles (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17499
|
+
program.command("license").description("Show license status (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17696
17500
|
}
|
|
17697
17501
|
}
|
|
17698
17502
|
loadLicensingExtension().finally(() => {
|