UNPKG

snyk

Version:

snyk library and cli utility

1,184 lines (1,102 loc) • 4.49 MB
exports.id = 917; exports.ids = [917]; exports.modules = { /***/ 68214: /***/ ((__unused_webpack_module, exports) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.formatTestError = void 0; function formatTestError(error) { // Possible error cases: // - the test found some vulns. `error.message` is a // JSON-stringified // test result. // - the flow failed, `error` is a real Error object. // - the flow failed, `error` is a number or string // describing the problem. // // To standardise this, make sure we use the best _object_ to // describe the error. let errorResponse; if (error instanceof Error) { errorResponse = error; } else if (typeof error !== 'object') { errorResponse = new Error(error); } else { try { errorResponse = JSON.parse(error.message); } catch (unused) { errorResponse = error; } } return errorResponse; } exports.formatTestError = formatTestError; /***/ }), /***/ 55935: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getFlag = void 0; const validate_credentials_1 = __webpack_require__(4593); const validate_test_options_1 = __webpack_require__(83476); const set_default_test_options_1 = __webpack_require__(13285); const process_command_args_1 = __webpack_require__(52369); const feature_flags_1 = __webpack_require__(63011); const rules_1 = __webpack_require__(95343); const measurable_methods_1 = __webpack_require__(5687); const config_1 = __webpack_require__(25425); const unsupported_entitlement_error_1 = __webpack_require__(78673); const scan_1 = __webpack_require__(71308); const output_1 = __webpack_require__(39313); const assert_iac_options_flag_1 = __webpack_require__(33111); async function default_1(...args) { var _a, _b, _c; const { options: originalOptions, paths } = process_command_args_1.processCommandArgs(...args); const options = set_default_test_options_1.setDefaultTestOptions(originalOptions); validate_test_options_1.validateTestOptions(options); validate_credentials_1.validateCredentials(options); const remoteRepoUrl = getFlag(options, 'remote-repo-url'); const targetName = getFlag(options, 'target-name'); const orgPublicId = (_a = options.org) !== null && _a !== void 0 ? _a : config_1.default.org; const iacOrgSettings = await measurable_methods_1.getIacOrgSettings(orgPublicId); if (!((_b = iacOrgSettings.entitlements) === null || _b === void 0 ? void 0 : _b.infrastructureAsCode)) { throw new unsupported_entitlement_error_1.UnsupportedEntitlementError('infrastructureAsCode'); } const buildOciRegistry = () => rules_1.buildDefaultOciRegistry(iacOrgSettings); const isIacShareCliResultsCustomRulesSupported = Boolean(await feature_flags_1.hasFeatureFlag('iacShareCliResultsCustomRules', options)); const isIacCustomRulesEntitlementEnabled = Boolean((_c = iacOrgSettings.entitlements) === null || _c === void 0 ? void 0 : _c.iacCustomRulesEntitlement); const testSpinner = output_1.buildSpinner(options); const projectRoot = process.cwd(); output_1.printHeader(options); const { iacOutputMeta, iacScanFailures, iacIgnoredIssuesCount, results, } = await scan_1.scan(iacOrgSettings, options, testSpinner, paths, orgPublicId, buildOciRegistry, projectRoot, remoteRepoUrl, targetName); return output_1.buildOutput({ results, options, isIacShareCliResultsCustomRulesSupported, isIacCustomRulesEntitlementEnabled, iacOutputMeta, iacScanFailures, iacIgnoredIssuesCount, testSpinner, }); } exports.default = default_1; function getFlag(options, flag) { const flagValue = options[flag]; if (!flagValue) { return; } // if the user does not provide a value, it will be of boolean type if (typeof flagValue !== 'string') { throw new assert_iac_options_flag_1.InvalidArgumentError(flag); } return flagValue; } exports.getFlag = getFlag; /***/ }), /***/ 80509: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getFileType = exports.shouldBeParsed = exports.getFilesForDirectoryGenerator = exports.getFilesForDirectory = exports.getAllDirectoriesForPath = void 0; const path = __webpack_require__(85622); const file_utils_1 = __webpack_require__(45281); const types_1 = __webpack_require__(94820); const detect_1 = __webpack_require__(45318); /** * Gets all nested directories for the path that we ran a scan. * @param pathToScan - the path to scan provided by the user * @param maxDepth? - An optional `maxDepth` argument can be provided to limit how deep in the file tree the search will go. * @returns {string[]} An array with all the non-empty nested directories in this path */ function getAllDirectoriesForPath(pathToScan, maxDepth) { // if it is a single file (it has an extension), we return the current path if (!detect_1.isLocalFolder(pathToScan)) { return [path.resolve(pathToScan)]; } return [...getAllDirectoriesForPathGenerator(pathToScan, maxDepth)]; } exports.getAllDirectoriesForPath = getAllDirectoriesForPath; /** * Gets all the directories included in this path * @param pathToScan - the path to scan provided by the user * @param maxDepth? - An optional `maxDepth` argument can be provided to limit how deep in the file tree the search will go. * @returns {Generator<string>} - a generator which yields the filepaths for the path to scan */ function* getAllDirectoriesForPathGenerator(pathToScan, maxDepth) { for (const filePath of file_utils_1.makeFileAndDirectoryGenerator(pathToScan, maxDepth)) { if (filePath.directory) yield filePath.directory; } } /** * Gets all file paths for the specific directory * @param pathToScan - the path to scan provided by the user * @param currentDirectory - the directory which we want to return files for * @returns {string[]} An array with all the Terraform filePaths for this directory */ function getFilesForDirectory(pathToScan, currentDirectory) { if (!detect_1.isLocalFolder(pathToScan)) { if (exports.shouldBeParsed(pathToScan) && !isIgnoredFile(pathToScan, currentDirectory)) { return [pathToScan]; } return []; } else { return [...getFilesForDirectoryGenerator(currentDirectory)]; } } exports.getFilesForDirectory = getFilesForDirectory; /** * Iterates through the makeFileAndDirectoryGenerator function and gets all the Terraform files in the specified directory * @param pathToScan - the pathToScan to scan provided by the user * @returns {Generator<string>} - a generator which holds all the filepaths */ function* getFilesForDirectoryGenerator(pathToScan) { for (const filePath of file_utils_1.makeFileAndDirectoryGenerator(pathToScan)) { if (filePath.file && filePath.file.dir !== pathToScan) { // we want to get files that belong just to the current walking directory, not the ones in nested directories continue; } if (filePath.file && exports.shouldBeParsed(filePath.file.fileName) && !isIgnoredFile(filePath.file.fileName, pathToScan)) { yield filePath.file.fileName; } } } exports.getFilesForDirectoryGenerator = getFilesForDirectoryGenerator; exports.shouldBeParsed = (pathToScan) => types_1.VALID_FILE_TYPES.includes(exports.getFileType(pathToScan)); exports.getFileType = (pathToScan) => { const extension = path.extname(pathToScan); if (extension.startsWith('.')) { return extension.substr(1); } return extension; }; /** * Checks if a file should be ignored from loading or not according to the filetype. * We ignore the same files that Terraform ignores. * https://github.com/hashicorp/terraform/blob/dc63fda44b67300d5161dabcd803426d0d2f468e/internal/configs/parser_config_dir.go#L137-L143 * @param {string} pathToScan - The filepath to check * @param {currentDirectory} currentDirectory - The directory for the filepath * @returns {boolean} if the filepath should be ignored or not */ function isIgnoredFile(pathToScan, currentDirectory) { // we resolve the path in case the user tries to scan a single file with a relative path // e.g. './my-folder/terraform.tf', or '../my-folder/terraform.tf' const resolvedPath = path.resolve(currentDirectory, currentDirectory); return (resolvedPath.startsWith('.') || // Unix-like hidden files resolvedPath.startsWith('~') || // vim (resolvedPath.startsWith('#') && resolvedPath.endsWith('#')) // emacs ); } /***/ }), /***/ 62201: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.FailedToLoadFileError = exports.NoFilesToScanError = exports.tryLoadFileData = exports.loadContentForFiles = void 0; const fs_1 = __webpack_require__(35747); const types_1 = __webpack_require__(94820); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); const directory_loader_1 = __webpack_require__(80509); const DEFAULT_ENCODING = 'utf-8'; async function loadContentForFiles(filePaths) { const loadedFiles = await Promise.all(filePaths.map(async (filePath) => { try { return await tryLoadFileData(filePath); } catch (e) { throw new FailedToLoadFileError(filePath); } })); return loadedFiles.filter((file) => file.fileContent !== ''); } exports.loadContentForFiles = loadContentForFiles; async function tryLoadFileData(pathToScan) { const fileType = directory_loader_1.getFileType(pathToScan); const fileContent = removeBom(await fs_1.promises.readFile(pathToScan, DEFAULT_ENCODING)); return { filePath: pathToScan, fileType: fileType, fileContent, }; } exports.tryLoadFileData = tryLoadFileData; function removeBom(s) { if (s.charCodeAt(0) === 0xfeff) { return s.slice(1); } return s; } class NoFilesToScanError extends errors_1.CustomError { constructor(message) { super(message || 'Could not find any valid IaC files'); this.code = types_1.IaCErrorCodes.NoFilesToScanError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = 'Could not find any valid infrastructure as code files. Supported file extensions are tf, yml, yaml & json.\nMore information can be found by running `snyk iac test --help` or through our documentation:\nhttps://support.snyk.io/hc/en-us/articles/360012429477-Test-your-Kubernetes-files-with-our-CLI-tool\nhttps://support.snyk.io/hc/en-us/articles/360013723877-Test-your-Terraform-files-with-our-CLI-tool'; } } exports.NoFilesToScanError = NoFilesToScanError; class FailedToLoadFileError extends errors_1.CustomError { constructor(filename) { super('Failed to load file content'); this.code = types_1.IaCErrorCodes.FailedToLoadFileError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.filename = filename; this.userMessage = `We were unable to read file "${filename}" for scanning. Please ensure that it is readable.`; } } exports.FailedToLoadFileError = FailedToLoadFileError; /***/ }), /***/ 39331: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.UnsupportedFileTypeError = exports.tryParseIacFile = exports.parseTerraformFiles = exports.parseNonTerraformFiles = exports.parseFiles = void 0; const config_type_detection_1 = __webpack_require__(8601); const terraform_file_parser_1 = __webpack_require__(11634); const terraform_plan_parser_1 = __webpack_require__(58540); const types_1 = __webpack_require__(94820); const analytics = __webpack_require__(82744); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); const yaml_parser_1 = __webpack_require__(80039); const hcl_to_json_v2_1 = __webpack_require__(70456); const constants_1 = __webpack_require__(68620); const Debug = __webpack_require__(15158); const debug = Debug('snyk-test'); async function parseFiles(filesData, options = {}) { let tfFileData = []; let nonTfFileData = []; tfFileData = filesData.filter((fileData) => types_1.VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType)); nonTfFileData = filesData.filter((fileData) => !types_1.VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType)); let { parsedFiles, failedFiles } = parseNonTerraformFiles(nonTfFileData, options); if (tfFileData.length > 0) { const { parsedFiles: parsedTfFiles, failedFiles: failedTfFiles, } = parseTerraformFiles(tfFileData); parsedFiles = parsedFiles.concat(parsedTfFiles); failedFiles = failedFiles.concat(failedTfFiles); } return { parsedFiles, failedFiles, }; } exports.parseFiles = parseFiles; function parseNonTerraformFiles(filesData, options) { const parsedFiles = []; const failedFiles = []; for (const fileData of filesData) { try { parsedFiles.push(...tryParseIacFile(fileData, options)); } catch (err) { failedFiles.push(generateFailedParsedFile(fileData, err)); } } return { parsedFiles, failedFiles, }; } exports.parseNonTerraformFiles = parseNonTerraformFiles; function parseTerraformFiles(filesData) { // the parser expects a map of <filePath>:<fileContent> key-value pairs const files = filesData.reduce((map, fileData) => { map[fileData.filePath] = fileData.fileContent; return map; }, {}); const { parsedFiles, failedFiles, debugLogs } = hcl_to_json_v2_1.default(files); const parsingResults = { parsedFiles: [], failedFiles: [], }; for (const fileData of filesData) { if (parsedFiles[fileData.filePath]) { parsingResults.parsedFiles.push({ ...fileData, jsonContent: JSON.parse(parsedFiles[fileData.filePath]), projectType: constants_1.IacProjectType.TERRAFORM, engineType: types_1.EngineType.Terraform, }); } else if (failedFiles[fileData.filePath]) { if (debugLogs[fileData.filePath]) { debug('File %s failed to parse with: %s', fileData.filePath, debugLogs[fileData.filePath]); } parsingResults.failedFiles.push(generateFailedParsedFile(fileData, new terraform_file_parser_1.FailedToParseTerraformFileError(fileData.filePath))); } } return parsingResults; } exports.parseTerraformFiles = parseTerraformFiles; function generateFailedParsedFile({ fileType, filePath, fileContent }, err) { return { err, failureReason: err.message, fileType, filePath, fileContent, engineType: null, jsonContent: null, }; } function tryParseIacFile(fileData, options = {}) { analytics.add('iac-terraform-plan', false); switch (fileData.fileType) { case 'yaml': case 'yml': { const parsedIacFile = yaml_parser_1.parseYAMLOrJSONFileData(fileData); return config_type_detection_1.detectConfigType(fileData, parsedIacFile); } case 'json': { const parsedIacFile = yaml_parser_1.parseYAMLOrJSONFileData(fileData); // the Kubernetes file can have more than one JSON object in it // but the Terraform plan can only have one if (parsedIacFile.length === 1 && terraform_plan_parser_1.isTerraformPlan(parsedIacFile[0])) { analytics.add('iac-terraform-plan', true); return terraform_plan_parser_1.tryParsingTerraformPlan(fileData, parsedIacFile[0], { isFullScan: options.scan === types_1.TerraformPlanScanMode.FullScan, }); } else { return config_type_detection_1.detectConfigType(fileData, parsedIacFile); } } default: throw new UnsupportedFileTypeError(fileData.fileType); } } exports.tryParseIacFile = tryParseIacFile; class UnsupportedFileTypeError extends errors_1.CustomError { constructor(fileType) { super('Unsupported file extension'); this.code = types_1.IaCErrorCodes.UnsupportedFileTypeError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = `Unable to process the file with extension ${fileType}. Supported file extensions are tf, yml, yaml & json.\nMore information can be found by running \`snyk iac test --help\` or through our documentation:\nhttps://support.snyk.io/hc/en-us/articles/360012429477-Test-your-Kubernetes-files-with-our-CLI-tool\nhttps://support.snyk.io/hc/en-us/articles/360013723877-Test-your-Terraform-files-with-our-CLI-tool`; } } exports.UnsupportedFileTypeError = UnsupportedFileTypeError; /***/ }), /***/ 88361: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.FailedToExecutePolicyEngine = exports.FailedToBuildPolicyEngine = exports.clearPolicyEngineCache = exports.validateResultFromCustomRules = exports.scanFiles = void 0; const types_1 = __webpack_require__(94820); const opa_wasm_1 = __webpack_require__(79264); const fs = __webpack_require__(35747); const local_cache_1 = __webpack_require__(50089); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); const common_1 = __webpack_require__(53110); async function scanFiles(parsedFiles) { // TODO: gracefully handle failed scans const scannedFiles = []; let failedScans = []; for (const parsedFile of parsedFiles) { const policyEngine = await getPolicyEngine(parsedFile.engineType); const result = policyEngine.scanFile(parsedFile); if (parsedFile.engineType === types_1.EngineType.Custom) { const { validatedResult, invalidIssues } = validateResultFromCustomRules(result); scannedFiles.push(validatedResult); failedScans = [...failedScans, ...invalidIssues]; } else { scannedFiles.push(result); } } return { scannedFiles, failedScans }; } exports.scanFiles = scanFiles; async function getPolicyEngine(engineType) { if (policyEngineCache[engineType]) { return policyEngineCache[engineType]; } policyEngineCache[engineType] = await buildPolicyEngine(engineType); return policyEngineCache[engineType]; } function validateResultFromCustomRules(result) { const invalidIssues = []; const filteredViolatedPolicies = []; for (const violatedPolicy of result.violatedPolicies) { let failureReason = ''; const invalidSeverity = !common_1.SEVERITIES.find((s) => s.verboseName === violatedPolicy.severity); if (invalidSeverity) { failureReason = `Invalid severity level for custom rule ${violatedPolicy.publicId}. Change to low, medium, high, or critical`; } const invalidLowercasePublicId = violatedPolicy.publicId !== violatedPolicy.publicId.toUpperCase(); if (invalidLowercasePublicId) { failureReason = `Invalid non-uppercase publicId for custom rule ${violatedPolicy.publicId}. Change to ${violatedPolicy.publicId.toUpperCase()}`; } const invalidSnykPublicId = violatedPolicy.publicId.startsWith('SNYK-CC-'); if (invalidSnykPublicId) { failureReason = `Invalid publicId for custom rule ${violatedPolicy.publicId}. Change to a publicId that does not start with SNYK-CC-`; } if (failureReason) { invalidIssues.push({ filePath: result.filePath, fileType: result.fileType, failureReason, }); } else { filteredViolatedPolicies.push(violatedPolicy); } } return { validatedResult: { ...result, violatedPolicies: filteredViolatedPolicies, }, invalidIssues, }; } exports.validateResultFromCustomRules = validateResultFromCustomRules; // used in tests only function clearPolicyEngineCache() { policyEngineCache = { [types_1.EngineType.Kubernetes]: null, [types_1.EngineType.Terraform]: null, [types_1.EngineType.CloudFormation]: null, [types_1.EngineType.ARM]: null, [types_1.EngineType.Custom]: null, }; } exports.clearPolicyEngineCache = clearPolicyEngineCache; let policyEngineCache = { [types_1.EngineType.Kubernetes]: null, [types_1.EngineType.Terraform]: null, [types_1.EngineType.CloudFormation]: null, [types_1.EngineType.ARM]: null, [types_1.EngineType.Custom]: null, }; async function buildPolicyEngine(engineType) { const [policyEngineCoreDataPath, policyEngineMetaDataPath,] = local_cache_1.getLocalCachePath(engineType); try { const wasmFile = fs.readFileSync(policyEngineCoreDataPath); const policyMetaData = fs.readFileSync(policyEngineMetaDataPath); const policyMetadataAsJson = JSON.parse(policyMetaData.toString()); const opaWasmInstance = await opa_wasm_1.loadPolicy(Buffer.from(wasmFile)); opaWasmInstance.setData(policyMetadataAsJson); return new PolicyEngine(opaWasmInstance); } catch (err) { throw new FailedToBuildPolicyEngine(); } } class PolicyEngine { constructor(opaWasmInstance) { this.opaWasmInstance = opaWasmInstance; this.opaWasmInstance = opaWasmInstance; } evaluate(data) { return this.opaWasmInstance.evaluate(data)[0].result; } scanFile(iacFile) { try { const violatedPolicies = this.evaluate(iacFile.jsonContent); return { ...iacFile, violatedPolicies, }; } catch (err) { // TODO: to distinguish between different failure reasons throw new FailedToExecutePolicyEngine(); } } } class FailedToBuildPolicyEngine extends errors_1.CustomError { constructor(message) { super(message || 'Failed to build policy engine'); this.code = types_1.IaCErrorCodes.FailedToBuildPolicyEngine; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = 'We were unable to run the test. Please run the command again with the `-d` flag and contact support@snyk.io with the contents of the output'; } } exports.FailedToBuildPolicyEngine = FailedToBuildPolicyEngine; class FailedToExecutePolicyEngine extends errors_1.CustomError { constructor(message) { super(message || 'Failed to execute policy engine'); this.code = types_1.IaCErrorCodes.FailedToExecutePolicyEngine; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = 'We were unable to run the test. Please run the command again with the `-d` flag and contact support@snyk.io with the contents of the output'; } } exports.FailedToExecutePolicyEngine = FailedToExecutePolicyEngine; /***/ }), /***/ 89627: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.InvalidVarFilePath = exports.parseTags = exports.removeFileContent = exports.test = void 0; const fs_1 = __webpack_require__(35747); const detect_1 = __webpack_require__(45318); const types_1 = __webpack_require__(94820); const analytics_1 = __webpack_require__(41519); const usage_tracking_1 = __webpack_require__(70413); const measurable_methods_1 = __webpack_require__(5687); const policy_1 = __webpack_require__(32615); const monitor_1 = __webpack_require__(3708); const directory_loader_1 = __webpack_require__(80509); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); const file_loader_1 = __webpack_require__(62201); // this method executes the local processing engine and then formats the results to adapt with the CLI output. // this flow is the default GA flow for IAC scanning. async function test(resultsProcessor, pathToScan, options, iacOrgSettings, rulesOrigin) { // Parse tags and attributes right now, so we can exit early if the user // provided invalid values. const tags = parseTags(options); const attributes = parseAttributes(options); const policy = await policy_1.findAndLoadPolicy(pathToScan, 'iac', options); let allParsedFiles = [], allFailedFiles = []; const allDirectories = directory_loader_1.getAllDirectoriesForPath(pathToScan, options.detectionDepth); // we load and parse files directory by directory // because we need all files in the same directory to share the same variable context for Terraform for (const currentDirectory of allDirectories) { const filePathsInDirectory = directory_loader_1.getFilesForDirectory(pathToScan, currentDirectory); if (currentDirectory === pathToScan && shouldLoadVarDefinitionsFile(options)) { const varDefinitionsFilePath = options['var-file']; filePathsInDirectory.push(varDefinitionsFilePath); } const filesToParse = await measurable_methods_1.loadContentForFiles(filePathsInDirectory); const { parsedFiles, failedFiles } = await measurable_methods_1.parseFiles(filesToParse, options); allParsedFiles = allParsedFiles.concat(parsedFiles); allFailedFiles = allFailedFiles.concat(failedFiles); } if (allParsedFiles.length === 0) { if (allFailedFiles.length === 0) { throw new file_loader_1.NoFilesToScanError(); } else { // we throw an array of errors in order to get the path of the files which generated an error throw allFailedFiles.map((f) => f.err); } } // Duplicate all the files and run them through the custom engine. if (rulesOrigin !== types_1.RulesOrigin.Internal) { allParsedFiles.push(...allParsedFiles.map((file) => ({ ...file, engineType: types_1.EngineType.Custom, }))); } // NOTE: No file or parsed file data should leave this function. let failures = detect_1.isLocalFolder(pathToScan) ? allFailedFiles.map(removeFileContent) : []; const { scannedFiles, failedScans } = await measurable_methods_1.scanFiles(allParsedFiles); failures = [...failures, ...failedScans]; const resultsWithCustomSeverities = await measurable_methods_1.applyCustomSeverities(scannedFiles, iacOrgSettings.customPolicies); const { filteredIssues, ignoreCount } = await resultsProcessor.processResults(resultsWithCustomSeverities, policy, tags, attributes); try { await measurable_methods_1.trackUsage(filteredIssues, iacOrgSettings.meta.org); } catch (e) { if (e instanceof usage_tracking_1.TestLimitReachedError) { throw e; } // If something has gone wrong, err on the side of allowing the user to // run their tests by squashing the error. } analytics_1.addIacAnalytics(filteredIssues, { ignoredIssuesCount: ignoreCount, rulesOrigin, }); // TODO: add support for proper typing of old TestResult interface. return { results: filteredIssues, failures, ignoreCount, }; } exports.test = test; function removeFileContent({ filePath, fileType, failureReason, projectType, }) { return { filePath, fileType, failureReason, projectType, }; } exports.removeFileContent = removeFileContent; function parseTags(options) { if (options.report) { return monitor_1.generateTags(options); } } exports.parseTags = parseTags; function parseAttributes(options) { if (options.report) { return monitor_1.generateProjectAttributes(options); } } function shouldLoadVarDefinitionsFile(options) { if (options['var-file']) { if (!fs_1.existsSync(options['var-file'])) { throw new InvalidVarFilePath(options['var-file']); } return true; } return false; } class InvalidVarFilePath extends errors_1.CustomError { constructor(path, message) { super(message || 'Invalid path to variable definitions file'); this.code = types_1.IaCErrorCodes.InvalidVarFilePath; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = `We were unable to locate a variable definitions file at: "${path}". The file at the provided path does not exist`; } } exports.InvalidVarFilePath = InvalidVarFilePath; /***/ }), /***/ 5687: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.pull = exports.localTest = exports.cleanLocalCache = exports.trackUsage = exports.formatScanResults = exports.applyCustomSeverities = exports.getIacOrgSettings = exports.scanFiles = exports.parseFiles = exports.loadContentForFiles = exports.initLocalCache = exports.performanceAnalyticsDecorator = exports.asyncPerformanceAnalyticsDecorator = void 0; const file_parser_1 = __webpack_require__(39331); const file_scanner_1 = __webpack_require__(88361); const results_formatter_1 = __webpack_require__(11059); const usage_tracking_1 = __webpack_require__(70413); const local_cache_1 = __webpack_require__(50089); const apply_custom_severities_1 = __webpack_require__(71632); const get_iac_org_settings_1 = __webpack_require__(11693); const index_1 = __webpack_require__(89627); const oci_pull_1 = __webpack_require__(166); const analytics_1 = __webpack_require__(41519); const types_1 = __webpack_require__(94820); const file_loader_1 = __webpack_require__(62201); // Note: The return type of the returned async function needs to be Promise<Val> for // the compiler to be happy, so we need to unwrap it with the messy // Awaiter<ReturnType<T>> rather than just using ReturnType<T> directly. function asyncPerformanceAnalyticsDecorator(measurableMethod, analyticsKey) { return async function (...args) { const startTime = Date.now(); const returnValue = await measurableMethod(...args); const durationMs = Date.now() - startTime; analytics_1.performanceAnalyticsObject[analyticsKey] = durationMs; return returnValue; }; } exports.asyncPerformanceAnalyticsDecorator = asyncPerformanceAnalyticsDecorator; function performanceAnalyticsDecorator(measurableMethod, analyticsKey) { return function (...args) { const startTime = Date.now(); const returnValue = measurableMethod(...args); const durationMs = Date.now() - startTime; analytics_1.performanceAnalyticsObject[analyticsKey] = durationMs; return returnValue; }; } exports.performanceAnalyticsDecorator = performanceAnalyticsDecorator; const measurableInitLocalCache = asyncPerformanceAnalyticsDecorator(local_cache_1.initLocalCache, types_1.PerformanceAnalyticsKey.InitLocalCache); exports.initLocalCache = measurableInitLocalCache; const measurableParseFiles = asyncPerformanceAnalyticsDecorator(file_parser_1.parseFiles, types_1.PerformanceAnalyticsKey.FileParsing); exports.parseFiles = measurableParseFiles; const measurableloadContentForFiles = asyncPerformanceAnalyticsDecorator(file_loader_1.loadContentForFiles, types_1.PerformanceAnalyticsKey.FileLoading); exports.loadContentForFiles = measurableloadContentForFiles; const measurableScanFiles = asyncPerformanceAnalyticsDecorator(file_scanner_1.scanFiles, types_1.PerformanceAnalyticsKey.FileScanning); exports.scanFiles = measurableScanFiles; const measurableGetIacOrgSettings = asyncPerformanceAnalyticsDecorator(get_iac_org_settings_1.getIacOrgSettings, types_1.PerformanceAnalyticsKey.OrgSettings); exports.getIacOrgSettings = measurableGetIacOrgSettings; const measurableApplyCustomSeverities = asyncPerformanceAnalyticsDecorator(apply_custom_severities_1.applyCustomSeverities, types_1.PerformanceAnalyticsKey.CustomSeverities); exports.applyCustomSeverities = measurableApplyCustomSeverities; const measurableCleanLocalCache = performanceAnalyticsDecorator(local_cache_1.cleanLocalCache, types_1.PerformanceAnalyticsKey.CacheCleanup); exports.cleanLocalCache = measurableCleanLocalCache; const measurableFormatScanResults = performanceAnalyticsDecorator(results_formatter_1.formatScanResults, types_1.PerformanceAnalyticsKey.ResultFormatting); exports.formatScanResults = measurableFormatScanResults; const measurableTrackUsage = asyncPerformanceAnalyticsDecorator(usage_tracking_1.trackUsage, types_1.PerformanceAnalyticsKey.UsageTracking); exports.trackUsage = measurableTrackUsage; const measurableLocalTest = asyncPerformanceAnalyticsDecorator(index_1.test, types_1.PerformanceAnalyticsKey.Total); exports.localTest = measurableLocalTest; const measurableOciPull = asyncPerformanceAnalyticsDecorator(oci_pull_1.pull, types_1.PerformanceAnalyticsKey.Total); exports.pull = measurableOciPull; /***/ }), /***/ 71632: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.applyCustomSeverities = void 0; const _ = __webpack_require__(96486); async function applyCustomSeverities(scannedFiles, customPolicies) { if (Object.keys(customPolicies).length > 0) { return scannedFiles.map((file) => { const updatedScannedFiles = _.cloneDeep(file); updatedScannedFiles.violatedPolicies.forEach((existingPolicy) => { var _a; const customPolicyForPublicID = customPolicies[existingPolicy.publicId]; if (customPolicyForPublicID) { existingPolicy.severity = (_a = customPolicyForPublicID.severity) !== null && _a !== void 0 ? _a : existingPolicy.severity; } }); return updatedScannedFiles; }); } return scannedFiles; } exports.applyCustomSeverities = applyCustomSeverities; /***/ }), /***/ 8601: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.checkRequiredFieldsMatch = exports.detectConfigType = exports.REQUIRED_ARM_FIELDS = exports.REQUIRED_CLOUDFORMATION_FIELDS = exports.REQUIRED_K8S_FIELDS = void 0; const constants_1 = __webpack_require__(68620); const types_1 = __webpack_require__(94820); exports.REQUIRED_K8S_FIELDS = ['apiVersion', 'kind', 'metadata']; exports.REQUIRED_CLOUDFORMATION_FIELDS = ['Resources']; exports.REQUIRED_ARM_FIELDS = ['$schema', 'contentVersion', 'resources']; function detectConfigType(fileData, parsedIacFiles) { return parsedIacFiles .map((parsedFile, docId) => { if (checkRequiredFieldsMatch(parsedFile, exports.REQUIRED_CLOUDFORMATION_FIELDS)) { return { ...fileData, jsonContent: parsedFile, projectType: constants_1.IacProjectType.CLOUDFORMATION, engineType: types_1.EngineType.CloudFormation, docId: fileData.fileType === 'json' ? undefined : docId, }; } else if (checkRequiredFieldsMatch(parsedFile, exports.REQUIRED_K8S_FIELDS)) { return { ...fileData, jsonContent: parsedFile, projectType: constants_1.IacProjectType.K8S, engineType: types_1.EngineType.Kubernetes, docId: fileData.fileType === 'json' ? undefined : docId, }; } else if (checkRequiredFieldsMatch(parsedFile, exports.REQUIRED_ARM_FIELDS)) { return { ...fileData, jsonContent: parsedFile, projectType: constants_1.IacProjectType.ARM, engineType: types_1.EngineType.ARM, }; } else { return null; } }) .filter((f) => !!f); } exports.detectConfigType = detectConfigType; function checkRequiredFieldsMatch(parsedDocument, requiredFields) { if (!parsedDocument) { return false; } return requiredFields.every((requiredField) => parsedDocument.hasOwnProperty(requiredField)); } exports.checkRequiredFieldsMatch = checkRequiredFieldsMatch; /***/ }), /***/ 70456: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; // This artifact was generated using GopherJS and https://github.com/snyk/snyk-iac-parsers Object.defineProperty(exports, "__esModule", ({ value: true })); const gopherJsArtifact = __webpack_require__(61520); function hclToJsonV2(files) { return gopherJsArtifact.parseModule(files); } exports.default = hclToJsonV2; /***/ }), /***/ 11634: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.FailedToParseTerraformFileError = void 0; const types_1 = __webpack_require__(94820); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); class FailedToParseTerraformFileError extends errors_1.CustomError { constructor(filename) { super('Failed to parse Terraform file'); this.code = types_1.IaCErrorCodes.FailedToParseTerraformFileError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.filename = filename; this.userMessage = `We were unable to parse the Terraform file "${filename}", please ensure it is valid HCL2. This can be done by running it through the 'terraform validate' command.`; } } exports.FailedToParseTerraformFileError = FailedToParseTerraformFileError; /***/ }), /***/ 58540: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.FailedToExtractResourcesInTerraformPlanError = exports.tryParsingTerraformPlan = exports.isTerraformPlan = void 0; const types_1 = __webpack_require__(94820); const errors_1 = __webpack_require__(55191); const error_utils_1 = __webpack_require__(36401); const constants_1 = __webpack_require__(68620); function terraformPlanReducer(scanInput, resource) { // TODO: investigate if this reduction logic covers all edge-cases (nested modules, similar names, etc') const { type, name, mode, index, values } = resource; const inputKey = mode === 'data' ? 'data' : 'resource'; if (scanInput[inputKey][type]) { // add new resources of the same type with different names scanInput[inputKey][type][getResourceName(index, name)] = values || {}; } else { // add a new resource type scanInput[inputKey][type] = { [getResourceName(index, name)]: values }; } return scanInput; } function getExpressions(expressions) { const result = {}; // expressions can be nested. we are only doing 1 depth to resolve top level depenencies for (const key of Object.keys(expressions)) { const referenceKey = getReference(expressions[key]); if (referenceKey) { result[key] = referenceKey; } } return result; } // this is very naive implementation // the referenences can be composed of number of keys // we only going to use the first reference for time being function getReference(value) { var _a; return (_a = value.references) === null || _a === void 0 ? void 0 : _a[0]; } function getResourceName(index, name) { return index !== undefined ? `${name}["${index}"]` : name; } function resourceChangeReducer(scanInput, resource, isFullScan) { // TODO: investigate if we need to address also `after_unknown` field. const { actions, after } = resource.change || { actions: [], after: {} }; if (isValidResourceActions(actions, isFullScan)) { const resourceForReduction = { ...resource, values: after || {} }; return terraformPlanReducer(scanInput, resourceForReduction); } return scanInput; } function isValidResourceActions(action, isFullScan) { const VALID_ACTIONS = isFullScan ? types_1.VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN : types_1.VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN; return VALID_ACTIONS.some((validAction) => { if (action.length !== validAction.length) { return false; } return validAction.every((field, idx) => action[idx] === field); }); } function referencedResourcesResolver(scanInput, resources) { var _a, _b; // check root module for references in first depth of attributes for (const resource of resources) { const { type, name, mode, index, expressions } = resource; // don't care about references in data sources for time being if (mode == 'data') { continue; } const inputKey = 'resource'; // only update the references in resources that have some resolved attributes already const resolvedResource = (_b = (_a = scanInput[inputKey]) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b[getResourceName(index, name)]; if (resolvedResource && expressions) { const resourceExpressions = getExpressions(expressions); for (const key of Object.keys(resourceExpressions)) { // only add non existing attributes. If we already have resolved value do not overwrite it with reference if (!resolvedResource[key]) { resolvedResource[key] = resourceExpressions[key]; } } scanInput[inputKey][type][getResourceName(index, name)] = resolvedResource; } } return scanInput; } function extractResourceChanges(terraformPlanJson) { return terraformPlanJson.resource_changes || []; } function extractReferencedResources(terraformPlanJson) { var _a, _b; return ((_b = (_a = terraformPlanJson.configuration) === null || _a === void 0 ? void 0 : _a.root_module) === null || _b === void 0 ? void 0 : _b.resources) || []; } function extractResourcesForScan(terraformPlanJson, isFullScan = false) { const resourceChanges = extractResourceChanges(terraformPlanJson); const scanInput = resourceChanges.reduce((memo, curr) => resourceChangeReducer(memo, curr, isFullScan), { resource: {}, data: {}, }); const referencedResources = extractReferencedResources(terraformPlanJson); return referencedResourcesResolver(scanInput, referencedResources); } function isTerraformPlan(terraformPlanJson) { const missingRequiredFields = terraformPlanJson.resource_changes === undefined; return !missingRequiredFields; } exports.isTerraformPlan = isTerraformPlan; function tryParsingTerraformPlan(terraformPlanFile, terraformPlanJson, { isFullScan } = { isFullScan: false }) { try { return [ { ...terraformPlanFile, jsonContent: extractResourcesForScan(terraformPlanJson, isFullScan), engineType: types_1.EngineType.Terraform, projectType: constants_1.IacProjectType.TERRAFORM, }, ]; } catch (err) { throw new FailedToExtractResourcesInTerraformPlanError(); } } exports.tryParsingTerraformPlan = tryParsingTerraformPlan; // This error is due to the complex reduction logic, so it catches scenarios we might have not covered. class FailedToExtractResourcesInTerraformPlanError extends errors_1.CustomError { constructor(message) { super(message || 'Failed to extract resources from Terraform plan JSON file'); this.code = types_1.IaCErrorCodes.FailedToExtractResourcesInTerraformPlanError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = 'We failed to extract resource changes from the Terraform plan file, please contact support@snyk.io, if possible with a redacted version of the file'; } } exports.FailedToExtractResourcesInTerraformPlanError = FailedToExtractResourcesInTerraformPlanError; /***/ }), /***/ 40008: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.shareResults = void 0; const config_1 = __webpack_require__(25425); const request_1 = __webpack_require__(52050); const api_token_1 = __webpack_require__(95181); const envelope_formatters_1 = __webpack_require__(88784); const analytics = __webpack_require__(82744); const dev_count_analysis_1 = __webpack_require__(73898); const Debug = __webpack_require__(15158); const errors_1 = __webpack_require__(55191); const usage_tracking_1 = __webpack_require__(70413); const debug = Debug('iac-cli-share-results'); async function shareResults({ results, policy, tags, attributes, options, meta, }) { var _a, _b; const scanResults = results.map((result) => envelope_formatters_1.convertIacResultToScanResult(result, policy, meta, options)); let contributors = []; if (meta.gitRemoteUrl) { if (analytics.allowAnalytics()) { try { contributors = await dev_count_analysis_1.getContributors(); } catch (err) { debug('error getting repo contributors', err); } } } const { res, body } = await request_1.makeRequest({ method: 'POST', url: `${config_1.default.API}/iac-cli-share-results`, json: true, qs: { org: (_a = options === null || options === void 0 ? void 0 : options.org) !== null && _a !== void 0 ? _a : config_1.default.org }, headers: { authorization: api_token_1.getAuthHeader(), }, body: { scanResults, contributors, tags, attributes, }, }); if (res.statusCode === 401) { throw errors_1.AuthFailedError(); } else if (res.statusCode === 429) { throw new usage_tracking_1.TestLimitReachedError(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion } else if (res.statusCode < 200 || res.statusCode > 299) { throw new errors_1.ValidationError((_b = res.body.error) !== null && _b !== void 0 ? _b : 'An error occurred, please contact Snyk support'); } return { projectPublicIds: body, gitRemoteUrl: meta.gitRemoteUrl }; } exports.shareResults = shareResults; /***/ }), /***/ 30537: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.extractLineNumber = exports.getFileTypeForParser = void 0; const types_1 = __webpack_require__(94820); const errors_1 = __webpack_require__(55191); const cloud_config_parser_1 = __webpack_require__(98611); const file_parser_1 = __webpack_require__(39331); const analytics = __webpack_require__(82744); const Debug = __webpack_require__(15158); const error_utils_1 = __webpack_require__(36401); const debug = Debug('iac-extract-line-number'); function getFileTypeForParser(fileType) { switch (fileType) { case 'yaml': case 'yml': return cloud_config_parser_1.CloudConfigFileTypes.YAML; case 'json': return cloud_config_parser_1.CloudConfigFileTypes.JSON; case 'tf': return cloud_config_parser_1.CloudConfigFileTypes.TF; default: throw new file_parser_1.UnsupportedFileTypeError(fileType); } } exports.getFileTypeForParser = getFileTypeForParser; function extractLineNumber(cloudConfigPath, fileType, treeByDocId) { try { return cloud_config_parser_1.getLineNumber(cloudConfigPath, fileType, treeByDocId); } catch { const err = new FailedToExtractLineNumberError(); analytics.add('error-code', err.code); debug('Parser library failed. Could not assign lineNumber to issue'); return -1; } } exports.extractLineNumber = extractLineNumber; class FailedToExtractLineNumberError extends errors_1.CustomError { constructor(message) { super(message || 'Parser library failed. Could not assign lineNumber to issue'); this.code = types_1.IaCErrorCodes.FailedToExtractLineNumberError; this.strCode = error_utils_1.getErrorStringCode(this.code); this.userMessage = ''; // Not a user facing error. } } /***/ }), /***/ 1229: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.SingleGroupResultsProcessor = void 0; const process_results_1 = __webpack_require__(78744); class SingleGroupResultsProcessor { constructor(projectRoot, orgPublicId, iacOrgSettings, options, meta) { this.projectRoot = projectRoot; this.orgPublicId = orgPublicId; this.iacOrgSettings = iacOrgSettings; this.options = options; this.meta = meta; } processResults(resultsWithCustomSeverities, policy, tags, attributes) { return process_results_1.processResults(resultsWithCustomSeverities, this.orgPublicId, this.iacOrgSettings, policy, tags, attributes, this.options, this.projectRoot, this.meta); } } exports.SingleGroupResultsProcessor = SingleGroupResultsProcessor; /***/ }), /***/ 91434: /***/ ((__unused_webpack_module, exports) => { "use strict";