Skip to content

Commit f3eb118

Browse files
Merge pull request #146 from microsoft/feature/syncWithADO
Sync repo with internal fork to bring it up to date
2 parents 6c63136 + 5d5a443 commit f3eb118

8 files changed

Lines changed: 4530 additions & 22 deletions

File tree

build.proj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
Condition=" '$(SkipCompile)' != 'true' ">
134134
<PropertyGroup>
135135
<RepoPackageJsonFilePath>$(RepoDirectory)\package.json</RepoPackageJsonFilePath>
136-
<SrcNpmrcFilePath>$(SrcDirectory)\.npmrc</SrcNpmrcFilePath>
136+
<SrcNpmrcFilePath>$(RepoDirectory)\.npmrc</SrcNpmrcFilePath>
137137
</PropertyGroup>
138138

139139
<Copy SourceFiles="$(RepoPackageJsonFilePath)" DestinationFiles="$(LibDirectory)\package.json" />

package-lock.json

Lines changed: 4363 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "microsoft-security-devops-azdevops",
3-
"version": "1.11.1",
3+
"version": "1.18.0",
44
"description": "Microsoft Security DevOps for Azure DevOps.",
55
"author": "Microsoft Corporation",
66
"license": "MIT",
@@ -13,9 +13,9 @@
1313
"test": "npx mocha **/*.tests.js"
1414
},
1515
"dependencies": {
16-
"@microsoft/security-devops-azdevops-task-lib": "1.11.0",
17-
"azure-pipelines-task-lib": "4.3.1",
18-
"azure-pipelines-tool-lib": "2.0.4",
16+
"@microsoft/security-devops-azdevops-task-lib": "1.13.0",
17+
"azure-pipelines-task-lib": "^4.13.0",
18+
"azure-pipelines-tool-lib": "^2.0.7",
1919
"uuid": "^9.0.1"
2020
},
2121
"devDependencies": {
@@ -28,6 +28,6 @@
2828
"mocha": "^10.2.0",
2929
"sinon": "^15.2.0",
3030
"tfx-cli": "^0.15.0",
31-
"typescript": "^5.1.3"
31+
"typescript": "5.1.6"
3232
}
3333
}

src/.npmrc

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/MicrosoftSecurityDevOps/v1/container-mapping.ts

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { IMicrosoftSecurityDevOps } from "./msdo-interface";
33
import tl = require('azure-pipelines-task-lib/task');
44
import { CommandExecutor, ICommandResult } from "./command-executor";
55
import {v4 as uuidv4} from 'uuid';
6+
import * as os from 'os';
7+
import * as https from "https";
8+
9+
const ContainerMappingUrlProd: string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/azuredevops/v1/container-mappings";
10+
const TokenApiVersion: string = "api-version=7.1-preview.1";
611

712
/**
813
* Represents the tasks for container mapping that are used to fetch Docker images pushed in a job run.
@@ -26,7 +31,7 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
2631
}
2732

2833
/*
29-
* Using the start time, fetch the docker events and docker images in this job run and log the encoded output
34+
* Using the start time, fetch the docker events and docker images in this job run and log the encoded output.
3035
*/
3136
private async runPostJob() {
3237
let startTime = tl.getVariable(Constants.PreJobStartTime);
@@ -35,6 +40,12 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
3540
writeToOutStream(Constants.PreJobStartTime + " variable not set/undefined, using now-10secs ");
3641
}
3742

43+
let reportData = {
44+
dockerVersion: "",
45+
dockerEvents: [],
46+
dockerImages: []
47+
};
48+
3849
// Initialize the commands
3950
let dockerVersionCmd = new CommandExecutor('docker', '--version');
4051
let eventsCmd = new CommandExecutor('docker', `events --since ${startTime} --until ${new Date().toISOString()} --filter event=push --filter type=image --format ID={{.ID}}`);
@@ -45,6 +56,9 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
4556
let evPromise : Promise<ICommandResult> = eventsCmd.execute();
4657
let imPromise : Promise<ICommandResult> = imagesCmd.execute();
4758

59+
// Get the OIDC token
60+
let bearerTokenPromise: Promise<string> = this.GetOIDCToken();
61+
4862
// Wait for Docker version
4963
let dockerVersion: ICommandResult = await dvPromise;
5064
if (dockerVersion.code != 0) {
@@ -53,6 +67,7 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
5367
}
5468
const cleanedDockerVersion = CommandExecutor.removeCommandFromOutput(dockerVersion.output);
5569
tl.debug(`Docker Version: ${cleanedDockerVersion}`);
70+
reportData.dockerVersion = cleanedDockerVersion;
5671

