snyk
Version:
snyk library and cli utility
1,184 lines (1,102 loc) • 4.49 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 }));
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";