Skip to content

Commit 3434ca0

Browse files
authored
Merge pull request #31 from microsoft/amarnatv/defendercli
Added defender cli support
2 parents 2aa03b3 + f8f3520 commit 3434ca0

File tree

4 files changed

+502
-1
lines changed

4 files changed

+502
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@microsoft/security-devops-azdevops-task-lib",
3-
"version": "1.13.0",
3+
"version": "1.13.1",
44
"description": "Microsoft Security DevOps for Azure DevOps task library.",
55
"author": "Microsoft Corporation",
66
"license": "MIT",

src/defender-client.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import * as path from 'path';
2+
import * as process from 'process';
3+
import * as fs from 'fs';
4+
import * as tl from 'azure-pipelines-task-lib/task';
5+
import { IExecOptions } from "azure-pipelines-task-lib/toolrunner";
6+
import * as common from './msdo-common';
7+
import * as installer from './defender-installer';
8+
9+
/**
10+
* The default version of Defender CLI to install if no version is specified.
11+
*/
12+
const cliVersionDefault: string = 'latest';
13+
14+
/**
15+
* Sets up the environment for the Defender CLI run.
16+
* Sets pipeline variables.
17+
* Resolves the version of Defender CLI to install.
18+
* Installs Defender CLI
19+
*
20+
* @param taskFolder The folder of the task that is using the Defender CLI
21+
*/
22+
async function setupEnvironment(): Promise<void> {
23+
24+
console.log('------------------------------------------------------------------------------');
25+
26+
// initialize the _defender directory
27+
let agentDirectory = path.join(process.env.AGENT_ROOTDIRECTORY, '_defender');
28+
tl.debug(`agentDirectory = ${agentDirectory}`);
29+
common.ensureDirectory(agentDirectory);
30+
31+
let agentPackagesDirectory = process.env.DEFENDER_PACKAGES_DIRECTORY;
32+
if (!agentPackagesDirectory) {
33+
agentPackagesDirectory = path.join(agentDirectory, 'packages');
34+
tl.debug(`agentPackagesDirectory = ${agentPackagesDirectory}`);
35+
common.ensureDirectory(agentPackagesDirectory);
36+
process.env.DEFENDER_PACKAGES_DIRECTORY = agentPackagesDirectory;
37+
}
38+
39+
if (!process.env.DEFENDER_FILEPATH) {
40+
let cliVersion = resolveCliVersion();
41+
await installer.install(cliVersion);
42+
}
43+
44+
console.log('------------------------------------------------------------------------------');
45+
}
46+
47+
/**
48+
* Resolves the version of Defender CLI to install.
49+
*
50+
* @returns The version of Defender CLI to install
51+
*/
52+
function resolveCliVersion(): string {
53+
let cliVersion = cliVersionDefault;
54+
55+
console.log(`Initial CLI version (default): ${cliVersion}`);
56+
57+
if (process.env.DEFENDER_VERSION) {
58+
cliVersion = process.env.DEFENDER_VERSION;
59+
console.log(`Using DEFENDER_VERSION: ${cliVersion}`);
60+
}
61+
62+
if (cliVersion.includes('*')) {
63+
console.log(`Version contains '*', switching to Latest`);
64+
cliVersion = cliVersionDefault;
65+
}
66+
67+
return cliVersion;
68+
}
69+
70+
/**
71+
* Gets the path to the Defender CLI
72+
*
73+
* @returns The path to the Defender CLI
74+
*/
75+
function getCliFilePath() : string {
76+
let cliFilePath: string = process.env.DEFENDER_FILEPATH;
77+
tl.debug(`cliFilePath = ${cliFilePath}`);
78+
return cliFilePath;
79+
}
80+
/**
81+
* Runs a Defender scan with the specified scan type and target
82+
* @param scanType - The type of scan to perform (e.g., "fs", "image")
83+
* @param target - The target to scan (directory path or image name)
84+
* @param policy - The policy to use for scanning (default: "mdc")
85+
* @param outputPath - The output SARIF file path
86+
* @param successfulExitCodes - The exit codes that are considered successful. Defaults to [0]. All others will throw an Error.
87+
* @param additionalArgs - Optional additional CLI arguments to append to the command
88+
*/
89+
async function scan(
90+
scanType: string,
91+
target: string,
92+
policy: string = 'mdc',
93+
outputPath?: string,
94+
successfulExitCodes: number[] = null,
95+
additionalArgs: string[] = []
96+
): Promise<void> {
97+
98+
if (!outputPath) {
99+
outputPath = path.join(process.env.BUILD_STAGINGDIRECTORY || process.cwd(), 'defender.sarif');
100+
}
101+
102+
let args = [
103+
'scan',
104+
scanType,
105+
target,
106+
'--defender-policy', policy,
107+
'--defender-output', outputPath
108+
];
109+
110+
// Append additional arguments if provided
111+
if (additionalArgs && additionalArgs.length > 0) {
112+
args = args.concat(additionalArgs);
113+
tl.debug(`Appending additional arguments: ${additionalArgs.join(' ')}`);
114+
}
115+
116+
await runDefenderCli(args, successfulExitCodes);
117+
}
118+
119+
/**
120+
* Runs the Defender CLI with the specified arguments for directory scanning
121+
* @param directoryPath - The directory path to scan
122+
* @param policy - The policy to use for scanning (default: "mdc")
123+
* @param outputPath - The output SARIF file path
124+
* @param successfulExitCodes - The exit codes that are considered successful. Defaults to [0]. All others will throw an Error.
125+
* @param additionalArgs - Optional additional CLI arguments to append to the command
126+
*/
127+
export async function scanDirectory(
128+
directoryPath: string,
129+
policy: string = 'mdc',
130+
outputPath?: string,
131+
successfulExitCodes: number[] = null,
132+
additionalArgs: string[] = []
133+
): Promise<void> {
134+
await scan('fs', directoryPath, policy, outputPath, successfulExitCodes, additionalArgs);
135+
}
136+
137+
/**
138+
* Runs the Defender CLI with the specified arguments for container image scanning
139+
* @param imageName - The container image name to scan
140+
* @param policy - The policy to use for scanning (default: "mdc")
141+
* @param outputPath - The output SARIF file path
142+
* @param successfulExitCodes - The exit codes that are considered successful. Defaults to [0]. All others will throw an Error.
143+
* @param additionalArgs - Optional additional CLI arguments to append to the command
144+
*/
145+
export async function scanImage(
146+
imageName: string,
147+
policy: string = 'mdc',
148+
outputPath?: string,
149+
successfulExitCodes: number[] = null,
150+
additionalArgs: string[] = []
151+
): Promise<void> {
152+
await scan('image', imageName, policy, outputPath, successfulExitCodes, additionalArgs);
153+
}
154+
155+
/**
156+
* Runs the Defender CLI with the specified arguments
157+
* @param inputArgs - The CLI arguments to pass to the Defender CLI
158+
* @param successfulExitCodes - The exit codes that are considered successful. Defaults to [0]. All others will throw an Error.
159+
*/
160+
async function runDefenderCli(inputArgs: string[], successfulExitCodes: number[] = null): Promise<void> {
161+
let tool = null;
162+
163+
try {
164+
165+
if (successfulExitCodes == null) {
166+
successfulExitCodes = [0];
167+
}
168+
169+
await setupEnvironment();
170+
171+
let cliFilePath = getCliFilePath();
172+
173+
tool = tl.tool(cliFilePath);
174+
175+
if (inputArgs != null) {
176+
for (let i = 0; i < inputArgs.length; i++) {
177+
tool.arg(inputArgs[i]);
178+
}
179+
}
180+
181+
let systemDebug = tl.getVariable("system.debug");
182+
183+
if (systemDebug == 'true') {
184+
// Add verbose logging if system debug is enabled
185+
tool.arg('--defender-debug');
186+
}
187+
188+
} catch (error) {
189+
console.error('Exception occurred while initializing Defender CLI:');
190+
tl.setResult(tl.TaskResult.Failed, error);
191+
return;
192+
}
193+
194+
try {
195+
// let us parse the exit code
196+
let options: IExecOptions = <IExecOptions>{
197+
ignoreReturnCode: true
198+
};
199+
200+
tl.debug('Running Microsoft Defender CLI...');
201+
202+
let exitCode = await tool.exec(options);
203+
204+
let success = false;
205+
for (let i = 0; i < successfulExitCodes.length; i++) {
206+
if (exitCode == successfulExitCodes[i]) {
207+
success = true;
208+
break;
209+
}
210+
}
211+
212+
if (!success) {
213+
throw new Error(`Defender CLI exited with an error exit code: ${exitCode}`);
214+
}
215+
} catch (error) {
216+
tl.setResult(tl.TaskResult.Failed, error);
217+
}
218+
}

0 commit comments

Comments
 (0)