Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
Condition=" '$(SkipCompile)' != 'true' ">
<PropertyGroup>
<RepoPackageJsonFilePath>$(RepoDirectory)\package.json</RepoPackageJsonFilePath>
<SrcNpmrcFilePath>$(SrcDirectory)\.npmrc</SrcNpmrcFilePath>
<SrcNpmrcFilePath>$(RepoDirectory)\.npmrc</SrcNpmrcFilePath>
</PropertyGroup>

<Copy SourceFiles="$(RepoPackageJsonFilePath)" DestinationFiles="$(LibDirectory)\package.json" />
Expand Down
4,363 changes: 4,363 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "microsoft-security-devops-azdevops",
"version": "1.11.1",
"version": "1.18.0",
"description": "Microsoft Security DevOps for Azure DevOps.",
"author": "Microsoft Corporation",
"license": "MIT",
Expand All @@ -13,9 +13,9 @@
"test": "npx mocha **/*.tests.js"
},
"dependencies": {
"@microsoft/security-devops-azdevops-task-lib": "1.11.0",
"azure-pipelines-task-lib": "4.3.1",
"azure-pipelines-tool-lib": "2.0.4",
"@microsoft/security-devops-azdevops-task-lib": "1.13.0",
"azure-pipelines-task-lib": "^4.13.0",
"azure-pipelines-tool-lib": "^2.0.7",
"uuid": "^9.0.1"
},
"devDependencies": {
Expand All @@ -28,6 +28,6 @@
"mocha": "^10.2.0",
"sinon": "^15.2.0",
"tfx-cli": "^0.15.0",
"typescript": "^5.1.3"
"typescript": "5.1.6"
}
}
2 changes: 0 additions & 2 deletions src/.npmrc

This file was deleted.

144 changes: 137 additions & 7 deletions src/MicrosoftSecurityDevOps/v1/container-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { IMicrosoftSecurityDevOps } from "./msdo-interface";
import tl = require('azure-pipelines-task-lib/task');
import { CommandExecutor, ICommandResult } from "./command-executor";
import {v4 as uuidv4} from 'uuid';
import * as os from 'os';
import * as https from "https";

const ContainerMappingUrlProd: string = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/azuredevops/v1/container-mappings";
const TokenApiVersion: string = "api-version=7.1-preview.1";

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

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

let reportData = {
dockerVersion: "",
dockerEvents: [],
dockerImages: []
};

// Initialize the commands
let dockerVersionCmd = new CommandExecutor('docker', '--version');
let eventsCmd = new CommandExecutor('docker', `events --since ${startTime} --until ${new Date().toISOString()} --filter event=push --filter type=image --format ID={{.ID}}`);
Expand All @@ -45,6 +56,9 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
let evPromise : Promise<ICommandResult> = eventsCmd.execute();
let imPromise : Promise<ICommandResult> = imagesCmd.execute();

// Get the OIDC token
let bearerTokenPromise: Promise<string> = this.GetOIDCToken();

// Wait for Docker version
let dockerVersion: ICommandResult = await dvPromise;
if (dockerVersion.code != 0) {
Expand All @@ -53,6 +67,7 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
}
const cleanedDockerVersion = CommandExecutor.removeCommandFromOutput(dockerVersion.output);
tl.debug(`Docker Version: ${cleanedDockerVersion}`);
reportData.dockerVersion = cleanedDockerVersion;

// Wait for Docker events command to verify any images were built on this run
let events: ICommandResult = await evPromise;
Expand All @@ -64,33 +79,148 @@ export class ContainerMapping implements IMicrosoftSecurityDevOps {
var images: ICommandResult;
if (!cleanedEventsOutput) {
tl.debug(`No Docker events found`);
// 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.
tl.logDetail(uuidv4(), "No Docker events found", null, "NoDockerEvents", "NoDockerEvents", 999);
// 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
tl.logDetail(uuidv4(), "No Docker events found", null, "NoDockerEvents", "NoDockerEvents", 999); //remove after oidc
// Initialize an empty Command Result for Docker images
images = <ICommandResult>{ code: 0, output: "" };
}
else {
reportData.dockerEvents = cleanedEventsOutput.split(os.EOL);
// Wait for Docker images command only if events were found
images = await imPromise;
if (images.code != 0) {
throw new Error(`Unable to fetch Docker images: ${images.output}`);
}
}

const cleanedImagesOutput = CommandExecutor.removeCommandFromOutput(images.output);
reportData.dockerImages = cleanedImagesOutput.split(os.EOL);

//remove after oidc
writeToOutStream(getEncodedContent(
cleanedDockerVersion,
cleanedEventsOutput,
CommandExecutor.removeCommandFromOutput(images.output)));
cleanedImagesOutput));
//remove after oidc

tl.debug(JSON.stringify(reportData));

// Upload the data
tl.debug(`Finished data collection, starting API calls`);

let bearerToken: string = await bearerTokenPromise
.then((token) => {
if (!token) {
throw new Error("Empty OIDC token received");
}
return token;
})
.catch((error) => {
throw new Error("Unable to get token: " + error);
});

const sendStartTime = new Date().toISOString();
await this.SendReport(JSON.stringify(reportData), bearerToken);
const sendEndTime = new Date().toISOString();
//writeToOutStream("Container Mapping data sent successfully in " + (new Date(sendEndTime).getTime() - new Date(sendStartTime).getTime()) + "ms"); //readd after oidc
writeToOutStream(`##[debug]Container Mapping data sent successfully in ${(new Date(sendEndTime).getTime() - new Date(sendStartTime).getTime())}ms`); //remove after oidc
}

