Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checksum verification of bash script #282

Merged
merged 8 commits into from Apr 16, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions action.yml
Expand Up @@ -59,6 +59,9 @@ inputs:
name:
description: 'User defined upload name. Visible in Codecov UI'
required: false
network_filter:
description: 'Used to restrict the set of git/hg files that can be matched with filenames in the coverage report. This is useful for monorepos or other setups where a full filepath may not be specified in the coverage report, and that shortened filepath may appear multiple times in a directory structure (e.g. __init__.py)'
required: false
override_branch:
description: 'Specify the branch name'
required: false
Expand All @@ -74,9 +77,6 @@ inputs:
override_tag:
description: 'Specify the git tag'
required: false
network_filter:
description: 'Used to restrict the set of git/hg files that can be matched with filenames in the coverage report. This is useful for monorepos or other setups where a full filepath may not be specified in the coverage report, and that shortened filepath may appear multiple times in a directory structure (e.g. __init__.py)'
required: false
path_to_write_report:
description: 'Write upload file to path before uploading'
required: false
Expand Down
241 changes: 206 additions & 35 deletions dist/index.js
Expand Up @@ -13152,62 +13152,119 @@ module.exports = {"$id":"log.json#","$schema":"http://json-schema.org/draft-06/s

"use strict";

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) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var core = __webpack_require__(470);
var exec = __webpack_require__(986);
var fs = __webpack_require__(747);
var request = __webpack_require__(335);
var buildExec_1 = __webpack_require__(983);
var validate_1 = __webpack_require__(743);
var failCi;
try {
request({
json: false,
maxAttempts: 10,
timeout: 3000,
url: 'https://codecov.io/bash',
}, function (error, response, body) {
var _a = buildExec_1["default"](), execArgs = _a.execArgs, options = _a.options, filepath = _a.filepath, failCi = _a.failCi;
try {
if (error && failCi) {
throw error;
}
else if (error) {
core.warning("Codecov warning: " + error.message);
}
fs.writeFile(filepath, body, function (err) {
if (err && failCi) {
throw err;
}
else if (err) {
core.warning("Codecov warning: " + err.message);
}
exec.exec('bash', execArgs, options)["catch"](function (err) {
if (failCi) {
core.setFailed("Codecov failed with the following error: " + err.message);
}, function (error, response, body) { return __awaiter(void 0, void 0, void 0, function () {
var _a, execArgs, options, filepath, failCi, isValid, failure, error_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = buildExec_1["default"](), execArgs = _a.execArgs, options = _a.options, filepath = _a.filepath, failCi = _a.failCi;
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, validate_1["default"](body)];
case 2:
isValid = _b.sent();
if (!isValid) {
failure = 'Codecov failure: ' +
'Bash script checksums do not match published values. ' +
'Please contact security@codecov.io immediately.';
core.setFailed(failure);
throw new Error(failure);
}
else {
core.warning("Codecov warning: " + err.message);
if (error && failCi) {
throw error;
}
})
.then(function () {
unlinkFile();
});
var unlinkFile = function () {
fs.unlink(filepath, function (err) {
else if (error) {
core.warning("Codecov warning: " + error.message);
}
fs.writeFile(filepath, body, function (err) {
if (err && failCi) {
throw err;
}
else if (err) {
core.warning("Codecov warning: " + err.message);
}
exec.exec('bash', execArgs, options)["catch"](function (err) {
if (failCi) {
core.setFailed("Codecov failed with the following error: " + err.message);
}
else {
core.warning("Codecov warning: " + err.message);
}
})
.then(function () {
unlinkFile();
});
var unlinkFile = function () {
fs.unlink(filepath, function (err) {
if (err && failCi) {
throw err;
}
else if (err) {
core.warning("Codecov warning: " + err.message);
}
});
};
});
};
});
}
catch (error) {
core.setFailed("Codecov failed with the following error: " + error.message);
}
});
return [3 /*break*/, 4];
case 3:
error_1 = _b.sent();
core.setFailed("Codecov failed with the following error: " + error_1.message);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); });
}
catch (error) {
if (failCi) {
Expand Down Expand Up @@ -49116,7 +49173,121 @@ module.exports = function (data, opts) {


/***/ }),
/* 743 */,
/* 743 */
/***/ (function(__unusedmodule, exports, __webpack_require__) {

"use strict";

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) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.retrieveChecksum = void 0;
var crypto = __webpack_require__(417);
var core = __webpack_require__(470);
var request = __webpack_require__(335);
var validateUploader = function (body) { return __awaiter(void 0, void 0, void 0, function () {
var version, _i, _a, i, publicChecksum, uploaderChecksum;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
version = getVersion(body);
if (version === null) {
core.warning('Codecov could not identify the bash uploader version.');
return [2 /*return*/, false];
}
_i = 0, _a = [1, 256, 512];
_b.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
i = _a[_i];
return [4 /*yield*/, exports.retrieveChecksum(version, i)];
case 2:
publicChecksum = _b.sent();
uploaderChecksum = calculateChecksum(body, i);
if (uploaderChecksum !== publicChecksum.trim()) {
core.warning("Codecov " + version + " checksums for SHA" + i + " failed to match.\n" +
("Public checksum: " + publicChecksum) +
("Uploader checksum: " + uploaderChecksum));
return [2 /*return*/, false];
}
_b.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, true];
}
});
}); };
var retrieveChecksum = function (version, encryption) { return __awaiter(void 0, void 0, void 0, function () {
var url, response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
url = "https://raw.githubusercontent.com/codecov/codecov-bash/" + version + "/SHA" + encryption + "SUM";
return [4 /*yield*/, request({
maxAttempts: 10,
timeout: 3000,
url: url,
})];
case 1:
response = _a.sent();
if (response.statusCode != 200) {
core.warning("Codecov could not retrieve checksum SHA" + encryption + " at " + url);
return [2 /*return*/, ''];
}
return [2 /*return*/, response.body];
}
});
}); };
exports.retrieveChecksum = retrieveChecksum;
var calculateChecksum = function (body, i) {
var shasum = crypto.createHash("sha" + i);
shasum.update(body);
return shasum.digest('hex') + " codecov";
};
var getVersion = function (body) {
var regex = /VERSION="(.*)+"/g;
var match = regex.exec(body);
return match ? match[1] : null;
};
exports["default"] = validateUploader;


/***/ }),
/* 744 */
/***/ (function(module) {

Expand Down
12 changes: 11 additions & 1 deletion src/index.ts
Expand Up @@ -5,6 +5,7 @@ const fs = require('fs');
const request = require('requestretry');

import buildExec from './buildExec';
import validateUploader from './validate';

let failCi;
try {
Expand All @@ -13,10 +14,19 @@ try {
maxAttempts: 10,
timeout: 3000,
url: 'https://codecov.io/bash',
}, (error, response, body) => {
}, async (error, response, body) => {
const {execArgs, options, filepath, failCi} = buildExec();

try {
const isValid = await validateUploader(body);
if (!isValid) {
const failure = 'Codecov failure: ' +
'Bash script checksums do not match published values. ' +
'Please contact security@codecov.io immediately.';
core.setFailed(failure);
throw new Error(failure);
}

if (error && failCi) {
throw error;
} else if (error) {
Expand Down
39 changes: 39 additions & 0 deletions src/validate.test.ts
@@ -0,0 +1,39 @@
import validateUploader, {retrieveChecksum} from './validate';

const request = require('requestretry');

const bashScript = (async () => {
try {
const script = await request({
json: false,
maxAttempts: 10,
timeout: 3000,
url: 'https://codecov.io/bash',
});
return script.body;
} catch (err) {
throw err;
}
});

test('valid checksums', async () => {
const valid = await validateUploader(await bashScript());
expect(valid).toBeTruthy();
});

test('invalid checksums', async () => {
const script = await bashScript();
const valid = await validateUploader(script.substring(0, script.length - 1));
expect(valid).toBeFalsy();
});

test('invalid script version', async () => {
const script = await bashScript();
const valid = await validateUploader(script.substring(0, 20));
expect(valid).toBeFalsy();
});

test('invalid public checksum file', async () => {
const checksum = await retrieveChecksum('foo', 'bar');
expect(checksum).toBeFalsy();
});