UNPKG

snyk

Version:

snyk library and cli utility

1,139 lines (1,071 loc) • 8.44 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 })); const Debug = __webpack_require__(15158); const os_1 = __webpack_require__(12087); const cloneDeep = __webpack_require__(83465); const assign = __webpack_require__(31730); const chalk_1 = __webpack_require__(32589); const errors_1 = __webpack_require__(55191); const types_1 = __webpack_require__(55246); const iac_test_result_1 = __webpack_require__(46816); const formatters_1 = __webpack_require__(81329); const utils = __webpack_require__(77978); const iac_output_1 = __webpack_require__(68145); const ecosystems_1 = __webpack_require__(5168); const vuln_helpers_1 = __webpack_require__(14784); const format_test_results_1 = __webpack_require__(59744); const local_execution_1 = __webpack_require__(89627); 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 format_test_error_1 = __webpack_require__(68214); const display_result_1 = __webpack_require__(89667); const analytics = __webpack_require__(82744); const protect_update_notification_1 = __webpack_require__(79304); const spotlight_vuln_notification_1 = __webpack_require__(24083); const assert_iac_options_flag_1 = __webpack_require__(33111); const assert_iac_options_flag_2 = __webpack_require__(33111); const feature_flags_1 = __webpack_require__(63011); const iac_output_2 = __webpack_require__(68145); const debug = Debug('snyk-test'); const SEPARATOR = '\n-------------------------------------------------------\n'; // TODO: avoid using `as any` whenever it's possible async function default_1(...args) { var _a, _b, _c, _d; 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 packageJsonPathsWithSnykDepForProtect = protect_update_notification_1.getPackageJsonPathsContainingSnykDependency(options.file, paths); analytics.add('upgradable-snyk-protect-paths', packageJsonPathsWithSnykDepForProtect.length); // Handles no image arg provided to the container command until // a validation interface is implemented in the docker plugin. if (options.docker && paths.length === 0) { throw new errors_1.MissingArgError(); } const ecosystem = ecosystems_1.getEcosystemForTest(options); if (ecosystem) { try { const commandResult = await ecosystems_1.testEcosystem(ecosystem, paths, options); return commandResult; } catch (error) { if (error instanceof Error) { throw error; } else { throw new Error(String(error)); } } } const resultOptions = []; const results = []; // Holds an array of scanned file metadata for output. let iacScanFailures; let iacIgnoredIssuesCount = 0; let iacOutputMeta; const isNewIacOutputSupported = await feature_flags_1.hasFeatureFlag('iacCliOutput', options); if (shouldPrintIacInitialMessage(options, isNewIacOutputSupported)) { console.log(os_1.EOL + chalk_1.default.bold('Snyk testing Infrastructure as Code configuration issues...')); } // Promise waterfall to test all other paths sequentially for (const path of paths) { // Create a copy of the options so a specific test can // modify them i.e. add `options.file` etc. We'll need // these options later. const testOpts = cloneDeep(options); testOpts.path = path; testOpts.projectName = testOpts['project-name']; let res; try { assert_iac_options_flag_2.assertIaCOptionsFlags(process.argv); const { results, failures, ignoreCount } = await local_execution_1.test(path, testOpts); iacOutputMeta = { orgName: (_a = results[0]) === null || _a === void 0 ? void 0 : _a.org, projectName: (_b = results[0]) === null || _b === void 0 ? void 0 : _b.projectName, gitRemoteUrl: (_d = (_c = results[0]) === null || _c === void 0 ? void 0 : _c.meta) === null || _d === void 0 ? void 0 : _d.gitRemoteUrl, }; res = results; iacScanFailures = failures; iacIgnoredIssuesCount += ignoreCount; } catch (error) { // not throwing here but instead returning error response // for legacy flow reasons. res = format_test_error_1.formatTestError(error); } // Not all test results are arrays in order to be backwards compatible // with scripts that use a callback with test. Coerce results/errors to be arrays // and add the result options to each to be displayed const resArray = Array.isArray(res) ? res : [res]; for (let i = 0; i < resArray.length; i++) { const pathWithOptionalProjectName = utils.getPathWithOptionalProjectName(path, resArray[i]); results.push(assign(resArray[i], { path: pathWithOptionalProjectName })); // currently testOpts are identical for each test result returned even if it's for multiple projects. // we want to return the project names, so will need to be crafty in a way that makes sense. if (!testOpts.projectNames) { resultOptions.push(testOpts); } else { resultOptions.push(assign(cloneDeep(testOpts), { projectName: testOpts.projectNames[i], })); } } } const vulnerableResults = results.filter((res) => (res.vulnerabilities && res.vulnerabilities.length) || (res.result && res.result.cloudConfigResults && res.result.cloudConfigResults.length)); const errorResults = results.filter((res) => res instanceof Error); const notSuccess = errorResults.length > 0; const foundVulnerabilities = vulnerableResults.length > 0; // resultOptions is now an array of 1 or more options used for // the tests results is now an array of 1 or more test results // values depend on `options.json` value - string or object const mappedResults = results.map(iac_test_result_1.mapIacTestResult); const { stdout: dataToSend, stringifiedData, stringifiedJsonData, stringifiedSarifData, } = format_test_results_1.extractDataToSendFromResults(results, mappedResults, options); if (options.json || options.sarif) { // if all results are ok (.ok == true) if (mappedResults.every((res) => res.ok)) { return types_1.TestCommandResult.createJsonTestCommandResult(stringifiedData, stringifiedJsonData, stringifiedSarifData); } const err = new Error(stringifiedData); if (foundVulnerabilities) { if (options.failOn) { const fail = shouldFail(vulnerableResults, options.failOn); if (!fail) { // return here to prevent failure return types_1.TestCommandResult.createJsonTestCommandResult(stringifiedData, stringifiedJsonData, stringifiedSarifData); } } err.code = 'VULNS'; const dataToSendNoVulns = dataToSend; delete dataToSendNoVulns.vulnerabilities; err.jsonNoVulns = dataToSendNoVulns; } if (notSuccess) { // Take the code of the first problem to go through error // translation. // Note: this is done based on the logic done below // for non-json/sarif outputs, where we take the code of // the first error. err.code = errorResults[0].code; } err.json = stringifiedData; err.jsonStringifiedResults = stringifiedJsonData; err.sarifStringifiedResults = stringifiedSarifData; throw err; } let response = ''; if (isNewIacOutputSupported && !notSuccess) { response += iac_output_2.getIacDisplayedIssues(results, iacOutputMeta); } else { response += results .map((result, i) => { return display_result_1.displayResult(results[i], { ...resultOptions[i], }, result.foundProjectCount); }) .join(`\n${SEPARATOR}`); } if (notSuccess) { debug(`Failed to test ${errorResults.length} projects, errors:`); errorResults.forEach((err) => { const errString = err.stack ? err.stack.toString() : err.toString(); debug('error: %s', errString); }); } let summaryMessage = ''; let errorResultsLength = errorResults.length; if (iacScanFailures) { errorResultsLength = iacScanFailures.length || errorResults.length; for (const reason of iacScanFailures) { response += chalk_1.default.bold.red(iac_output_1.getIacDisplayErrorFileOutput(reason, isNewIacOutputSupported)); } } if (iacOutputMeta && isNewIacOutputSupported) { response += `${os_1.EOL}${SEPARATOR}${os_1.EOL}`; const iacTestSummary = `${iac_output_2.formatIacTestSummary({ results, ignoreCount: iacIgnoredIssuesCount, }, iacOutputMeta)}`; response += iacTestSummary; } if (results.length > 1) { const projects = results.length === 1 ? 'project' : 'projects'; summaryMessage = `\n\n\nTested ${results.length} ${projects}` + formatters_1.summariseVulnerableResults(vulnerableResults, options) + formatters_1.summariseErrorResults(errorResultsLength) + '\n'; } if (notSuccess) { response += chalk_1.default.bold.red(summaryMessage); const error = new Error(response); // take the code of the first problem to go through error // translation // HACK as there can be different errors, and we pass only the // first one error.code = errorResults[0].code; error.userMessage = errorResults[0].userMessage; error.strCode = errorResults[0].strCode; throw error; } if (foundVulnerabilities) { if (options.failOn) { const fail = shouldFail(vulnerableResults, options.failOn); if (!fail) { // return here to prevent throwing failure response += chalk_1.default.bold.green(summaryMessage); response += os_1.EOL + os_1.EOL; response += protect_update_notification_1.getProtectUpgradeWarningForPaths(packageJsonPathsWithSnykDepForProtect); return types_1.TestCommandResult.createHumanReadableTestCommandResult(response, stringifiedJsonData, stringifiedSarifData); } } response += chalk_1.default.bold.red(summaryMessage); response += os_1.EOL + os_1.EOL; const foundSpotlightVulnIds = spotlight_vuln_notification_1.containsSpotlightVulnIds(results); const spotlightVulnsMsg = spotlight_vuln_notification_1.notificationForSpotlightVulns(foundSpotlightVulnIds); response += spotlightVulnsMsg; if (assert_iac_options_flag_1.isIacShareResultsOptions(options)) { response += chalk_1.default.bold.white(iac_output_1.shareResultsOutput(iacOutputMeta)) + os_1.EOL; } const error = new Error(response); // take the code of the first problem to go through error // translation // HACK as there can be different errors, and we pass only the // first one error.code = vulnerableResults[0].code || 'VULNS'; error.userMessage = vulnerableResults[0].userMessage; error.jsonStringifiedResults = stringifiedJsonData; error.sarifStringifiedResults = stringifiedSarifData; throw error; } response += chalk_1.default.bold.green(summaryMessage); response += os_1.EOL + os_1.EOL; response += protect_update_notification_1.getProtectUpgradeWarningForPaths(packageJsonPathsWithSnykDepForProtect); if (assert_iac_options_flag_1.isIacShareResultsOptions(options)) { response += chalk_1.default.bold.white(iac_output_1.shareResultsOutput(iacOutputMeta)) + os_1.EOL; } return types_1.TestCommandResult.createHumanReadableTestCommandResult(response, stringifiedJsonData, stringifiedSarifData); } exports.default = default_1; function shouldFail(vulnerableResults, failOn) { // find reasons not to fail if (failOn === 'all') { return vuln_helpers_1.hasFixes(vulnerableResults); } if (failOn === 'upgradable') { return vuln_helpers_1.hasUpgrades(vulnerableResults); } if (failOn === 'patchable') { return vuln_helpers_1.hasPatches(vulnerableResults); } // should fail by default when there are vulnerable results return vulnerableResults.length > 0; } function shouldPrintIacInitialMessage(options, iacCliOutputFeatureFlag) { return !options.json && !options.sarif && iacCliOutputFeatureFlag; } /***/ }), /***/ 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.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 = {}, isTFVarSupportEnabled = false) { let tfFileData = []; let nonTfFileData = []; if (!isTFVarSupportEnabled) { nonTfFileData = filesData; } else { 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); } } case 'tf': return terraform_file_parser_1.tryParsingTerraformFile(fileData); 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.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); async function scanFiles(parsedFiles) { // TODO: gracefully handle failed scans const scanResults = []; for (const parsedFile of parsedFiles) { const policyEngine = await getPolicyEngine(parsedFile.engineType); const result = policyEngine.scanFile(parsedFile); scanResults.push(result); } return scanResults; } exports.scanFiles = scanFiles; async function getPolicyEngine(engineType) { if (policyEngineCache[engineType]) { return policyEngineCache[engineType]; } policyEngineCache[engineType] = await buildPolicyEngine(engineType); return policyEngineCache[engineType]; } // 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.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 unsupported_entitlement_error_1 = __webpack_require__(78673); const config_1 = __webpack_require__(22541); const policy_1 = __webpack_require__(32615); const feature_flags_1 = __webpack_require__(63011); const rules_1 = __webpack_require__(75516); const file_loader_1 = __webpack_require__(62201); const process_results_1 = __webpack_require__(1229); 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 assert_iac_options_flag_1 = __webpack_require__(33111); // 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(pathToScan, options) { var _a, _b; try { 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'); } // 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 rulesOrigin = await rules_1.initRules(iacOrgSettings, options); const policy = await policy_1.findAndLoadPolicy(pathToScan, 'iac', options); const isTFVarSupportEnabled = (await feature_flags_1.isFeatureFlagSupportedForOrg('iacTerraformVarSupport', iacOrgSettings.meta.org)).ok; 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, isTFVarSupportEnabled)) { 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, isTFVarSupportEnabled); allParsedFiles = allParsedFiles.concat(parsedFiles); allFailedFiles = allFailedFiles.concat(failedFiles); } // if none of the files were parsed then either we didn't have any IaC files // or there was only one file passed via the CLI and it failed parsing if (allParsedFiles.length === 0) { if (allFailedFiles.length === 1) { throw allFailedFiles[0].err; } else { throw new file_loader_1.NoFilesToScanError(); } } // 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, }))); } const scannedFiles = await measurable_methods_1.scanFiles(allParsedFiles); const resultsWithCustomSeverities = await measurable_methods_1.applyCustomSeverities(scannedFiles, iacOrgSettings.customPolicies); const { filteredIssues, ignoreCount } = await process_results_1.processResults(resultsWithCustomSeverities, orgPublicId, iacOrgSettings, policy, tags, attributes, options); try { await measurable_methods_1.trackUsage(filteredIssues); } 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, // NOTE: No file or parsed file data should leave this function. failures: detect_1.isLocalFolder(pathToScan) ? allFailedFiles.map(removeFileContent) : undefined, ignoreCount, }; } finally { measurable_methods_1.cleanLocalCache(); } } 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); } } function parseAttributes(options) { if (options.report) { return monitor_1.generateProjectAttributes(options); } } function shouldLoadVarDefinitionsFile(options, isTFVarSupportEnabled = false) { if (options['var-file']) { if (!isTFVarSupportEnabled) { throw new assert_iac_options_flag_1.FeatureFlagError('var-file', 'iacTerraformVarSupport'); } 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__(584); 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; /***/ }), /***/ 97641: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); // This artifact was generated using GopherJS and https://github.com/tmccombs/hcl2json (version v0.3.1) const gopherJsArtifact = __webpack_require__(99548); function hclToJson(fileContent) { return gopherJsArtifact.hcltojson(fileContent); } exports.default = hclToJson; /***/ }), /***/ 11634: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.FailedToParseTerraformFileError = exports.tryParsingTerraformFile = void 0; const hcl_to_json_1 = __webpack_require__(97641); 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 tryParsingTerraformFile(fileData) { try { return [ { ...fileData, jsonContent: hcl_to_json_1.default(fileData.fileContent), projectType: constants_1.IacProjectType.TERRAFORM, engineType: types_1.EngineType.Terraform, }, ]; } catch (err) { throw new FailedToParseTerraformFileError(fileData.filePath); } } exports.tryParsingTerraformFile = tryParsingTerraformFile; 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.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