Skip to content

Commit

Permalink
fix: decouple image analyzers and introduce static scanning opts
Browse files Browse the repository at this point in the history
Decouple the way we retrieve package manager files (e.g. APK DB file), which is currently done with Docker run, from the logic that processes the file content. This will allow reusing the logic of processing a file that was acquired in different ways (e.g. statically or dynamically with Docker) while still supporting the existing functionality.

Additionally introduced static stanning options which serve as hints that the caller wants a static analysis. The code now branches at the entrypoint "inspect()" for the two new paths: static analysis and dynamic (Docker) analysis.
  • Loading branch information
ivanstanev committed Sep 27, 2019
1 parent d0473b6 commit 780919a
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 193 deletions.
87 changes: 87 additions & 0 deletions lib/analyzer/docker-analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as Debug from "debug";
import { DockerOptions } from "../docker";
import * as dockerFile from "../docker-file";
import * as binariesAnalyzer from "./binaries-analyzer";
import * as imageInspector from "./image-inspector";
import * as osReleaseDetector from "./os-release-detector";

import apkInputDocker = require("../inputs/apk/docker");
import aptInputDocker = require("../inputs/apt/docker");
import rpmInputDocker = require("../inputs/rpm/docker");
import apkAnalyzer = require("./package-manager/apk");
import aptAnalyzer = require("./package-manager/apt");
import rpmAnalyzer = require("./package-manager/rpm");

const debug = Debug("snyk");

export async function analyze(
targetImage: string,
dockerfileAnalysis?: dockerFile.DockerFileAnalysis,
options?: DockerOptions,
) {
const [imageInspection, osRelease] = await Promise.all([
imageInspector.detect(targetImage, options),
osReleaseDetector.detect(targetImage, dockerfileAnalysis, options),
]);

const [
apkDbFileContent,
aptDbFileContent,
rpmDbFileContent,
] = await Promise.all([
apkInputDocker.getApkDbFileContent(targetImage, options),
aptInputDocker.getAptDbFileContent(targetImage, options),
rpmInputDocker.getRpmDbFileContent(targetImage, options),
]);

const results = await Promise.all([
apkAnalyzer.analyze(targetImage, apkDbFileContent),
aptAnalyzer.analyze(targetImage, aptDbFileContent),
rpmAnalyzer.analyze(targetImage, rpmDbFileContent),
]).catch((err) => {
debug(`Error while running analyzer: '${err.stderr}'`);
throw new Error("Failed to detect installed OS packages");
});

const { installedPackages, pkgManager } = getInstalledPackages(
results as any[],
);
let binaries;
try {
binaries = await binariesAnalyzer.analyze(
targetImage,
installedPackages,
pkgManager,
options,
);
} catch (err) {
debug(`Error while running binaries analyzer: '${err}'`);
throw new Error("Failed to detect binaries versions");
}

return {
imageId: imageInspection.Id,
osRelease,
results,
binaries,
imageLayers: imageInspection.RootFS && imageInspection.RootFS.Layers,
};
}

function getInstalledPackages(
results: any[],
): { installedPackages: string[]; pkgManager?: string } {
const dockerAnalysis = results.find((res) => {
return res.Analysis && res.Analysis.length > 0;
});

if (!dockerAnalysis) {
return { installedPackages: [] };
}
const installedPackages = dockerAnalysis.Analysis.map((pkg) => pkg.Name);
let pkgManager = dockerAnalysis.AnalyzeType;
if (pkgManager) {
pkgManager = pkgManager.toLowerCase();
}
return { installedPackages, pkgManager };
}
77 changes: 3 additions & 74 deletions lib/analyzer/index.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,4 @@
import * as Debug from "debug";
import { DockerOptions } from "../docker";
import * as dockerFile from "../docker-file";
import * as apkAnalyzer from "./apk-analyzer";
import * as aptAnalyzer from "./apt-analyzer";
import * as binariesAnalyzer from "./binaries-analyzer";
import * as imageInspector from "./image-inspector";
import * as osReleaseDetector from "./os-release-detector";
import * as rpmAnalyzer from "./rpm-analyzer";
import { analyze as analyzeDynamically } from "./docker-analyzer";
import { analyze as analyzeStatically } from "./static-analyzer";

export { analyze };

const debug = Debug("snyk");

async function analyze(
targetImage: string,
dockerfileAnalysis?: dockerFile.DockerFileAnalysis,
options?: DockerOptions,
) {
const [imageInspection, osRelease] = await Promise.all([
imageInspector.detect(targetImage, options),
osReleaseDetector.detect(targetImage, dockerfileAnalysis, options),
]);

const results = await Promise.all([
apkAnalyzer.analyze(targetImage, options),
aptAnalyzer.analyze(targetImage, options),
rpmAnalyzer.analyze(targetImage, options),
]).catch((err) => {
debug(`Error while running analyzer: '${err.stderr}'`);
throw new Error("Failed to detect installed OS packages");
});

const { installedPackages, pkgManager } = getInstalledPackages(
results as any[],
);
let binaries;
try {
binaries = await binariesAnalyzer.analyze(
targetImage,
installedPackages,
pkgManager,
options,
);
} catch (err) {
debug(`Error while running binaries analyzer: '${err}'`);
throw new Error("Failed to detect binaries versions");
}

return {
imageId: imageInspection.Id,
osRelease,
results,
binaries,
imageLayers: imageInspection.RootFS && imageInspection.RootFS.Layers,
};
}