5772
// Wait for Docker events command to verify any images were built on this run
5873
let events: ICommandResult = await evPromise;
@@ -64,33 +79,148 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
6479
var images: ICommandResult;
6580
if (!cleanedEventsOutput) {
6681
tl.debug(`No Docker events found`);
67-
// Log a detail if no events found. We will check for this DetailTimeline record from our backend to reduce calls to ADO REST API to be mindful of Rate Limits.
68-
tl.logDetail(uuidv4(), "No Docker events found", null, "NoDockerEvents", "NoDockerEvents", 999);
82+
// Log a detail if no events found. We will check for this DetailTimeline record from our backend to reduce calls to ADO REST API to be mindful of Rate Limits., remove after oidc
83+
tl.logDetail(uuidv4(), "No Docker events found", null, "NoDockerEvents", "NoDockerEvents", 999); //remove after oidc
6984
// Initialize an empty Command Result for Docker images
7085
images = <ICommandResult>{ code: 0, output: "" };
7186
}
7287
else {
88+
reportData.dockerEvents = cleanedEventsOutput.split(os.EOL);
7389
// Wait for Docker images command only if events were found
7490
images = await imPromise;
7591
if (images.code != 0) {
7692
throw new Error(`Unable to fetch Docker images: ${images.output}`);
7793
}
7894
}
7995

96+
const cleanedImagesOutput = CommandExecutor.removeCommandFromOutput(images.output);
97+
reportData.dockerImages = cleanedImagesOutput.split(os.EOL);
98+
99+
//remove after oidc
80100
writeToOutStream(getEncodedContent(
81101
cleanedDockerVersion,
82102
cleanedEventsOutput,
83-
CommandExecutor.removeCommandFromOutput(images.output)));
103+
cleanedImagesOutput));
104+
//remove after oidc
105+
106+
tl.debug(JSON.stringify(reportData));
107+
108+
// Upload the data
109+
tl.debug(`Finished data collection, starting API calls`);
110+
111+
let bearerToken: string = await bearerTokenPromise
112+
.then((token) => {
113+
if (!token) {
114+
throw new Error("Empty OIDC token received");
115+
}
116+
return token;
117+
})
118+
.catch((error) => {
119+
throw new Error("Unable to get token: " + error);
120+
});
121+
122+
const sendStartTime = new Date().toISOString();
123+
await this.SendReport(JSON.stringify(reportData), bearerToken);
124+
const sendEndTime = new Date().toISOString();
125+
//writeToOutStream("Container Mapping data sent successfully in " + (new Date(sendEndTime).getTime() - new Date(sendStartTime).getTime()) + "ms"); //readd after oidc
126+
writeToOutStream(`##[debug]Container Mapping data sent successfully in ${(new Date(sendEndTime).getTime() - new Date(sendStartTime).getTime())}ms`); //remove after oidc
127+
}
128+
129+
/*
130+
* Get the OIDC Token. Returns the token as a string.
131+
*/
132+
private async GetOIDCToken(): Promise<string>
133+
{
134+
// https://learn.microsoft.com/rest/api/azure/devops/distributedtask/oidctoken/create?view=azure-devops-rest-7.1
135+
let collectionUri = tl.getVariable('SYSTEM_CollectionUri');
136+
let teamProjectId = tl.getVariable('SYSTEM_TeamProjectId');
137+
let hostType = tl.getVariable('SYSTEM_HostType');
138+
let planId = tl.getVariable('SYSTEM_PlanId');
139+
let jobId = tl.getVariable('SYSTEM_JobId');
140+
let uri = collectionUri + teamProjectId + "/_apis/distributedtask/hubs/" + hostType + "/plans/" + planId + "/jobs/" + jobId + "/oidctoken?" + TokenApiVersion;
141+
142+
let bearerToken = tl.getVariable('SYSTEM_ACCESSTOKEN');
143+
let data = JSON.stringify({authorizationId: "00000000-0000-0000-0000-000000000000"});
144+
145+
return this.PostData(uri, data, bearerToken, true)
146+
.then((response) => JSON.parse(response))
147+
.then((json) => json.oidcToken)
148+
.catch((reason) => { throw new Error("Unable to get token: " + reason); });
149+
}
150+
151+
/*
152+
* Upload the data to Defender for DevOps. Returns the status code of the API call.
153+
*/
154+
private async SendReport(reportData: string, bearerToken: string): Promise<number>
155+
{
156+
let alternateDevOpsServer: string = tl.getInput('alternateDevOpsServer');
157+
let containerMappingUrl: string = (alternateDevOpsServer && alternateDevOpsServer.length > 0) ? alternateDevOpsServer : ContainerMappingUrlProd;
158+
159+
return this.PostData(containerMappingUrl, reportData, bearerToken, false)
160+
.then(response => response.statusCode)
161+
.catch((reason) => { throw new Error("Unable to post data: " + reason); })
162+
}
163+
164+
/*
165+
* Post Request to the specified URI with the data and auth token provided. Returns the response object.
166+
*/
167+
private async PostData(uri: string, data: string, auth: string, returnData: boolean): Promise<any>
168+
{
169+
return new Promise(async (resolve, reject) => {
170+
let options = {
171+
method: 'POST',
172+
timeout: 2500,
173+
body: data,
174+
headers: {
175+
'Content-Type': 'application/json',
176+
'Authorization': 'Bearer ' + auth,
177+
'Content-Length': '' + data.length
178+
}
179+
};
180+
writeToOutStream(`##[debug]${options['method'].toUpperCase()} ${uri}`);
181+
182+
const req = https.request(uri, options, (res) => {
183+
let resData = '';
184+
res.on('data', (chunk) => {
185+
resData += chunk.toString();
186+
});
187+
188+
res.on('end', () => {
189+
if (res.statusCode < 200 || res.statusCode >= 300) {
190+
return reject(`Received Failed Status code when calling url: ${res.statusCode} ${resData}`);
191+
}
192+
writeToOutStream(`##[debug]Received Status code: ${res.statusCode} and Status message: ${res.statusMessage}`);
193+
194+
// Return the data if requested
195+
if (returnData) {
196+
resolve(resData);
197+
}
198+
// Return client response otherwise
199+
resolve(res);
200+
});
201+
202+
res.on('error', (error) => {
203+
reject(new Error(`Error calling url error: ${error}`));
204+
});
205+
});
206+
207+
req.on('error', (error) => {
208+
reject(new Error(`Error calling url: ${error}`));
209+
});
210+
211+
req.write(data);
212+
req.end();
213+
});
84214
}
85215

