File: /home/f/r/e/frenchy/www/french-american.org/current/node_modules/snyk-docker-plugin/dist/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const Debug = require("debug");
const path = require("path");
const analyzer = require("./analyzer");
const docker_1 = require("./docker");
const dockerFile = require("./docker-file");
exports.dockerFile = dockerFile;
const response_builder_1 = require("./response-builder");
const debug = Debug("snyk");
function inspect(root, targetFile, options) {
const dockerOptions = options
? {
host: options.host,
tlsverify: options.tlsverify,
tlscert: options.tlscert,
tlscacert: options.tlscacert,
tlskey: options.tlskey,
manifestGlobs: options.manifestGlobs,
}
: {};
const targetImage = root;
return dockerFile
.readDockerfileAndAnalyse(targetFile)
.then((dockerfileAnalysis) => {
return Promise.all([
getRuntime(dockerOptions),
getDependencies(targetImage, dockerfileAnalysis, dockerOptions),
getManifestFiles(targetImage, dockerOptions),
]).then((res) => {
return response_builder_1.buildResponse(res[0], res[1], dockerfileAnalysis, res[2], options);
});
});
}
exports.inspect = inspect;
function getRuntime(options) {
return docker_1.Docker.run(["version"], options)
.then((output) => {
const versionMatch = /Version:\s+(.*)\n/.exec(output.stdout);
if (versionMatch) {
return "docker " + versionMatch[1];
}
return undefined;
})
.catch((error) => {
throw new Error(`Docker error: ${error.stderr}`);
});
}
function handleCommonErrors(error, targetImage) {
if (error.indexOf("command not found") !== -1) {
throw new Error("Snyk docker CLI was not found");
}
if (error.indexOf("Cannot connect to the Docker daemon") !== -1) {
throw new Error("Cannot connect to the Docker daemon. Is the docker" + " daemon running?");
}
const ERROR_LOADING_IMAGE_STR = "Error loading image from docker engine:";
if (error.indexOf(ERROR_LOADING_IMAGE_STR) !== -1) {
if (error.indexOf("reference does not exist") !== -1) {
throw new Error(`Docker image was not found locally: ${targetImage}`);
}
if (error.indexOf("permission denied while trying to connect") !== -1) {
let errString = error.split(ERROR_LOADING_IMAGE_STR)[1];
errString = (errString || "").slice(0, -2); // remove trailing \"
throw new Error("Permission denied connecting to docker daemon. " +
"Please make sure user has the required permissions. " +
"Error string: " +
errString);
}
}
if (error.indexOf("Error getting docker client:") !== -1) {
throw new Error("Failed getting docker client");
}
if (error.indexOf("Error processing image:") !== -1) {
throw new Error("Failed processing image:" + targetImage);
}
}
function getDependencies(targetImage, dockerfileAnalysis, options) {
let result;
return analyzer
.analyze(targetImage, dockerfileAnalysis, options)
.then((output) => {
result = parseAnalysisResults(targetImage, output, dockerfileAnalysis);
return buildTree(targetImage, result.type, result.depInfosList, result.targetOS);
})
.then((pkg) => {
return {
package: pkg,
packageManager: result.type,
imageId: result.imageId,
binaries: result.binaries,
imageLayers: result.imageLayers,
};
})
.catch((error) => {
if (typeof error === "string") {
debug(`Error while running analyzer: '${error}'`);
handleCommonErrors(error, targetImage);
let errorMsg = error;
const errorMatch = /msg="(.*)"/g.exec(errorMsg);
if (errorMatch) {
errorMsg = errorMatch[1];
}
throw new Error(errorMsg);
}
throw error;
});
}
function getManifestFiles(targetImage, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!options.manifestGlobs) {
return [];
}
const globs = options.manifestGlobs;
const docker = new docker_1.Docker(targetImage, options);
const contents = yield Promise.all(globs.map((g) => docker.catSafe(g)));
return globs
.map((g, i) => {
return {
name: path.basename(g),
path: path.dirname(g),
contents: Buffer.from(contents[i].stdout).toString("base64"),
};
})
.filter((i) => i.contents !== "");
});
}
function parseAnalysisResults(targetImage, analysisJson, dockerfileAnalysis) {
let analysisResult = analysisJson.results.filter((res) => {
return res.Analysis && res.Analysis.length > 0;
})[0];
if (!analysisResult) {
if (dockerfileAnalysis && dockerfileAnalysis.baseImage === "scratch") {
// Special case when we have no package management
// on scratch images
analysisResult = {
Image: targetImage,
AnalyzeType: "linux",
Analysis: [],
};
}
else {
throw new Error("Failed to detect a supported Linux package manager (deb/rpm/apk)");
}
}
let depType;
switch (analysisResult.AnalyzeType) {
case "Apt": {
depType = "deb";
break;
}
default: {
depType = analysisResult.AnalyzeType.toLowerCase();
}
}
return {
imageId: analysisJson.imageId,
targetOS: analysisJson.osRelease,
type: depType,
depInfosList: analysisResult.Analysis,
binaries: analysisJson.binaries.Analysis,
imageLayers: analysisJson.imageLayers,
};
}
function buildTree(targetImage, depType, depInfosList, targetOS) {
// A tag can only occur in the last section of a docker image name, so
// check any colon separator after the final '/'. If there are no '/',
// which is common when using Docker's official images such as
// "debian:stretch", just check for ':'
const finalSlash = targetImage.lastIndexOf("/");
const hasVersion = (finalSlash >= 0 && targetImage.slice(finalSlash).includes(":")) ||
targetImage.includes(":");
// Defaults for simple images from dockerhub, like "node" or "centos"
let imageName = targetImage;
let imageVersion = "latest";
// If we have a version, split on the last ':' to avoid the optional
// port on a hostname (i.e. localhost:5000)
if (hasVersion) {
const versionSeparator = targetImage.lastIndexOf(":");
imageName = targetImage.slice(0, versionSeparator);
imageVersion = targetImage.slice(versionSeparator + 1);
}
const shaString = "@sha256";
if (imageName.endsWith(shaString)) {
imageName = imageName.slice(0, imageName.length - shaString.length);
imageVersion = "";
}
const root = {
// don't use the real image name to avoid scanning it as an issue
name: "docker-image|" + imageName,
version: imageVersion,
targetOS,
packageFormatVersion: depType + ":0.0.1",
dependencies: {},
};
const depsMap = depInfosList.reduce((acc, depInfo) => {
const name = depInfo.Name;
acc[name] = depInfo;
return acc;
}, {});
const virtualDepsMap = depInfosList.reduce((acc, depInfo) => {
const providesNames = depInfo.Provides || [];
providesNames.forEach((name) => {
acc[name] = depInfo;
});
return acc;
}, {});
const depsCounts = {};
depInfosList.forEach((depInfo) => {
countDepsRecursive(depInfo.Name, new Set(), depsMap, virtualDepsMap, depsCounts);
});
const DEP_FREQ_THRESHOLD = 100;
const tooFrequentDepNames = Object.keys(depsCounts).filter((depName) => {
return depsCounts[depName] > DEP_FREQ_THRESHOLD;
});
const attachDeps = (depInfos) => {
const depNamesToSkip = new Set(tooFrequentDepNames);
depInfos.forEach((depInfo) => {
const subtree = buildTreeRecurisve(depInfo.Name, new Set(), depsMap, virtualDepsMap, depNamesToSkip);
if (subtree) {
root.dependencies[subtree.name] = subtree;
}
});
};
// attach (as direct deps) pkgs not marked auto-installed:
const manuallyInstalledDeps = depInfosList.filter((depInfo) => {
return !depInfo.AutoInstalled;
});
attachDeps(manuallyInstalledDeps);
// attach (as direct deps) pkgs marked as auto-insatalled,
// but not dependant upon:
const notVisitedDeps = depInfosList.filter((depInfo) => {
const depName = depInfo.Name;
return !depsMap[depName]._visited;
});
attachDeps(notVisitedDeps);
// group all the "too frequest" deps under a meta package:
if (tooFrequentDepNames.length > 0) {
const tooFrequentDeps = tooFrequentDepNames.map((name) => {
return depsMap[name];
});
const metaSubtree = {
name: "meta-common-packages",
version: "meta",
dependencies: {},
};
tooFrequentDeps.forEach((depInfo) => {
const pkg = {
name: depFullName(depInfo),
version: depInfo.Version,
};
metaSubtree.dependencies[pkg.name] = pkg;
});
root.dependencies[metaSubtree.name] = metaSubtree;
}
return root;
}
function buildTreeRecurisve(depName, ancestors, depsMap, virtualDepsMap, depNamesToSkip) {
const depInfo = depsMap[depName] || virtualDepsMap[depName];
if (!depInfo) {
return null;
}
// "realName" as the argument depName might be a virtual pkg
const realName = depInfo.Name;
const fullName = depFullName(depInfo);
if (ancestors.has(fullName) || depNamesToSkip.has(realName)) {
return null;
}
const tree = {
name: fullName,
version: depInfo.Version,
};
if (depInfo._visited) {
return tree;
}
depInfo._visited = true;
const newAncestors = new Set(ancestors).add(fullName);
const deps = depInfo.Deps || {};
Object.keys(deps).forEach((name) => {
const subTree = buildTreeRecurisve(name, newAncestors, depsMap, virtualDepsMap, depNamesToSkip);
if (subTree) {
if (!tree.dependencies) {
tree.dependencies = {};
}
if (!tree.dependencies[subTree.name]) {
tree.dependencies[subTree.name] = subTree;
}
}
});
return tree;
}
function countDepsRecursive(depName, ancestors, depsMap, virtualDepsMap, depCounts) {
const depInfo = depsMap[depName] || virtualDepsMap[depName];
if (!depInfo) {
return;
}
// "realName" as the argument depName might be a virtual pkg
const realName = depInfo.Name;
if (ancestors.has(realName)) {
return;
}
depCounts[realName] = (depCounts[realName] || 0) + 1;
const newAncestors = new Set(ancestors).add(realName);
const deps = depInfo.Deps || {};
Object.keys(deps).forEach((name) => {
countDepsRecursive(name, newAncestors, depsMap, virtualDepsMap, depCounts);
});
}
function depFullName(depInfo) {
let fullName = depInfo.Name;
if (depInfo.Source) {
fullName = depInfo.Source + "/" + fullName;
}
return fullName;
}
//# sourceMappingURL=index.js.map