function getInstalledPackages(
results: any[],
): { installedPackages: string[]; pkgManager?: string } {
const dockerAnalysis = results.find((res) => {
return res.Analysis && res.Analysis.length > 0;
});

if (!dockerAnalysis) {
return { installedPackages: [] };
}
const installedPackages = dockerAnalysis.Analysis.map((pkg) => pkg.Name);
let pkgManager = dockerAnalysis.AnalyzeType;
if (pkgManager) {
pkgManager = pkgManager.toLowerCase();
}
return { installedPackages, pkgManager };
}
export { analyzeDynamically, analyzeStatically };
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Docker, DockerOptions } from "../docker";
import { AnalyzerPkg } from "./types";
export { analyze };
import { AnalyzerPkg, AnalyzerResult } from "../types";

function analyze(targetImage: string, options?: DockerOptions) {
return getPackages(targetImage, options).then((pkgs) => ({
export function analyze(
targetImage: string,
apkDbFileContent: string,
): AnalyzerResult {
return {
Image: targetImage,
AnalyzeType: "Apk",
Analysis: pkgs,
}));
}

function getPackages(targetImage: string, options?: DockerOptions) {
return new Docker(targetImage, options)
.catSafe("/lib/apk/db/installed")
.then((output) => parseFile(output.stdout));
Analysis: parseFile(apkDbFileContent),
};
}

function parseFile(text: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Docker, DockerOptions } from "../docker";
import { AnalyzerPkg } from "./types";
import { AnalyzerPkg } from "../types";

export { analyze };

async function analyze(targetImage: string, options?: DockerOptions) {
const docker = new Docker(targetImage, options);
const dpkgFile = (await docker.catSafe("/var/lib/dpkg/status")).stdout;
const pkgs = parseDpkgFile(dpkgFile);
async function analyze(
targetImage: string,
aptFiles: {
dpkgFile: string;
extFile: string;
},
) {
const pkgs = parseDpkgFile(aptFiles.dpkgFile);

const extFile = (await docker.catSafe("/var/lib/apt/extended_states")).stdout;
if (extFile) {
setAutoInstalledPackages(extFile, pkgs);
if (aptFiles.extFile) {
setAutoInstalledPackages(aptFiles.extFile, pkgs);
}

return {
Expand Down
34 changes: 34 additions & 0 deletions lib/analyzer/package-manager/rpm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AnalyzerPkg } from "../types";

export { analyze };

async function analyze(targetImage: string, rpmDbFilecontent: string) {
return {
Image: targetImage,
AnalyzeType: "Rpm",
Analysis: parseOutput(rpmDbFilecontent),
};
}

function parseOutput(output: string) {
const pkgs: AnalyzerPkg[] = [];
for (const line of output.split("\n")) {
parseLine(line, pkgs);
}
return pkgs;
}

function parseLine(text: string, pkgs: AnalyzerPkg[]) {
const [name, version, size] = text.split("\t");
if (name && version && size) {
const pkg: AnalyzerPkg = {
Name: name,
Version: version,
Source: undefined,
Provides: [],
Deps: {},
AutoInstalled: undefined,
};
pkgs.push(pkg);
}
}
55 changes: 0 additions & 55 deletions lib/analyzer/rpm-analyzer.ts

This file was deleted.

8 changes: 8 additions & 0 deletions lib/analyzer/static-analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { StaticAnalysisOptions } from "../types";

export async function analyze(
targetImage: string,
options: StaticAnalysisOptions,
): Promise<never> {
throw new Error("Not yet implemented!");
}
6 changes: 6 additions & 0 deletions lib/analyzer/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export interface AnalyzerPkg {
AutoInstalled?: boolean;
}

export interface AnalyzerResult {
Image: string;
AnalyzeType: string;
Analysis: AnalyzerPkg[];
}

export interface OSRelease {
name: string;
version: string;
Expand Down
20 changes: 11 additions & 9 deletions lib/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface DockerOptions {
tlsCert?: string;
tlsCaCert?: string;
tlsKey?: string;
manifestGlobs?: string;
manifestExcludeGlobs?: string;
}

class Docker {
Expand All @@ -21,25 +23,25 @@ class Docker {
]);
}

private static createOptionsList(options: any) {
private static createOptionsList(options?: DockerOptions) {
const opts: string[] = [];
if (!options) {
return opts;
}
if (options.host) {
opts.push(`--host=${options.host}`);
}
if (options.tlscert) {
opts.push(`--tlscert=${options.tlscert}`);
if (options.tlsCert) {
opts.push(`--tlscert=${options.tlsCert}`);
}
if (options.tlscacert) {
opts.push(`--tlscacert=${options.tlscacert}`);
if (options.tlsCaCert) {
opts.push(`--tlscacert=${options.tlsCaCert}`);
}
if (options.tlskey) {
opts.push(`--tlskey=${options.tlskey}`);
if (options.tlsKey) {
opts.push(`--tlskey=${options.tlsKey}`);
}
if (options.tlsverify) {
opts.push(`--tlsverify=${options.tlsverify}`);
if (options.tlsVerify) {
opts.push(`--tlsverify=${options.tlsVerify}`);
}
return opts;
}
Expand Down
Loading

0 comments on commit 780919a

Please sign in to comment.