Skip to content
Open
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
39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Build and Test

on:
pull_request:
branches:
- main
- 'release/**'
push:
branches:
- main

permissions:
contents: read

jobs:
test:
name: Build and Test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'

- name: Install dependencies
run: npm install

- name: Compile TypeScript
run: npm run build

- name: Compile Tests
run: npm run buildTests

- name: Run Tests
run: npm test
148 changes: 81 additions & 67 deletions lib/container-mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
Expand All @@ -37,24 +47,28 @@ const https = __importStar(require("https"));
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const os = __importStar(require("os"));
const sendReportRetryCount = 1;
const SEND_REPORT_RETRY_COUNT = 1;
const REQUEST_TIMEOUT_MS = 2500;
const PRE_JOB_FALLBACK_OFFSET_MS = 10000;
const GetScanContextURL = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/auth-push/GetScanContext?context=authOnly";
const ContainerMappingURL = "https://dfdinfra-afdendpoint-prod-d5fqbucbg7fue0cf.z01.azurefd.net/github/v1/container-mappings";
class ContainerMapping {
constructor() {
this.succeedOnError = true;
}
runPreJob() {
try {
core.info("::group::Microsoft Defender for DevOps container mapping pre-job - https://go.microsoft.com/fwlink/?linkid=2231419");
this._runPreJob();
}
catch (error) {
core.info("Error in Container Mapping pre-job: " + error);
}
finally {
core.info("::endgroup::");
}
return __awaiter(this, void 0, void 0, function* () {
try {
core.info("::group::Microsoft Defender for DevOps container mapping pre-job - https://go.microsoft.com/fwlink/?linkid=2231419");
this._runPreJob();
}
catch (error) {
core.warning(`Error in Container Mapping pre-job: ${error}`);
}
finally {
core.info("::endgroup::");
}
});
}
_runPreJob() {
const startTime = new Date().toISOString();
Expand All @@ -72,7 +86,7 @@ class ContainerMapping {
yield this._runPostJob();
}
catch (error) {
core.info("Error in Container Mapping post-job: " + error);
core.warning(`Error in Container Mapping post-job: ${error}`);
}
finally {
core.info("::endgroup::");
Expand All @@ -83,7 +97,7 @@ class ContainerMapping {
return __awaiter(this, void 0, void 0, function* () {
let startTime = core.getState('PreJobStartTime');
if (startTime.length <= 0) {
startTime = new Date(new Date().getTime() - 10000).toISOString();
startTime = new Date(new Date().getTime() - PRE_JOB_FALLBACK_OFFSET_MS).toISOString();
core.debug(`PreJobStartTime not defined, using now-10secs`);
}
core.info(`PreJobStartTime: ${startTime}`);
Expand All @@ -95,34 +109,33 @@ class ContainerMapping {
let bearerToken = yield core.getIDToken()
.then((token) => { return token; })
.catch((error) => {
throw new Error("Unable to get token: " + error);
throw new Error(`Unable to get OIDC token. Ensure the workflow has 'id-token: write' permission. Details: ${error}`);
});
if (!bearerToken) {
throw new Error("Empty OIDC token received");
throw new Error("Empty OIDC token received. Ensure the workflow has 'id-token: write' permission.");
}
var callerIsOnboarded = yield this.checkCallerIsCustomer(bearerToken, sendReportRetryCount);
var callerIsOnboarded = yield this.checkCallerIsCustomer(bearerToken, SEND_REPORT_RETRY_COUNT);
if (!callerIsOnboarded) {
core.info("Client is not onboarded to Defender for DevOps. Skipping container mapping workload.");
core.warning("Client is not onboarded to Defender for DevOps. Skipping container mapping workload.");
return;
}
core.info("Client is onboarded for container mapping.");
let dockerVersionOutput = yield exec.getExecOutput('docker --version');
if (dockerVersionOutput.exitCode != 0) {
core.info(`Unable to get docker version: ${dockerVersionOutput}`);
core.info(`Skipping container mapping since docker not found/available.`);
if (dockerVersionOutput.exitCode !== 0) {
core.warning(`Docker not found or not available. Skipping container mapping. Exit code: ${dockerVersionOutput.exitCode}`);
return;
}
reportData.dockerVersion = dockerVersionOutput.stdout.trim();
yield this.execCommand(`docker events --since ${startTime} --until ${new Date().toISOString()} --filter event=push --filter type=image --format ID={{.ID}}`, reportData.dockerEvents)
.catch((error) => {
throw new Error("Unable to get docker events: " + error);
throw new Error(`Unable to get docker events: ${error}`);
});
yield this.execCommand(`docker images --format CreatedAt={{.CreatedAt}}::Repo={{.Repository}}::Tag={{.Tag}}::Digest={{.Digest}}`, reportData.dockerImages)
.catch((error) => {
throw new Error("Unable to get docker images: " + error);
throw new Error(`Unable to get docker images: ${error}`);
});
core.debug("Finished data collection, starting API calls.");
var reportSent = yield this.sendReport(JSON.stringify(reportData), bearerToken, sendReportRetryCount);
var reportSent = yield this.sendReport(JSON.stringify(reportData), bearerToken, SEND_REPORT_RETRY_COUNT);
if (!reportSent) {
throw new Error("Unable to send report to backend service");
}
Expand All @@ -134,7 +147,7 @@ class ContainerMapping {
return __awaiter(this, void 0, void 0, function* () {
return exec.getExecOutput(command)
.then((result) => {
if (result.exitCode != 0) {
if (result.exitCode !== 0) {
return Promise.reject(`Command execution failed: ${result}`);
}
result.stdout.trim().split(os.EOL).forEach(element => {
Expand All @@ -145,15 +158,15 @@ class ContainerMapping {
});
});
}
sendReport(data, bearerToken, retryCount = 0) {
return __awaiter(this, void 0, void 0, function* () {
sendReport(data_1, bearerToken_1) {
return __awaiter(this, arguments, void 0, function* (data, bearerToken, retryCount = 0) {
core.debug(`attempting to send report: ${data}`);
return yield this._sendReport(data, bearerToken)
.then(() => {
return true;
})
.catch((error) => __awaiter(this, void 0, void 0, function* () {
if (retryCount == 0) {
if (retryCount === 0) {
return false;
}
else {
Expand All @@ -166,29 +179,30 @@ class ContainerMapping {
}
_sendReport(data, bearerToken) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
let apiTime = new Date().getMilliseconds();
let options = {
method: 'POST',
timeout: 2500,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + bearerToken,
'Content-Length': data.length
}
};
core.debug(`${options['method'].toUpperCase()} ${ContainerMappingURL}`);
const apiStartTime = new Date().getTime();
const options = {
method: 'POST',
timeout: REQUEST_TIMEOUT_MS,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'Content-Length': data.length
}
};
core.debug(`${options.method} ${ContainerMappingURL}`);
return new Promise((resolve, reject) => {
const req = https.request(ContainerMappingURL, options, (res) => {
let resData = '';
res.on('data', (chunk) => {
resData += chunk.toString();
});
res.on('end', () => {
core.debug('API calls finished. Time taken: ' + (new Date().getMilliseconds() - apiTime) + "ms");
const elapsed = new Date().getTime() - apiStartTime;
core.debug(`API calls finished. Time taken: ${elapsed}ms`);
core.debug(`Status code: ${res.statusCode} ${res.statusMessage}`);
core.debug('Response headers: ' + JSON.stringify(res.headers));
core.debug(`Response headers: ${JSON.stringify(res.headers)}`);
if (resData.length > 0) {
core.debug('Response: ' + resData);
core.debug(`Response: ${resData}`);
}
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(`Received Failed Status code when calling url: ${res.statusCode} ${resData}`);
Expand All @@ -201,17 +215,17 @@ class ContainerMapping {
});
req.write(data);
req.end();
}));
});
});
}
checkCallerIsCustomer(bearerToken, retryCount = 0) {
return __awaiter(this, void 0, void 0, function* () {
checkCallerIsCustomer(bearerToken_1) {
return __awaiter(this, arguments, void 0, function* (bearerToken, retryCount = 0) {
return yield this._checkCallerIsCustomer(bearerToken)
.then((statusCode) => __awaiter(this, void 0, void 0, function* () {
if (statusCode == 200) {
if (statusCode === 200) {
return true;
}
else if (statusCode == 403) {
else if (statusCode === 403) {
return false;
}
else {
Expand All @@ -227,7 +241,7 @@ class ContainerMapping {
}
retryCall(bearerToken, retryCount) {
return __awaiter(this, void 0, void 0, function* () {
if (retryCount == 0) {
if (retryCount === 0) {
core.info(`All retries failed.`);
return false;
}
Expand All @@ -240,28 +254,28 @@ class ContainerMapping {
}
_checkCallerIsCustomer(bearerToken) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
let options = {
method: 'GET',
timeout: 2500,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + bearerToken,
}
};
core.debug(`${options['method'].toUpperCase()} ${GetScanContextURL}`);
const options = {
method: 'GET',
timeout: REQUEST_TIMEOUT_MS,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
}
};
core.debug(`${options.method} ${GetScanContextURL}`);
return new Promise((resolve, reject) => {
const req = https.request(GetScanContextURL, options, (res) => {
res.on('end', () => {
resolve(res.statusCode);
});
res.on('data', function (d) {
res.on('data', () => {
});
});
req.on('error', (error) => {
reject(new Error(`Error calling url: ${error}`));
});
req.end();
}));
});
});
}
}
Expand Down
26 changes: 18 additions & 8 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
Expand Down Expand Up @@ -54,7 +64,7 @@ function shouldRunMain() {
let toolsString = core.getInput('tools');
if (!common.isNullOrWhiteSpace(toolsString)) {
let tools = toolsString.split(',');
if (tools.length == 1 && tools[0].trim() == msdo_helpers_1.Tools.ContainerMapping) {
if (tools.length === 1 && tools[0].trim() === msdo_helpers_1.Tools.ContainerMapping) {
return false;
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/msdo-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeToOutStream = exports.getEncodedContent = exports.encode = exports.Constants = exports.Tools = exports.RunnerType = exports.Inputs = void 0;
exports.encode = exports.Constants = exports.Tools = exports.RunnerType = exports.Inputs = void 0;
exports.getEncodedContent = getEncodedContent;
exports.writeToOutStream = writeToOutStream;
const os_1 = __importDefault(require("os"));
var Inputs;
(function (Inputs) {
Expand Down Expand Up @@ -49,8 +51,6 @@ function getEncodedContent(dockerVersion, dockerEvents, dockerImages) {
data.push(dockerImages);
return (0, exports.encode)(data.join(os_1.default.EOL));
}
exports.getEncodedContent = getEncodedContent;
function writeToOutStream(data, outStream = process.stdout) {
outStream.write(data.trim() + os_1.default.EOL);
}
exports.writeToOutStream = writeToOutStream;
3 changes: 1 addition & 2 deletions lib/msdo-interface.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getExecutor = void 0;
exports.getExecutor = getExecutor;
function getExecutor(runner) {
return new runner();
}
exports.getExecutor = getExecutor;
Loading
Loading