snyk-docker-plugin
Version:
Snyk CLI docker plugin
131 lines (114 loc) • 4.43 kB
text/typescript
import * as Debug from "debug";
import { normalize as normalizePath } from "path";
import { DockerFileAnalysis } from "../../dockerfile/types";
import { ExtractedLayers } from "../../extractor/types";
import { getOsReleaseStatic as getOsRelease } from "../../inputs/os-release";
import { OsReleaseFilePath } from "../../types";
import { OSRelease } from "../types";
import {
tryAlpineRelease,
tryCentosRelease,
tryDebianVersion,
tryLsbRelease,
tryOracleRelease,
tryOSRelease,
tryRedHatRelease,
} from "./release-analyzer";
const debug = Debug("snyk");
const CHISEL_MANIFEST_PATH = "/var/lib/chisel/manifest.wall";
type OsReleaseHandler = (text: string) => Promise<OSRelease | null>;
/**
* Checks if a Chisel manifest exists in the extracted layers.
* Chisel is Ubuntu's tool for creating minimal container images.
*
* @param extractedLayers - Layers extracted from the Docker image
* @returns true if Chisel manifest.wall file is present
*/
function hasChiselManifest(extractedLayers: ExtractedLayers): boolean {
const manifestPath = normalizePath(CHISEL_MANIFEST_PATH);
return manifestPath in extractedLayers;
}
const releaseDetectors: Record<OsReleaseFilePath, OsReleaseHandler> = {
[OsReleaseFilePath.Linux]: tryOSRelease,
// Fallback for the case where the same file exists in different location or is a symlink to the other location
[OsReleaseFilePath.LinuxFallback]: tryOSRelease,
// Generic fallback
[OsReleaseFilePath.Lsb]: tryLsbRelease,
// Fallbacks for specific older distributions
[OsReleaseFilePath.Debian]: tryDebianVersion,
[OsReleaseFilePath.Alpine]: tryAlpineRelease,
[OsReleaseFilePath.Oracle]: tryOracleRelease,
[OsReleaseFilePath.RedHat]: tryRedHatRelease,
[OsReleaseFilePath.Centos]: tryCentosRelease,
};
export async function detect(
extractedLayers: ExtractedLayers,
dockerfileAnalysis: DockerFileAnalysis | undefined,
): Promise<OSRelease> {
/**
* We want to detect whether the OS release file existed, but it just could not be parsed successfully.
* This is so that we can distinguish between images with multiple "os-release" files - some of them
* may fail to parse while others will succeed. This will depend purely on the order of our handlers.
* We want to run all handlers and only then decide if detection succeeded or not.
*/
let hadOsReleaseFile = false;
let osRelease: OSRelease | null = null;
for (const [type, handler] of Object.entries(releaseDetectors)) {
const osReleaseFile = getOsRelease(
extractedLayers,
type as OsReleaseFilePath,
);
if (!osReleaseFile) {
continue;
}
hadOsReleaseFile = true;
try {
osRelease = await handler(osReleaseFile);
} catch (err) {
debug(`Malformed OS release file: ${err.message}`);
}
if (osRelease) {
break;
}
}
if (!osRelease && hadOsReleaseFile) {
throw new Error("Failed to parse OS release file");
}
if (!osRelease) {
// Check if this is a Chisel image without OS release information
const isChiselImage = hasChiselManifest(extractedLayers);
if (isChiselImage) {
// Chisel images detected but OS version could not be determined
// This happens when ultra-minimal Chisel slices are used without base-files_release-info
debug(
`Chisel manifest found at ${CHISEL_MANIFEST_PATH} but no OS release files detected`,
);
// Set OS name to "chisel" so downstream systems can identify these images
// note we only do this to alert the user that they are missing release info
// when they have release info, we identify the image with that instead
osRelease = { name: "chisel", version: "0.0", prettyName: "" };
} else if (
dockerfileAnalysis &&
dockerfileAnalysis.baseImage === "scratch"
) {
// If the docker file was build from a scratch image
// then we don't have a known OS
osRelease = { name: "scratch", version: "0.0", prettyName: "" };
} else {
osRelease = { name: "unknown", version: "0.0", prettyName: "" };
}
}
// Oracle Linux identifies itself as "ol"
if (osRelease.name.trim() === "ol") {
osRelease.name = "oracle";
}
// Support round version. ie change SLES 15 to SLES 15.0
if (
osRelease.name.trim() === "sles" &&
osRelease.version &&
!osRelease.version.includes(".")
) {
osRelease.version += ".0";
}
return osRelease;
}