commit
866e511e6d
14 changed files with 445 additions and 304 deletions
14
README.md
14
README.md
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
Harden-Runner GitHub Action installs a security agent on the GitHub-hosted runner (Ubuntu VM) to
|
||||
|
||||
1. Monitor the build process,
|
||||
2. Prevent exfiltration of credentials, and
|
||||
1. Monitor the build process
|
||||
2. Prevent exfiltration of credentials
|
||||
3. Detect compromised dependencies or build tools
|
||||
|
||||
<p align="left">
|
||||
|
|
@ -12,11 +12,11 @@ Harden-Runner GitHub Action installs a security agent on the GitHub-hosted runne
|
|||
|
||||
## Why
|
||||
|
||||
Hijacked dependencies and compromised build tools typically make outbound requests during the build process to exfiltrate data or credentials. There is also a risk that a compromised dependency or build tool may modify source code, dependencies, or artifacts.
|
||||
Hijacked dependencies and compromised build tools typically make outbound requests to exfiltrate data or credentials, or may modify source code, dependencies, or artifacts during the build.
|
||||
|
||||
Harden-Runner automatically correlates outbound traffic, file modifications, and process activity with each step of a workflow. You can also set a policy to restrict outbound traffic.
|
||||
|
||||
Check out the [hands-on tutorials](https://github.com/step-security/supply-chain-goat) to learn how `harden-runner` would have prevented past software supply chain attacks.
|
||||
Check out the [hands-on tutorials](https://github.com/step-security/supply-chain-goat) to learn how Harden-Runner would have prevented past supply chain attacks and read this [blog post](https://infosecwriteups.com/detecting-malware-packages-in-github-actions-7b93a9985635) on how Harden-Runner detected malicious packages.
|
||||
|
||||
## How
|
||||
|
||||
|
|
@ -63,6 +63,12 @@ If you have questions or ideas, please use [discussions](https://github.com/step
|
|||
4. [Cryptographically verify tools run as part of the CI/ CD pipeline](https://github.com/step-security/harden-runner/discussions/94)
|
||||
5. [Automatic signing](https://github.com/step-security/harden-runner/discussions/77)
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Harden-Runner GitHub Action only works for GitHub-hosted runners. Self-hosted runners are not supported.
|
||||
2. Only Ubuntu VM is supported. Windows and MacOS GitHub-hosted runners are not supported. There is a discussion about that [here](https://github.com/step-security/harden-runner/discussions/121).
|
||||
3. Harden-Runner is not supported when [job is run in a container](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) as it needs sudo access on the Ubuntu VM to run.
|
||||
|
||||
## Testimonials
|
||||
|
||||
> _I think this is a great idea and for the threat model of build-time, an immediate network egress request monitoring makes a lot of sense_ - [Liran Tal](https://stars.github.com/profiles/lirantal/), GitHub Star, and Author of Essential Node.js Security
|
||||
|
|
|
|||
42
dist/index.js
vendored
42
dist/index.js
vendored
|
|
@ -1696,9 +1696,44 @@ function printInfo(web_url) {
|
|||
console.log("\x1b[32m%s\x1b[0m", "View security insights and recommended policy at:");
|
||||
console.log(`${web_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}`);
|
||||
}
|
||||
const CONTAINER_MESSAGE = "This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
const UBUNTU_MESSAGE = "This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
|
||||
var core = __nccwpck_require__(186);
|
||||
;// CONCATENATED MODULE: external "node:fs"
|
||||
const external_node_fs_namespaceObject = require("node:fs");
|
||||
;// CONCATENATED MODULE: ./node_modules/is-docker/index.js
|
||||
|
||||
|
||||
let isDockerCached;
|
||||
|
||||
function hasDockerEnv() {
|
||||
try {
|
||||
external_node_fs_namespaceObject.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasDockerCGroup() {
|
||||
try {
|
||||
return external_node_fs_namespaceObject.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isDocker() {
|
||||
// TODO: Use `??=` when targeting Node.js 16.
|
||||
if (isDockerCached === undefined) {
|
||||
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
||||
}
|
||||
|
||||
return isDockerCached;
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/index.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
|
|
@ -1711,9 +1746,14 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|||
};
|
||||
|
||||
|
||||
|
||||
(() => __awaiter(void 0, void 0, void 0, function* () {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (core.getBooleanInput("disable-telemetry") &&
|
||||
|
|
|
|||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
49
dist/post/index.js
vendored
49
dist/post/index.js
vendored
|
|
@ -1697,6 +1697,47 @@ var external_fs_ = __nccwpck_require__(747);
|
|||
const external_child_process_namespaceObject = require("child_process");
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
|
||||
var core = __nccwpck_require__(186);
|
||||
;// CONCATENATED MODULE: ./src/common.ts
|
||||
function printInfo(web_url) {
|
||||
console.log("\x1b[32m%s\x1b[0m", "View security insights and recommended policy at:");
|
||||
console.log(`${web_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}`);
|
||||
}
|
||||
const CONTAINER_MESSAGE = "This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
const UBUNTU_MESSAGE = "This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
|
||||
;// CONCATENATED MODULE: external "node:fs"
|
||||
const external_node_fs_namespaceObject = require("node:fs");
|
||||
;// CONCATENATED MODULE: ./node_modules/is-docker/index.js
|
||||
|
||||
|
||||
let isDockerCached;
|
||||
|
||||
function hasDockerEnv() {
|
||||
try {
|
||||
external_node_fs_namespaceObject.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasDockerCGroup() {
|
||||
try {
|
||||
return external_node_fs_namespaceObject.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isDocker() {
|
||||
// TODO: Use `??=` when targeting Node.js 16.
|
||||
if (isDockerCached === undefined) {
|
||||
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
||||
}
|
||||
|
||||
return isDockerCached;
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/cleanup.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
|
|
@ -1710,9 +1751,15 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
(() => __awaiter(void 0, void 0, void 0, function* () {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
external_fs_.writeFileSync("/home/agent/post_event.json", JSON.stringify({ event: "post" }));
|
||||
|
|
|
|||
2
dist/post/index.js.map
vendored
2
dist/post/index.js.map
vendored
File diff suppressed because one or more lines are too long
46
dist/pre/index.js
vendored
46
dist/pre/index.js
vendored
|
|
@ -6258,6 +6258,8 @@ function printInfo(web_url) {
|
|||
console.log("\x1b[32m%s\x1b[0m", "View security insights and recommended policy at:");
|
||||
console.log(`${web_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}`);
|
||||
}
|
||||
const CONTAINER_MESSAGE = "This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
const UBUNTU_MESSAGE = "This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js
|
||||
var tool_cache = __nccwpck_require__(7784);
|
||||
|
|
@ -6272,13 +6274,46 @@ function verifyChecksum(downloadPath) {
|
|||
const checksum = external_crypto_.createHash("sha256")
|
||||
.update(fileBuffer)
|
||||
.digest("hex"); // checksum of downloaded file
|
||||
const expectedChecksum = "8a8d304cb1e413f0fd2c1dffacefc0d91ba693eee2040f4ea7893ef29f3f10b1"; // checksum for v0.9.1
|
||||
const expectedChecksum = "c9fa91c602954155391c9da6318560d7fb5998155660e4948175c9ab8690716c"; // checksum for v0.9.3
|
||||
if (checksum !== expectedChecksum) {
|
||||
core.setFailed(`Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`);
|
||||
}
|
||||
core.debug("Checksum verification passed.");
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: external "node:fs"
|
||||
const external_node_fs_namespaceObject = require("node:fs");
|
||||
;// CONCATENATED MODULE: ./node_modules/is-docker/index.js
|
||||
|
||||
|
||||
let isDockerCached;
|
||||
|
||||
function hasDockerEnv() {
|
||||
try {
|
||||
external_node_fs_namespaceObject.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasDockerCGroup() {
|
||||
try {
|
||||
return external_node_fs_namespaceObject.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isDocker() {
|
||||
// TODO: Use `??=` when targeting Node.js 16.
|
||||
if (isDockerCached === undefined) {
|
||||
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
||||
}
|
||||
|
||||
return isDockerCached;
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/setup.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
|
|
@ -6298,10 +6333,15 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|||
|
||||
|
||||
|
||||
|
||||
(() => __awaiter(void 0, void 0, void 0, function* () {
|
||||
try {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
var correlation_id = v4();
|
||||
|
|
@ -6343,7 +6383,7 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|||
// Note: to avoid github rate limiting
|
||||
let token = core.getInput("token");
|
||||
let auth = `token ${token}`;
|
||||
const downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.9.1/agent_0.9.1_linux_amd64.tar.gz", undefined, auth);
|
||||
const downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.9.3/agent_0.9.3_linux_amd64.tar.gz", undefined, auth);
|
||||
verifyChecksum(downloadPath); // NOTE: verifying agent's checksum, before extracting
|
||||
const extractPath = yield tool_cache.extractTar(downloadPath);
|
||||
console.log(`Step Security Job Correlation ID: ${correlation_id}`);
|
||||
|
|
|
|||
2
dist/pre/index.js.map
vendored
2
dist/pre/index.js.map
vendored
File diff suppressed because one or more lines are too long
546
package-lock.json
generated
546
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "step-security-harden-runner",
|
||||
"version": "1.4.0",
|
||||
"description": "Security monitoring for the GitHub-hosted runner",
|
||||
"version": "1.4.2",
|
||||
"description": "Security agent for GitHub-hosted runner to monitor the build process",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "npm run main && npm run pre && npm run post",
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/tool-cache": "^1.7.1",
|
||||
"ansi-regex": ">=5.0.1",
|
||||
"is-docker": "^3.0.0",
|
||||
"node-fetch": ">=3.2.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function verifyChecksum(downloadPath: string) {
|
|||
.digest("hex"); // checksum of downloaded file
|
||||
|
||||
const expectedChecksum: string =
|
||||
"8a8d304cb1e413f0fd2c1dffacefc0d91ba693eee2040f4ea7893ef29f3f10b1"; // checksum for v0.9.1
|
||||
"c9fa91c602954155391c9da6318560d7fb5998155660e4948175c9ab8690716c"; // checksum for v0.9.3
|
||||
|
||||
if (checksum !== expectedChecksum) {
|
||||
core.setFailed(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import * as fs from "fs";
|
||||
import * as cp from "child_process";
|
||||
import * as core from "@actions/core";
|
||||
import * as common from "./common";
|
||||
import isDocker from "is-docker";
|
||||
|
||||
(async () => {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(common.CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,9 @@ export function printInfo(web_url) {
|
|||
`${web_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}`
|
||||
);
|
||||
}
|
||||
|
||||
export const CONTAINER_MESSAGE =
|
||||
"This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
|
||||
export const UBUNTU_MESSAGE =
|
||||
"This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
|
|
|
|||
11
src/index.ts
11
src/index.ts
|
|
@ -1,9 +1,14 @@
|
|||
import { printInfo } from "./common";
|
||||
import * as common from "./common";
|
||||
import * as core from "@actions/core";
|
||||
import isDocker from "is-docker";
|
||||
|
||||
(async () => {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(common.CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +21,6 @@ import * as core from "@actions/core";
|
|||
);
|
||||
} else {
|
||||
var web_url = "https://app.stepsecurity.io";
|
||||
printInfo(web_url);
|
||||
common.printInfo(web_url);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
14
src/setup.ts
14
src/setup.ts
|
|
@ -4,13 +4,19 @@ import * as fs from "fs";
|
|||
import * as httpm from "@actions/http-client";
|
||||
import * as path from "path";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { printInfo } from "./common";
|
||||
import * as common from "./common";
|
||||
import * as tc from "@actions/tool-cache";
|
||||
import { verifyChecksum } from "./checksum";
|
||||
import isDocker from "is-docker";
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("Only runs on linux");
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isDocker()) {
|
||||
console.log(common.CONTAINER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +71,7 @@ import { verifyChecksum } from "./checksum";
|
|||
let auth = `token ${token}`;
|
||||
|
||||
const downloadPath: string = await tc.downloadTool(
|
||||
"https://github.com/step-security/agent/releases/download/v0.9.1/agent_0.9.1_linux_amd64.tar.gz",
|
||||
"https://github.com/step-security/agent/releases/download/v0.9.3/agent_0.9.3_linux_amd64.tar.gz",
|
||||
undefined,
|
||||
auth
|
||||
);
|
||||
|
|
@ -76,7 +82,7 @@ import { verifyChecksum } from "./checksum";
|
|||
console.log(`Step Security Job Correlation ID: ${correlation_id}`);
|
||||
|
||||
if (!confg.disable_telemetry || confg.egress_policy === "audit") {
|
||||
printInfo(web_url);
|
||||
common.printInfo(web_url);
|
||||
}
|
||||
|
||||
let cmd = "cp",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue