UNPKG

snyk-docker-plugin

Version:
203 lines (185 loc) 5.02 kB
import { PackageURL } from "packageurl-js"; import { AnalysisType, AnalyzedPackage, AnalyzedPackageWithVersion, IAptFiles, ImagePackagesAnalysis, OSRelease, } from "../types"; export function analyze( targetImage: string, aptFiles: IAptFiles, osRelease?: OSRelease, ): Promise<ImagePackagesAnalysis> { const pkgs = parseDpkgFile(aptFiles.dpkgFile, osRelease); if (aptFiles.extFile) { setAutoInstalledPackages(aptFiles.extFile, pkgs); } return Promise.resolve({ Image: targetImage, AnalyzeType: AnalysisType.Apt, Analysis: pkgs, }); } export function analyzeDistroless( targetImage: string, aptFiles: string[], osRelease?: OSRelease, ): Promise<ImagePackagesAnalysis> { const analyzedPackages: AnalyzedPackageWithVersion[] = []; for (const fileContent of aptFiles) { const currentPackages = parseDpkgFile(fileContent, osRelease); analyzedPackages.push(...currentPackages); } return Promise.resolve({ Image: targetImage, AnalyzeType: AnalysisType.Apt, Analysis: analyzedPackages, }); } function parseDpkgFile( text: string, osRelease?: OSRelease, ): AnalyzedPackageWithVersion[] { const pkgs: AnalyzedPackageWithVersion[] = []; let curPkg: AnalyzedPackageWithVersion | null = null; for (const line of text.split("\n")) { curPkg = parseDpkgLine(line, curPkg!, pkgs); if (curPkg) { curPkg.Purl = purl(curPkg, osRelease); } } return pkgs; } const debianCodenames = new Map<string, string>([ ["8", "jessie"], ["9", "stretch"], ["10", "buster"], ["11", "bullseye"], ["12", "bookworm"], ["13", "trixie"], ["unstable", "sid"], ]); export function purl( curPkg: AnalyzedPackageWithVersion, osRelease?: OSRelease, ): string | undefined { let vendor = ""; if (!curPkg.Name || !curPkg.Version) { return undefined; } const qualifiers: { [key: string]: string } = {}; if (curPkg.Source && curPkg.SourceVersion) { qualifiers.upstream = `${curPkg.Source}@${curPkg.SourceVersion}`; } else if (curPkg.Source) { qualifiers.upstream = curPkg.Source; } if (osRelease) { const codenameOrVersion = debianCodenames.get(osRelease.version) ?? osRelease.version; qualifiers.distro = `${osRelease.name}-${codenameOrVersion}`; vendor = osRelease.name; } return new PackageURL( "deb", vendor, curPkg.Name, curPkg.Version, // make sure that we pass in undefined if there are no qualifiers, because // the packageurl-js library doesn't handle that properly... Object.keys(qualifiers).length !== 0 ? qualifiers : undefined, undefined, ).toString(); } function parseDpkgLine( text: string, curPkg: AnalyzedPackageWithVersion, pkgs: AnalyzedPackageWithVersion[], ): AnalyzedPackageWithVersion { const [key, value] = text.split(": "); switch (key) { case "Package": curPkg = { Name: value, Version: "", Source: undefined, SourceVersion: undefined, Provides: [], Deps: {}, AutoInstalled: undefined, }; pkgs.push(curPkg); break; case "Version": curPkg.Version = value; break; case "Source": /** * The value may look something like this: * libgcc6 (1.3.0-b1) * <name> (<version>) * * For example, Syft matches these values with a regex: * https://github.com/anchore/syft/blob/1764e1c3f6bd66781f8350d957a1f95e4d9ad3de/syft/pkg/cataloger/deb/parse_dpkg_db.go#L169-L173 */ const parts = value.split(" "); curPkg.Source = parts[0]; if (parts.length > 1) { curPkg.SourceVersion = parts[1] .trim() .replace("(", "") .replace(")", ""); } break; case "Provides": for (let name of value.split(",")) { name = name.trim().split(" ")[0]; curPkg.Provides.push(name); } break; case "Pre-Depends": case "Depends": for (const depElem of value.split(",")) { for (let name of depElem.split("|")) { name = name.trim().split(" ")[0]; curPkg.Deps[name] = true; } } break; } return curPkg; } function setAutoInstalledPackages(text: string, pkgs: AnalyzedPackage[]) { const autoPkgs = parseExtFile(text); for (const pkg of pkgs) { if (autoPkgs[pkg.Name]) { pkg.AutoInstalled = true; } } } interface PkgMap { [name: string]: boolean; } function parseExtFile(text: string) { const pkgMap: PkgMap = {}; let curPkgName: any = null; for (const line of text.split("\n")) { curPkgName = parseExtLine(line, curPkgName, pkgMap); } return pkgMap; } function parseExtLine(text: string, curPkgName: string, pkgMap: PkgMap) { const [key, value] = text.split(": "); switch (key) { case "Package": curPkgName = value; break; case "Auto-Installed": if (parseInt(value, 10) === 1) { pkgMap[curPkgName] = true; } break; } return curPkgName; }