86216
/*
87-
* Run the specified function based on the task type
217+
* Run the specified function based on the task type.
88218
*/
89219
async run() {
90220
// Group command adds a collapsible section in the logs - https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands
91221
writeToOutStream("##[group]This task was injected as part of Microsoft Defender for DevOps enablement- https://go.microsoft.com/fwlink/?linkid=2231419");
92-
// This section is used as a delimiter while fetching logs from the REST API in our backend, do not modify
93-
writeToOutStream("##[section]:::::");
222+
// This section is used as a delimiter while fetching logs from the REST API in our backend, remove after oidc
223+
writeToOutStream("##[section]:::::"); //remove after oidc
94224

95225
try {
96226
switch (this.commandType) {

src/MicrosoftSecurityDevOps/v1/task.json

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
"author": "Microsoft Corporation",
1212
"version": {
1313
"Major": 1,
14-
"Minor": 12,
15-
"Patch": 1
14+
"Minor": 18,
15+
"Patch": 0
1616
},
1717
"preview": true,
18-
"minimumAgentVersion": "1.83.0",
18+
"minimumAgentVersion": "3.232.1",
1919
"groups": [
2020
{
2121
"name": "advanced",
@@ -108,14 +108,31 @@
108108
"helpMarkDown": "The name of the pipeline artifact to publish the SARIF result file to. Default: CodeAnalysisLogs</br>\"CodeAnalysisLogs\" is required for integration with [Defender for DevOps](https://aka.ms/defender-for-devops).</br>If left as \"CodeAnalysisLogs\", it integrates with the [SARIF Scans Tab](https://marketplace.visualstudio.com/items?itemName=sariftools.scans) viewing experience.",
109109
"defaultValue": "CodeAnalysisLogs",
110110
"groupName": "advanced"
111+
},
112+
{
113+
"name": "alternateDevOpsServer",
114+
"label": "Alternate DevOps Server",
115+
"type": "string",
116+
"required": false,
117+
"helpMarkDown": "An alternative DevOps server endpoint for advanced scenarios. This should be left empty.",
118+
"groupName": "advanced"
111119
}
112120
],
113121
"instanceNameFormat": "Run Microsoft Defender for DevOps",
114122
"execution": {
115-
"Node16": {
123+
"Node18": {
124+
"target": "index.js"
125+
},
126+
"Node20": {
127+
"target": "index.js"
128+
},
129+
"Node20_1": {
130+
"target": "index.js"
131+
},
132+
"Node22": {
116133
"target": "index.js"
117134
},
118-
"Node10": {
135+
"Node24": {
119136
"target": "index.js"
120137
}
121138
}

src/extension-manifest-debug.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifestVersion": 1,
33
"id": "microsoft-security-devops-azdevops",
44
"name": "Microsoft Security DevOps (Debug)",
5-
"version": "1.12.1.0",
5+
"version": "1.18.0.0",
66
"publisher": "ms-securitydevops",
77
"description": "Build tasks for performing security analysis.",
88
"public": false,

src/extension-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifestVersion": 1,
33
"id": "microsoft-security-devops-azdevops",
44
"name": "Microsoft Security DevOps",
5-
"version": "1.12.1",
5+
"version": "1.18.0",
66
"publisher": "ms-securitydevops",
77
"description": "Build tasks for performing security analysis.",
88
"public": true,

0 commit comments

Comments
 (0)