/*
* Get the OIDC Token. Returns the token as a string.
*/
private async GetOIDCToken(): Promise<string>
{
// https://learn.microsoft.com/rest/api/azure/devops/distributedtask/oidctoken/create?view=azure-devops-rest-7.1
let collectionUri = tl.getVariable('SYSTEM_CollectionUri');
let teamProjectId = tl.getVariable('SYSTEM_TeamProjectId');
let hostType = tl.getVariable('SYSTEM_HostType');
let planId = tl.getVariable('SYSTEM_PlanId');
let jobId = tl.getVariable('SYSTEM_JobId');
let uri = collectionUri + teamProjectId + "/_apis/distributedtask/hubs/" + hostType + "/plans/" + planId + "/jobs/" + jobId + "/oidctoken?" + TokenApiVersion;

let bearerToken = tl.getVariable('SYSTEM_ACCESSTOKEN');
let data = JSON.stringify({authorizationId: "00000000-0000-0000-0000-000000000000"});

return this.PostData(uri, data, bearerToken, true)
.then((response) => JSON.parse(response))
.then((json) => json.oidcToken)
.catch((reason) => { throw new Error("Unable to get token: " + reason); });
}

/*
* Upload the data to Defender for DevOps. Returns the status code of the API call.
*/
private async SendReport(reportData: string, bearerToken: string): Promise<number>
{
let alternateDevOpsServer: string = tl.getInput('alternateDevOpsServer');
let containerMappingUrl: string = (alternateDevOpsServer && alternateDevOpsServer.length > 0) ? alternateDevOpsServer : ContainerMappingUrlProd;

return this.PostData(containerMappingUrl, reportData, bearerToken, false)
.then(response => response.statusCode)
.catch((reason) => { throw new Error("Unable to post data: " + reason); })
}

/*
* Post Request to the specified URI with the data and auth token provided. Returns the response object.
*/
private async PostData(uri: string, data: string, auth: string, returnData: boolean): Promise<any>
{
return new Promise(async (resolve, reject) => {
let options = {
method: 'POST',
timeout: 2500,
body: data,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + auth,
'Content-Length': '' + data.length
}
};
writeToOutStream(`##[debug]${options['method'].toUpperCase()} ${uri}`);

const req = https.request(uri, options, (res) => {
let resData = '';
res.on('data', (chunk) => {
resData += chunk.toString();
});

res.on('end', () => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(`Received Failed Status code when calling url: ${res.statusCode} ${resData}`);
}
writeToOutStream(`##[debug]Received Status code: ${res.statusCode} and Status message: ${res.statusMessage}`);

// Return the data if requested
if (returnData) {
resolve(resData);
}
// Return client response otherwise
resolve(res);
});

res.on('error', (error) => {
reject(new Error(`Error calling url error: ${error}`));
});
});

req.on('error', (error) => {
reject(new Error(`Error calling url: ${error}`));
});

req.write(data);
req.end();
});
}

/*
* Run the specified function based on the task type
* Run the specified function based on the task type.
*/
async run() {
// 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
writeToOutStream("##[group]This task was injected as part of Microsoft Defender for DevOps enablement- https://go.microsoft.com/fwlink/?linkid=2231419");
// This section is used as a delimiter while fetching logs from the REST API in our backend, do not modify
writeToOutStream("##[section]:::::");
// This section is used as a delimiter while fetching logs from the REST API in our backend, remove after oidc
writeToOutStream("##[section]:::::"); //remove after oidc

try {
switch (this.commandType) {
Expand Down
27 changes: 22 additions & 5 deletions src/MicrosoftSecurityDevOps/v1/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
"author": "Microsoft Corporation",
"version": {
"Major": 1,
"Minor": 12,
"Patch": 1
"Minor": 18,
"Patch": 0
},
"preview": true,
"minimumAgentVersion": "1.83.0",
"minimumAgentVersion": "3.232.1",
"groups": [
{
"name": "advanced",
Expand Down Expand Up @@ -108,14 +108,31 @@
"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.",
"defaultValue": "CodeAnalysisLogs",
"groupName": "advanced"
},
{
"name": "alternateDevOpsServer",
"label": "Alternate DevOps Server",
"type": "string",
"required": false,
"helpMarkDown": "An alternative DevOps server endpoint for advanced scenarios. This should be left empty.",
"groupName": "advanced"
}
],
"instanceNameFormat": "Run Microsoft Defender for DevOps",
"execution": {
"Node16": {
"Node18": {
"target": "index.js"
},
"Node20": {
"target": "index.js"
},
"Node20_1": {
"target": "index.js"
},
"Node22": {
"target": "index.js"
},
"Node10": {
"Node24": {
"target": "index.js"
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/extension-manifest-debug.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifestVersion": 1,
"id": "microsoft-security-devops-azdevops",
"name": "Microsoft Security DevOps (Debug)",
"version": "1.12.1.0",
"version": "1.18.0.0",
"publisher": "ms-securitydevops",
"description": "Build tasks for performing security analysis.",
"public": false,
Expand Down
2 changes: 1 addition & 1 deletion src/extension-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifestVersion": 1,
"id": "microsoft-security-devops-azdevops",
"name": "Microsoft Security DevOps",
"version": "1.12.1",
"version": "1.18.0",
"publisher": "ms-securitydevops",
"description": "Build tasks for performing security analysis.",
"public": true,
Expand Down