snyk
Version:
snyk library and cli utility
1,139 lines (1,071 loc) • 8.44 MB
JavaScript
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