UNPKG

5.15 kBPlain TextView Raw
1import * as fs from "fs";
2import * as path from "path";
3
4import { getImageArchive } from "./analyzer/image-inspector";
5import { readDockerfileAndAnalyse } from "./dockerfile";
6import { DockerFileAnalysis } from "./dockerfile/types";
7import { ImageName } from "./extractor/image";
8import { fullImageSavePath } from "./image-save-path";
9import { getArchivePath, getImageType } from "./image-type";
10import { isNumber, isTrue } from "./option-utils";
11import * as staticModule from "./static";
12import { ImageType, PluginOptions, PluginResponse } from "./types";
13
14// Registry credentials may also be provided by env vars. When both are set, flags take precedence.
15export function mergeEnvVarsIntoCredentials(
16 options: Partial<PluginOptions>,
17): void {
18 options.username = options.username || process.env.SNYK_REGISTRY_USERNAME;
19 options.password = options.password || process.env.SNYK_REGISTRY_PASSWORD;
20}
21
22export async function scan(
23 options?: Partial<PluginOptions>,
24): Promise<PluginResponse> {
25 if (!options) {
26 throw new Error("No plugin options provided");
27 }
28
29 mergeEnvVarsIntoCredentials(options);
30
31 if (!options.path) {
32 throw new Error("No image identifier or path provided");
33 }
34
35 const nestedJarsDepth =
36 options["nested-jars-depth"] || options["shaded-jars-depth"];
37 if (
38 (isTrue(nestedJarsDepth) || isNumber(nestedJarsDepth)) &&
39 isTrue(options["exclude-app-vulns"])
40 ) {
41 throw new Error(
42 "To use --nested-jars-depth, you must not use --exclude-app-vulns",
43 );
44 }
45
46 if (
47 (!isNumber(nestedJarsDepth) &&
48 !isTrue(nestedJarsDepth) &&
49 typeof nestedJarsDepth !== "undefined") ||
50 Number(nestedJarsDepth) < 0
51 ) {
52 throw new Error(
53 "--nested-jars-depth accepts only numbers bigger than or equal to 0",
54 );
55 }
56
57 // TODO temporary solution to avoid double results for PHP if exists in `globsToFind`
58 if (options.globsToFind) {
59 options.globsToFind.include = options.globsToFind.include.filter(
60 (glob) => !glob.includes("composer"),
61 );
62 }
63
64 const targetImage = appendLatestTagIfMissing(options.path);
65
66 const dockerfilePath = options.file;
67 const dockerfileAnalysis = await readDockerfileAndAnalyse(dockerfilePath);
68
69 const imageType = getImageType(targetImage);
70 switch (imageType) {
71 case ImageType.DockerArchive:
72 case ImageType.OciArchive:
73 return localArchiveAnalysis(
74 targetImage,
75 imageType,
76 dockerfileAnalysis,
77 options,
78 );
79 case ImageType.Identifier:
80 return imageIdentifierAnalysis(
81 targetImage,
82 imageType,
83 dockerfileAnalysis,
84 options,
85 );
86
87 default:
88 throw new Error("Unhandled image type for image " + targetImage);
89 }
90}
91
92async function localArchiveAnalysis(
93 targetImage: string,
94 imageType: ImageType,
95 dockerfileAnalysis: DockerFileAnalysis | undefined,
96 options: Partial<PluginOptions>,
97): Promise<PluginResponse> {
98 const globToFind = {
99 include: options.globsToFind?.include || [],
100 exclude: options.globsToFind?.exclude || [],
101 };
102
103 const archivePath = getArchivePath(targetImage);
104 if (!fs.existsSync(archivePath)) {
105 throw new Error(
106 "The provided archive path does not exist on the filesystem",
107 );
108 }
109 if (!fs.lstatSync(archivePath).isFile()) {
110 throw new Error("The provided archive path is not a file");
111 }
112
113 const imageIdentifier =
114 options.imageNameAndTag ||
115 // The target image becomes the base of the path, e.g. "archive.tar" for "/var/tmp/archive.tar"
116 path.basename(archivePath);
117
118 let imageName: ImageName | undefined;
119 if (
120 (options.digests?.manifest || options.digests?.index) &&
121 options.imageNameAndTag
122 ) {
123 imageName = new ImageName(options.imageNameAndTag, {
124 manifest: options.digests?.manifest,
125 index: options.digests?.index,
126 });
127 }
128
129 return await staticModule.analyzeStatically(
130 imageIdentifier,
131 dockerfileAnalysis,
132 imageType,
133 archivePath,
134 globToFind,
135 options,
136 imageName,
137 );
138}
139
140async function imageIdentifierAnalysis(
141 targetImage: string,
142 imageType: ImageType,
143 dockerfileAnalysis: DockerFileAnalysis | undefined,
144 options: Partial<PluginOptions>,
145): Promise<PluginResponse> {
146 const globToFind = {
147 include: options.globsToFind?.include || [],
148 exclude: options.globsToFind?.exclude || [],
149 };
150
151 const imageSavePath = fullImageSavePath(options.imageSavePath);
152 const archiveResult = await getImageArchive(
153 targetImage,
154 imageSavePath,
155 options.username,
156 options.password,
157 options.platform,
158 );
159
160 const imagePath = archiveResult.path;
161 const imageName = archiveResult.imageName;
162 try {
163 return await staticModule.analyzeStatically(
164 targetImage,
165 dockerfileAnalysis,
166 imageType,
167 imagePath,
168 globToFind,
169 options,
170 imageName,
171 );
172 } finally {
173 archiveResult.removeArchive();
174 }
175}
176
177export function appendLatestTagIfMissing(targetImage: string): string {
178 if (
179 getImageType(targetImage) === ImageType.Identifier &&
180 !targetImage.includes(":")
181 ) {
182 return `${targetImage}:latest`;
183 }
184 return targetImage;
185}