1
0
Fork 0
mirror of synced 2026-06-05 16:28:19 +00:00

Release v2.19.0 (#661)

This commit is contained in:
Varun Sharma 2026-04-20 00:33:22 -07:00 committed by GitHub
commit 8d3c67de8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 691 additions and 35 deletions

13
dist/index.js vendored
View file

@ -31913,6 +31913,19 @@ function isAgentInstalled(platform) {
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) { function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled; return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
} }
function detectThirdPartyRunnerProvider() {
var _a;
if (process.env["DEPOT_RUNNER"] === "1")
return "depot";
if (process.env["NAMESPACE_GITHUB_RUNTIME"])
return "namespace";
const runnerName = (_a = process.env["RUNNER_NAME"]) !== null && _a !== void 0 ? _a : "";
if (runnerName.startsWith("warp-"))
return "warp";
if (runnerName.startsWith("blacksmith-"))
return "blacksmith";
return null;
}
function utils_getAnnotationLogs(platform) { function utils_getAnnotationLogs(platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

52
dist/post/index.js vendored
View file

@ -31919,6 +31919,19 @@ function isAgentInstalled(platform) {
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) { function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled; return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
} }
function detectThirdPartyRunnerProvider() {
var _a;
if (process.env["DEPOT_RUNNER"] === "1")
return "depot";
if (process.env["NAMESPACE_GITHUB_RUNTIME"])
return "namespace";
const runnerName = (_a = process.env["RUNNER_NAME"]) !== null && _a !== void 0 ? _a : "";
if (runnerName.startsWith("warp-"))
return "warp";
if (runnerName.startsWith("blacksmith-"))
return "blacksmith";
return null;
}
function getAnnotationLogs(platform) { function getAnnotationLogs(platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":
@ -32205,6 +32218,7 @@ var cleanup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _
console.log(`[!] ${ARC_RUNNER_MESSAGE}`); console.log(`[!] ${ARC_RUNNER_MESSAGE}`);
return; return;
} }
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (process.env.STATE_selfHosted === "true") { if (process.env.STATE_selfHosted === "true") {
return; return;
} }
@ -32218,7 +32232,12 @@ var cleanup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _
} }
switch (process.platform) { switch (process.platform) {
case "linux": case "linux":
yield handleLinuxCleanup(); if (thirdPartyProvider) {
yield handleAgentBravoCleanup();
}
else {
yield handleLinuxCleanup();
}
break; break;
case "win32": case "win32":
yield handleWindowsCleanup(); yield handleWindowsCleanup();
@ -32234,6 +32253,37 @@ var cleanup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _
console.log(exception); console.log(exception);
} }
}))(); }))();
function handleAgentBravoCleanup() {
return cleanup_awaiter(this, void 0, void 0, function* () {
external_child_process_.execFileSync("/usr/bin/echo", ["step_policy_jobend"]);
const doneFile = "/home/agent/done.json";
let counter = 0;
while (true) {
if (!external_fs_.existsSync(doneFile)) {
counter++;
if (counter > 10) {
console.log("timed out");
break;
}
yield sleep(1000);
}
else {
console.log(external_fs_.readFileSync(doneFile, "utf-8"));
break;
}
}
const log = "/home/agent/agent.log";
if (external_fs_.existsSync(log)) {
console.log("log:");
console.log(external_fs_.readFileSync(log, "utf-8"));
}
const status = "/home/agent/agent.status";
if (external_fs_.existsSync(status)) {
console.log("status:");
console.log(external_fs_.readFileSync(status, "utf-8"));
}
});
}
function handleLinuxCleanup() { function handleLinuxCleanup() {
return cleanup_awaiter(this, void 0, void 0, function* () { return cleanup_awaiter(this, void 0, void 0, function* () {
if (process.env.STATE_isTLS === "false" && process.arch === "arm64") { if (process.env.STATE_isTLS === "false" && process.arch === "arm64") {

File diff suppressed because one or more lines are too long

175
dist/pre/index.js vendored
View file

@ -84976,6 +84976,7 @@ __nccwpck_require__.r(__webpack_exports__);
// EXPORTS // EXPORTS
__nccwpck_require__.d(__webpack_exports__, { __nccwpck_require__.d(__webpack_exports__, {
installAgentForBravo: () => (/* binding */ installAgentForBravo),
installAgentForSelfHosted: () => (/* binding */ installAgentForSelfHosted), installAgentForSelfHosted: () => (/* binding */ installAgentForSelfHosted),
sleep: () => (/* binding */ setup_sleep) sleep: () => (/* binding */ setup_sleep)
}); });
@ -85037,6 +85038,19 @@ function isAgentInstalled(platform) {
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) { function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled; return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
} }
function detectThirdPartyRunnerProvider() {
var _a;
if (process.env["DEPOT_RUNNER"] === "1")
return "depot";
if (process.env["NAMESPACE_GITHUB_RUNTIME"])
return "namespace";
const runnerName = (_a = process.env["RUNNER_NAME"]) !== null && _a !== void 0 ? _a : "";
if (runnerName.startsWith("warp-"))
return "warp";
if (runnerName.startsWith("blacksmith-"))
return "blacksmith";
return null;
}
function utils_getAnnotationLogs(platform) { function utils_getAnnotationLogs(platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":
@ -85441,19 +85455,23 @@ var external_crypto_ = __nccwpck_require__(6982);
const CHECKSUMS = { const CHECKSUMS = {
tls: { tls: {
amd64: "86d042adcdc03eb1ea50d35d265da47622a6d0aedef9657f84ce1eb7f04d6057", amd64: "713c91e921292027dacf446db44bafbc8e36a3f7f51dff664ba681c6e4398a05",
arm64: "ea1074a2358d50db9a9fe18ae3971b87305cda63f262c494a5f43b25f4e524ce", arm64: "2c1eb365d6d9ae4cd4b6632a5f833bcdb7e75d0d9604de3391ff22e4e28e8d42",
}, },
non_tls: { non_tls: {
amd64: "4aaaeebbe10e619d8ce13e8cc4a1acbafc8f891e8cdd319984480b9ec08407b8", // v0.15.0 amd64: "e38de61e1afd98dd339bb9acce4996183875d482be1638fb198ab02b3e25bbef", // v0.16.0
}, },
darwin: "797399a3a3f6f9c4c000a02e0d8c7b16499129c9bdc2ad9cf2a10072c10654fb", bravo: {
amd64: "8d002af0c1c4bb73eaef0f2b641f7aa353cc3f4da36a4e418b69895a2baa922c",
arm64: "1ce74a30d704c2e994246fc809d65af83e3f354aae7b9080b2c2eaee715cf005",
},
darwin: "fe26a1f6af4afe9f1a854d8633832f5d18ab542827003cae445b3a64021d612c",
windows: { windows: {
amd64: "e98f8b9cf9ecf6566f1e16a470fbe4aef01610a644fd8203a1bab3ff142186c8", // v1.0.0 amd64: "93f1e5d87c6647e6eca7963d5f4b4bd73107029430f8e6945ffece93007a89f5", // v1.0.2
}, },
}; };
// verifyChecksum returns true if checksum is valid // verifyChecksum returns true if checksum is valid
function verifyChecksum(downloadPath, isTLS, variant, platform) { function verifyChecksum(downloadPath, isTLS, variant, platform, agentType = "default") {
const fileBuffer = external_fs_.readFileSync(downloadPath); const fileBuffer = external_fs_.readFileSync(downloadPath);
const checksum = external_crypto_.createHash("sha256") const checksum = external_crypto_.createHash("sha256")
.update(fileBuffer) .update(fileBuffer)
@ -85461,9 +85479,14 @@ function verifyChecksum(downloadPath, isTLS, variant, platform) {
let expectedChecksum = ""; let expectedChecksum = "";
switch (platform) { switch (platform) {
case "linux": case "linux":
expectedChecksum = isTLS if (agentType === "bravo") {
? CHECKSUMS["tls"][variant] expectedChecksum = CHECKSUMS["bravo"][variant];
: CHECKSUMS["non_tls"][variant]; }
else {
expectedChecksum = isTLS
? CHECKSUMS["tls"][variant]
: CHECKSUMS["non_tls"][variant];
}
break; break;
case "darwin": case "darwin":
expectedChecksum = CHECKSUMS["darwin"]; expectedChecksum = CHECKSUMS["darwin"];
@ -85513,14 +85536,14 @@ function installAgent(isTLS, configStr) {
encoding: "utf8", encoding: "utf8",
}); });
if (isTLS) { if (isTLS) {
downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-ebpf/releases/download/v1.8.0/harden-runner_1.8.0_linux_${variant}.tar.gz`, undefined, auth); downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-ebpf/releases/download/v1.8.2/harden-runner_1.8.2_linux_${variant}.tar.gz`, undefined, auth);
} }
else { else {
if (variant === "arm64") { if (variant === "arm64") {
console.log(ARM64_RUNNER_MESSAGE); console.log(ARM64_RUNNER_MESSAGE);
return false; return false;
} }
downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.15.0/agent_0.15.0_linux_amd64.tar.gz", undefined, auth); downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.16.0/agent_0.16.0_linux_amd64.tar.gz", undefined, auth);
} }
if (!verifyChecksum(downloadPath, isTLS, variant, "linux")) { if (!verifyChecksum(downloadPath, isTLS, variant, "linux")) {
return false; return false;
@ -85542,6 +85565,51 @@ function installAgent(isTLS, configStr) {
return true; return true;
}); });
} }
function installAgentBravo(configStr) {
return install_agent_awaiter(this, void 0, void 0, function* () {
// Note: to avoid github rate limiting
const token = lib_core.getInput("token", { required: true });
const auth = `token ${token}`;
const variant = process.arch === "x64" ? "amd64" : "arm64";
const downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-ebpf/releases/download/v1.8.2/harden-runner-bravo_1.8.2_linux_${variant}.tar.gz`, undefined, auth);
if (!verifyChecksum(downloadPath, true, variant, "linux", "bravo")) {
return false;
}
const extractPath = yield tool_cache.extractTar(downloadPath);
external_child_process_.execFileSync("cp", [external_path_.join(extractPath, "agent"), "/home/agent/agent"]);
external_child_process_.execSync("chmod +x /home/agent/agent");
external_fs_.writeFileSync("/home/agent/agent.json", configStr);
const logStream = external_fs_.openSync("/home/agent/agent.stdout", "a");
const agentProcess = external_child_process_.spawn("sudo", ["/home/agent/agent"], {
cwd: "/home/agent",
detached: true,
stdio: ["ignore", logStream, logStream],
});
agentProcess.unref();
const agentStatus = "/home/agent/agent.status";
const deadline = Date.now() + 10000;
while (true) {
if (!external_fs_.existsSync(agentStatus)) {
if (Date.now() >= deadline) {
console.log("timed out waiting for bravo agent");
if (external_fs_.existsSync("/home/agent/agent.stdout")) {
console.log(external_fs_.readFileSync("/home/agent/agent.stdout", "utf-8"));
}
if (external_fs_.existsSync("/home/agent/agent.log")) {
console.log(external_fs_.readFileSync("/home/agent/agent.log", "utf-8"));
}
break;
}
yield new Promise((resolve) => setTimeout(resolve, 300));
}
else {
console.log(external_fs_.readFileSync(agentStatus, "utf-8"));
break;
}
}
return true;
});
}
function installMacosAgent(configStr) { function installMacosAgent(configStr) {
return install_agent_awaiter(this, void 0, void 0, function* () { return install_agent_awaiter(this, void 0, void 0, function* () {
const token = lib_core.getInput("token", { required: true }); const token = lib_core.getInput("token", { required: true });
@ -85557,7 +85625,7 @@ function installMacosAgent(configStr) {
external_fs_.writeFileSync("/opt/step-security/agent.json", configStr); external_fs_.writeFileSync("/opt/step-security/agent.json", configStr);
lib_core.info("✓ Successfully created agent.json at /opt/step-security/agent.json"); lib_core.info("✓ Successfully created agent.json at /opt/step-security/agent.json");
// Download installer package // Download installer package
const downloadUrl = "https://github.com/step-security/agent-releases/releases/download/v0.0.4-mac/macos-installer-0.0.4.tar.gz"; const downloadUrl = "https://github.com/step-security/agent-releases/releases/download/v0.0.5-mac/macos-installer-0.0.5.tar.gz";
lib_core.info(`Downloading macOS installer.. : ${downloadUrl}`); lib_core.info(`Downloading macOS installer.. : ${downloadUrl}`);
const downloadPath = yield tool_cache.downloadTool(downloadUrl, undefined, auth); const downloadPath = yield tool_cache.downloadTool(downloadUrl, undefined, auth);
lib_core.info(`✓ Successfully downloaded installer to: ${downloadPath}`); lib_core.info(`✓ Successfully downloaded installer to: ${downloadPath}`);
@ -85622,7 +85690,7 @@ function installWindowsAgent(configStr) {
encoding: "utf8", encoding: "utf8",
}); });
const agentExePath = external_path_.join(agentDir, "agent.exe"); const agentExePath = external_path_.join(agentDir, "agent.exe");
const downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-releases/releases/download/v1.0.0-win/harden-runner-agent-windows_1.0.0_windows_amd64.tar.gz`, undefined, auth); const downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-releases/releases/download/v1.0.2-win/harden-runner-agent-windows_1.0.2_windows_amd64.tar.gz`, undefined, auth);
// validate the checksum // validate the checksum
if (!verifyChecksum(downloadPath, false, variant, process.platform)) { if (!verifyChecksum(downloadPath, false, variant, process.platform)) {
return false; return false;
@ -85662,6 +85730,27 @@ function installWindowsAgent(configStr) {
}); });
} }
;// CONCATENATED MODULE: ./src/bravo-config.ts
function buildBravoConfig(confg) {
return {
repo: confg.repo,
run_id: confg.run_id,
correlation_id: confg.correlation_id,
working_directory: confg.working_directory,
api_url: confg.api_url,
telemetry_url: confg.telemetry_url,
one_time_key: confg.one_time_key,
allowed_endpoints: confg.allowed_endpoints,
egress_policy: confg.egress_policy,
disable_telemetry: confg.disable_telemetry,
disable_sudo: confg.disable_sudo,
disable_sudo_and_containers: confg.disable_sudo_and_containers,
disable_file_monitoring: confg.disable_file_monitoring,
private: confg.private,
is_github_hosted: true,
};
}
;// CONCATENATED MODULE: ./src/setup.ts ;// CONCATENATED MODULE: ./src/setup.ts
var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@ -85703,6 +85792,7 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
(() => setup_awaiter(void 0, void 0, void 0, function* () { (() => setup_awaiter(void 0, void 0, void 0, function* () {
@ -85896,6 +85986,19 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
const runnerName = process.env.RUNNER_NAME || ""; const runnerName = process.env.RUNNER_NAME || "";
lib_core.info(`RUNNER_NAME: ${runnerName}`); lib_core.info(`RUNNER_NAME: ${runnerName}`);
if (!isGithubHosted()) { if (!isGithubHosted()) {
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (thirdPartyProvider) {
const providerLabel = thirdPartyProvider.charAt(0).toUpperCase() + thirdPartyProvider.slice(1);
if (process.platform !== "linux") {
lib_core.info(`Detected ${providerLabel} runner on ${process.platform}. Bravo agent is Linux-only, skipping install.`);
return;
}
lib_core.info(`Detected ${providerLabel} runner environment. Installing agent-bravo.`);
confg.correlation_id = runnerName || confg.correlation_id;
yield callMonitorEndpoint(api_url, confg);
yield installAgentForBravo(github.context.repo.owner, confg);
return;
}
external_fs_.appendFileSync(process.env.GITHUB_STATE, `selfHosted=true${external_os_.EOL}`, { external_fs_.appendFileSync(process.env.GITHUB_STATE, `selfHosted=true${external_os_.EOL}`, {
encoding: "utf8", encoding: "utf8",
}); });
@ -86041,6 +86144,33 @@ function setup_sleep(ms) {
setTimeout(resolve, ms); setTimeout(resolve, ms);
}); });
} }
function callMonitorEndpoint(api_url, confg) {
return setup_awaiter(this, void 0, void 0, function* () {
const _http = new lib.HttpClient();
_http.requestOptions = { socketTimeout: 3 * 1000 };
let statusCode;
let addSummary = "false";
try {
const monitorRequestData = {
correlation_id: confg.correlation_id,
job: process.env["GITHUB_JOB"],
};
const resp = yield _http.postJson(`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`, monitorRequestData);
statusCode = resp.statusCode;
if (resp.statusCode === 200 && resp.result) {
console.log(`Runner IP Address: ${resp.result.runner_ip_address}`);
confg.one_time_key = resp.result.one_time_key;
addSummary = resp.result.monitoring_started ? "true" : "false";
}
}
catch (e) {
console.log(`error in connecting to ${api_url}: ${e}`);
}
external_fs_.appendFileSync(process.env.GITHUB_STATE, `monitorStatusCode=${statusCode}${external_os_.EOL}`, { encoding: "utf8" });
external_fs_.appendFileSync(process.env.GITHUB_STATE, `addSummary=${addSummary}${external_os_.EOL}`, { encoding: "utf8" });
external_fs_.appendFileSync(process.env.GITHUB_STATE, `correlation_id=${confg.correlation_id}${external_os_.EOL}`, { encoding: "utf8" });
});
}
function installAgentForSelfHosted(owner, confg) { function installAgentForSelfHosted(owner, confg) {
return setup_awaiter(this, void 0, void 0, function* () { return setup_awaiter(this, void 0, void 0, function* () {
try { try {
@ -86097,6 +86227,25 @@ function installAgentForSelfHosted(owner, confg) {
} }
}); });
} }
function installAgentForBravo(owner, confg) {
return setup_awaiter(this, void 0, void 0, function* () {
try {
console.log("Installing Harden Runner bravo agent for third-party runner");
let isTLS = yield isTLSEnabled(owner);
if (!isTLS) {
console.log("TLS is not enabled for this organization. Bravo agent installation skipped.");
return;
}
const bravoConfigStr = JSON.stringify(buildBravoConfig(confg));
external_child_process_.execSync("sudo mkdir -p /home/agent");
chownForFolder(process.env.USER, "/home/agent");
yield installAgentBravo(bravoConfigStr);
}
catch (error) {
console.log(`Failed to install bravo agent: ${error.message}`);
}
});
}
})(); })();

File diff suppressed because one or more lines are too long

81
src/bravo-config.test.ts Normal file
View file

@ -0,0 +1,81 @@
import { buildBravoConfig } from "./bravo-config";
import { Configuration } from "./interfaces";
const base: Configuration = {
repo: "org/repo",
run_id: "123",
correlation_id: "depot-abc",
working_directory: "/w",
api_url: "https://int.api.stepsecurity.io/v1",
telemetry_url: "https://int.app-api.stepsecurity.io/v1",
allowed_endpoints: "github.com:443",
egress_policy: "audit",
disable_telemetry: false,
disable_sudo: false,
disable_sudo_and_containers: false,
disable_file_monitoring: false,
is_github_hosted: false,
private: "true" as unknown as string,
is_debug: false,
one_time_key: "otk-xyz",
api_key: "tenant-key",
use_policy_store: false,
deploy_on_self_hosted_vm: false,
};
describe("buildBravoConfig", () => {
test("forces is_github_hosted=true so agent honors passed correlation_id", () => {
expect(buildBravoConfig(base).is_github_hosted).toBe(true);
});
test("omits api_key (agent authenticates via one_time_key, not vm-api-key)", () => {
expect(buildBravoConfig(base)).not.toHaveProperty("api_key");
});
test("omits customer (server infers tenant from repo)", () => {
expect(buildBravoConfig(base)).not.toHaveProperty("customer");
});
test("omits use_policy_store (action-side concern, not agent)", () => {
expect(buildBravoConfig(base)).not.toHaveProperty("use_policy_store");
});
test("forwards telemetry_url so network events hit configured env", () => {
expect(buildBravoConfig(base).telemetry_url).toBe(base.telemetry_url);
});
test("forwards one_time_key so agent can auth to presigned URL endpoint", () => {
expect(buildBravoConfig(base).one_time_key).toBe("otk-xyz");
});
test("forwards repo, run_id, correlation_id so server can attribute events", () => {
const cfg = buildBravoConfig(base);
expect(cfg.repo).toBe("org/repo");
expect(cfg.run_id).toBe("123");
expect(cfg.correlation_id).toBe("depot-abc");
});
test("forwards private flag", () => {
expect(buildBravoConfig(base).private).toBe(base.private);
});
test("forwards egress_policy and allowed_endpoints", () => {
const cfg = buildBravoConfig(base);
expect(cfg.egress_policy).toBe("audit");
expect(cfg.allowed_endpoints).toBe("github.com:443");
});
test("forwards disable_* flags", () => {
const cfg = buildBravoConfig({
...base,
disable_telemetry: true,
disable_sudo: true,
disable_sudo_and_containers: true,
disable_file_monitoring: true,
});
expect(cfg.disable_telemetry).toBe(true);
expect(cfg.disable_sudo).toBe(true);
expect(cfg.disable_sudo_and_containers).toBe(true);
expect(cfg.disable_file_monitoring).toBe(true);
});
});

21
src/bravo-config.ts Normal file
View file

@ -0,0 +1,21 @@
import { Configuration } from "./interfaces";
export function buildBravoConfig(confg: Configuration) {
return {
repo: confg.repo,
run_id: confg.run_id,
correlation_id: confg.correlation_id,
working_directory: confg.working_directory,
api_url: confg.api_url,
telemetry_url: confg.telemetry_url,
one_time_key: confg.one_time_key,
allowed_endpoints: confg.allowed_endpoints,
egress_policy: confg.egress_policy,
disable_telemetry: confg.disable_telemetry,
disable_sudo: confg.disable_sudo,
disable_sudo_and_containers: confg.disable_sudo_and_containers,
disable_file_monitoring: confg.disable_file_monitoring,
private: confg.private,
is_github_hosted: true,
};
}

98
src/checksum.test.ts Normal file
View file

@ -0,0 +1,98 @@
import * as fs from "fs";
import * as crypto from "crypto";
import * as core from "@actions/core";
import { verifyChecksum, CHECKSUMS } from "./checksum";
jest.mock("fs", () => ({
...jest.requireActual("fs"),
readFileSync: jest.fn(),
}));
jest.mock("crypto", () => ({
...jest.requireActual("crypto"),
createHash: jest.fn(),
}));
jest.mock("@actions/core");
const mockReadFile = fs.readFileSync as jest.MockedFunction<typeof fs.readFileSync>;
const mockSetFailed = core.setFailed as jest.MockedFunction<typeof core.setFailed>;
const mockCreateHash = crypto.createHash as jest.MockedFunction<typeof crypto.createHash>;
function stubHash(hash: string) {
mockCreateHash.mockReturnValue({
update: jest.fn().mockReturnThis(),
digest: jest.fn().mockReturnValue(hash),
} as unknown as crypto.Hash);
}
const WRONG_HASH = "0".repeat(64);
describe("verifyChecksum", () => {
beforeEach(() => {
jest.clearAllMocks();
mockReadFile.mockReturnValue(Buffer.from("test-payload"));
});
describe("agentType=bravo", () => {
test("passes with matching bravo amd64 checksum", () => {
stubHash(CHECKSUMS.bravo.amd64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux", "bravo")).toBe(true);
expect(mockSetFailed).not.toHaveBeenCalled();
});
test("passes with matching bravo arm64 checksum", () => {
stubHash(CHECKSUMS.bravo.arm64);
expect(verifyChecksum("/tmp/f", true, "arm64", "linux", "bravo")).toBe(true);
});
test("uses bravo checksum even when isTLS=false", () => {
stubHash(CHECKSUMS.bravo.amd64);
expect(verifyChecksum("/tmp/f", false, "amd64", "linux", "bravo")).toBe(true);
});
test("fails on mismatched bravo checksum", () => {
stubHash(WRONG_HASH);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux", "bravo")).toBe(false);
expect(mockSetFailed).toHaveBeenCalled();
});
});
describe("agentType default (omitted)", () => {
test("uses TLS checksum when isTLS=true", () => {
stubHash(CHECKSUMS.tls.amd64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux")).toBe(true);
});
test("uses non_tls checksum when isTLS=false", () => {
stubHash(CHECKSUMS.non_tls.amd64);
expect(verifyChecksum("/tmp/f", false, "amd64", "linux")).toBe(true);
});
test("TLS mismatch fails", () => {
stubHash(CHECKSUMS.bravo.amd64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux")).toBe(false);
expect(mockSetFailed).toHaveBeenCalled();
});
});
describe("darwin", () => {
test("passes with matching darwin checksum", () => {
stubHash(CHECKSUMS.darwin);
expect(verifyChecksum("/tmp/f", false, "", "darwin")).toBe(true);
});
});
describe("win32", () => {
test("passes with matching windows amd64 checksum", () => {
stubHash(CHECKSUMS.windows.amd64);
expect(verifyChecksum("/tmp/f", false, "amd64", "win32")).toBe(true);
});
});
describe("unsupported platform", () => {
test("returns false without calling setFailed", () => {
stubHash(CHECKSUMS.bravo.amd64);
expect(verifyChecksum("/tmp/f", true, "amd64", "freebsd")).toBe(false);
expect(mockSetFailed).not.toHaveBeenCalled();
});
});
});

View file

@ -2,17 +2,21 @@ import * as core from "@actions/core";
import * as crypto from "crypto"; import * as crypto from "crypto";
import * as fs from "fs"; import * as fs from "fs";
const CHECKSUMS = { export const CHECKSUMS = {
tls: { tls: {
amd64: "86d042adcdc03eb1ea50d35d265da47622a6d0aedef9657f84ce1eb7f04d6057", // v1.8.0 amd64: "713c91e921292027dacf446db44bafbc8e36a3f7f51dff664ba681c6e4398a05", // v1.8.2
arm64: "ea1074a2358d50db9a9fe18ae3971b87305cda63f262c494a5f43b25f4e524ce", arm64: "2c1eb365d6d9ae4cd4b6632a5f833bcdb7e75d0d9604de3391ff22e4e28e8d42",
}, },
non_tls: { non_tls: {
amd64: "4aaaeebbe10e619d8ce13e8cc4a1acbafc8f891e8cdd319984480b9ec08407b8", // v0.15.0 amd64: "e38de61e1afd98dd339bb9acce4996183875d482be1638fb198ab02b3e25bbef", // v0.16.0
}, },
darwin: "797399a3a3f6f9c4c000a02e0d8c7b16499129c9bdc2ad9cf2a10072c10654fb", // v0.0.4 bravo: {
amd64: "8d002af0c1c4bb73eaef0f2b641f7aa353cc3f4da36a4e418b69895a2baa922c", // v1.8.2
arm64: "1ce74a30d704c2e994246fc809d65af83e3f354aae7b9080b2c2eaee715cf005",
},
darwin: "fe26a1f6af4afe9f1a854d8633832f5d18ab542827003cae445b3a64021d612c", // v0.0.5
windows: { windows: {
amd64: "e98f8b9cf9ecf6566f1e16a470fbe4aef01610a644fd8203a1bab3ff142186c8", // v1.0.0 amd64: "93f1e5d87c6647e6eca7963d5f4b4bd73107029430f8e6945ffece93007a89f5", // v1.0.2
}, },
}; };
@ -21,7 +25,8 @@ export function verifyChecksum(
downloadPath: string, downloadPath: string,
isTLS: boolean, isTLS: boolean,
variant: string, variant: string,
platform: string platform: string,
agentType: "default" | "bravo" = "default"
) { ) {
const fileBuffer: Buffer = fs.readFileSync(downloadPath); const fileBuffer: Buffer = fs.readFileSync(downloadPath);
const checksum: string = crypto const checksum: string = crypto
@ -33,9 +38,13 @@ export function verifyChecksum(
switch (platform) { switch (platform) {
case "linux": case "linux":
expectedChecksum = isTLS if (agentType === "bravo") {
? CHECKSUMS["tls"][variant] expectedChecksum = CHECKSUMS["bravo"][variant];
: CHECKSUMS["non_tls"][variant]; } else {
expectedChecksum = isTLS
? CHECKSUMS["tls"][variant]
: CHECKSUMS["non_tls"][variant];
}
break; break;
case "darwin": case "darwin":
expectedChecksum = CHECKSUMS["darwin"]; expectedChecksum = CHECKSUMS["darwin"];

View file

@ -6,7 +6,7 @@ import isDocker from "is-docker";
import { isARCRunner } from "./arc-runner"; import { isARCRunner } from "./arc-runner";
import { isGithubHosted } from "./tls-inspect"; import { isGithubHosted } from "./tls-inspect";
import { context } from "@actions/github"; import { context } from "@actions/github";
import { isPlatformSupported, isAgentInstalled } from "./utils"; import { isPlatformSupported, isAgentInstalled, detectThirdPartyRunnerProvider } from "./utils";
(async () => { (async () => {
console.log("[harden-runner] post-step"); console.log("[harden-runner] post-step");
@ -31,6 +31,8 @@ import { isPlatformSupported, isAgentInstalled } from "./utils";
return; return;
} }
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (process.env.STATE_selfHosted === "true") { if (process.env.STATE_selfHosted === "true") {
return; return;
} }
@ -49,7 +51,11 @@ import { isPlatformSupported, isAgentInstalled } from "./utils";
switch (process.platform) { switch (process.platform) {
case "linux": case "linux":
await handleLinuxCleanup(); if (thirdPartyProvider) {
await handleAgentBravoCleanup();
} else {
await handleLinuxCleanup();
}
break; break;
case "win32": case "win32":
await handleWindowsCleanup(); await handleWindowsCleanup();
@ -66,6 +72,38 @@ import { isPlatformSupported, isAgentInstalled } from "./utils";
} }
})(); })();
async function handleAgentBravoCleanup() {
cp.execFileSync("/usr/bin/echo", ["step_policy_jobend"]);
const doneFile = "/home/agent/done.json";
let counter = 0;
while (true) {
if (!fs.existsSync(doneFile)) {
counter++;
if (counter > 10) {
console.log("timed out");
break;
}
await sleep(1000);
} else {
console.log(fs.readFileSync(doneFile, "utf-8"));
break;
}
}
const log = "/home/agent/agent.log";
if (fs.existsSync(log)) {
console.log("log:");
console.log(fs.readFileSync(log, "utf-8"));
}
const status = "/home/agent/agent.status";
if (fs.existsSync(status)) {
console.log("status:");
console.log(fs.readFileSync(status, "utf-8"));
}
}
async function handleLinuxCleanup() { async function handleLinuxCleanup() {
if (process.env.STATE_isTLS === "false" && process.arch === "arm64") { if (process.env.STATE_isTLS === "false" && process.arch === "arm64") {
return; return;

View file

@ -26,7 +26,7 @@ export async function installAgent(
if (isTLS) { if (isTLS) {
downloadPath = await tc.downloadTool( downloadPath = await tc.downloadTool(
`https://github.com/step-security/agent-ebpf/releases/download/v1.8.0/harden-runner_1.8.0_linux_${variant}.tar.gz`, `https://github.com/step-security/agent-ebpf/releases/download/v1.8.2/harden-runner_1.8.2_linux_${variant}.tar.gz`,
undefined, undefined,
auth auth
); );
@ -36,7 +36,7 @@ export async function installAgent(
return false; return false;
} }
downloadPath = await tc.downloadTool( downloadPath = await tc.downloadTool(
"https://github.com/step-security/agent/releases/download/v0.15.0/agent_0.15.0_linux_amd64.tar.gz", "https://github.com/step-security/agent/releases/download/v0.16.0/agent_0.16.0_linux_amd64.tar.gz",
undefined, undefined,
auth auth
); );
@ -69,6 +69,60 @@ export async function installAgent(
return true; return true;
} }
export async function installAgentBravo(configStr: string): Promise<boolean> {
// Note: to avoid github rate limiting
const token = core.getInput("token", { required: true });
const auth = `token ${token}`;
const variant = process.arch === "x64" ? "amd64" : "arm64";
const downloadPath = await tc.downloadTool(
`https://github.com/step-security/agent-ebpf/releases/download/v1.8.2/harden-runner-bravo_1.8.2_linux_${variant}.tar.gz`,
undefined,
auth
);
if (!verifyChecksum(downloadPath, true, variant, "linux", "bravo")) {
return false;
}
const extractPath = await tc.extractTar(downloadPath);
cp.execFileSync("cp", [path.join(extractPath, "agent"), "/home/agent/agent"]);
cp.execSync("chmod +x /home/agent/agent");
fs.writeFileSync("/home/agent/agent.json", configStr);
const logStream = fs.openSync("/home/agent/agent.stdout", "a");
const agentProcess = cp.spawn("sudo", ["/home/agent/agent"], {
cwd: "/home/agent",
detached: true,
stdio: ["ignore", logStream, logStream],
});
agentProcess.unref();
const agentStatus = "/home/agent/agent.status";
const deadline = Date.now() + 10000;
while (true) {
if (!fs.existsSync(agentStatus)) {
if (Date.now() >= deadline) {
console.log("timed out waiting for bravo agent");
if (fs.existsSync("/home/agent/agent.stdout")) {
console.log(fs.readFileSync("/home/agent/agent.stdout", "utf-8"));
}
if (fs.existsSync("/home/agent/agent.log")) {
console.log(fs.readFileSync("/home/agent/agent.log", "utf-8"));
}
break;
}
await new Promise((resolve) => setTimeout(resolve, 300));
} else {
console.log(fs.readFileSync(agentStatus, "utf-8"));
break;
}
}
return true;
}
export async function installMacosAgent(configStr: string): Promise<boolean> { export async function installMacosAgent(configStr: string): Promise<boolean> {
const token = core.getInput("token", { required: true }); const token = core.getInput("token", { required: true });
const auth = `token ${token}`; const auth = `token ${token}`;
@ -89,7 +143,7 @@ export async function installMacosAgent(configStr: string): Promise<boolean> {
// Download installer package // Download installer package
const downloadUrl = const downloadUrl =
"https://github.com/step-security/agent-releases/releases/download/v0.0.4-mac/macos-installer-0.0.4.tar.gz"; "https://github.com/step-security/agent-releases/releases/download/v0.0.5-mac/macos-installer-0.0.5.tar.gz";
core.info(`Downloading macOS installer.. : ${downloadUrl}`); core.info(`Downloading macOS installer.. : ${downloadUrl}`);
const downloadPath = await tc.downloadTool(downloadUrl, undefined, auth); const downloadPath = await tc.downloadTool(downloadUrl, undefined, auth);
core.info(`✓ Successfully downloaded installer to: ${downloadPath}`); core.info(`✓ Successfully downloaded installer to: ${downloadPath}`);
@ -172,7 +226,7 @@ export async function installWindowsAgent(configStr: string): Promise<boolean> {
const agentExePath = path.join(agentDir, "agent.exe"); const agentExePath = path.join(agentDir, "agent.exe");
const downloadPath = await tc.downloadTool( const downloadPath = await tc.downloadTool(
`https://github.com/step-security/agent-releases/releases/download/v1.0.0-win/harden-runner-agent-windows_1.0.0_windows_amd64.tar.gz`, `https://github.com/step-security/agent-releases/releases/download/v1.0.2-win/harden-runner-agent-windows_1.0.2_windows_amd64.tar.gz`,
undefined, undefined,
auth auth
); );

View file

@ -33,11 +33,13 @@ import {
import { isGithubHosted, isTLSEnabled } from "./tls-inspect"; import { isGithubHosted, isTLSEnabled } from "./tls-inspect";
import { import {
installAgent, installAgent,
installAgentBravo,
installMacosAgent, installMacosAgent,
installWindowsAgent, installWindowsAgent,
} from "./install-agent"; } from "./install-agent";
import { chownForFolder, isAgentInstalled, isPlatformSupported, shouldDeployAgentOnSelfHosted } from "./utils"; import { chownForFolder, detectThirdPartyRunnerProvider, isAgentInstalled, isPlatformSupported, shouldDeployAgentOnSelfHosted } from "./utils";
import { buildBravoConfig } from "./bravo-config";
interface MonitorResponse { interface MonitorResponse {
runner_ip_address?: string; runner_ip_address?: string;
@ -289,6 +291,20 @@ interface MonitorResponse {
const runnerName = process.env.RUNNER_NAME || ""; const runnerName = process.env.RUNNER_NAME || "";
core.info(`RUNNER_NAME: ${runnerName}`); core.info(`RUNNER_NAME: ${runnerName}`);
if (!isGithubHosted()) { if (!isGithubHosted()) {
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (thirdPartyProvider) {
const providerLabel = thirdPartyProvider.charAt(0).toUpperCase() + thirdPartyProvider.slice(1);
if (process.platform !== "linux") {
core.info(`Detected ${providerLabel} runner on ${process.platform}. Bravo agent is Linux-only, skipping install.`);
return;
}
core.info(`Detected ${providerLabel} runner environment. Installing agent-bravo.`);
confg.correlation_id = runnerName || confg.correlation_id;
await callMonitorEndpoint(api_url, confg);
await installAgentForBravo(context.repo.owner, confg);
return;
}
fs.appendFileSync(process.env.GITHUB_STATE, `selfHosted=true${EOL}`, { fs.appendFileSync(process.env.GITHUB_STATE, `selfHosted=true${EOL}`, {
encoding: "utf8", encoding: "utf8",
}); });
@ -470,6 +486,34 @@ export function sleep(ms: number) {
}); });
} }
async function callMonitorEndpoint(api_url: string, confg: Configuration) {
const _http = new httpm.HttpClient();
_http.requestOptions = { socketTimeout: 3 * 1000 };
let statusCode: number | undefined;
let addSummary = "false";
try {
const monitorRequestData = {
correlation_id: confg.correlation_id,
job: process.env["GITHUB_JOB"],
};
const resp = await _http.postJson<MonitorResponse>(
`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`,
monitorRequestData
);
statusCode = resp.statusCode;
if (resp.statusCode === 200 && resp.result) {
console.log(`Runner IP Address: ${resp.result.runner_ip_address}`);
confg.one_time_key = resp.result.one_time_key;
addSummary = resp.result.monitoring_started ? "true" : "false";
}
} catch (e) {
console.log(`error in connecting to ${api_url}: ${e}`);
}
fs.appendFileSync(process.env.GITHUB_STATE, `monitorStatusCode=${statusCode}${EOL}`, { encoding: "utf8" });
fs.appendFileSync(process.env.GITHUB_STATE, `addSummary=${addSummary}${EOL}`, { encoding: "utf8" });
fs.appendFileSync(process.env.GITHUB_STATE, `correlation_id=${confg.correlation_id}${EOL}`, { encoding: "utf8" });
}
export async function installAgentForSelfHosted(owner: string, confg: Configuration) { export async function installAgentForSelfHosted(owner: string, confg: Configuration) {
try { try {
console.log("Installing Harden Runner agent for self-hosted runner"); console.log("Installing Harden Runner agent for self-hosted runner");
@ -528,3 +572,25 @@ export async function installAgentForSelfHosted(owner: string, confg: Configurat
console.log(`Failed to install agent for self-hosted runner: ${error.message}`); console.log(`Failed to install agent for self-hosted runner: ${error.message}`);
} }
} }
export async function installAgentForBravo(owner: string, confg: Configuration) {
try {
console.log("Installing Harden Runner bravo agent for third-party runner");
let isTLS = await isTLSEnabled(owner);
if (!isTLS) {
console.log("TLS is not enabled for this organization. Bravo agent installation skipped.");
return;
}
const bravoConfigStr = JSON.stringify(buildBravoConfig(confg));
cp.execSync("sudo mkdir -p /home/agent");
chownForFolder(process.env.USER, "/home/agent");
await installAgentBravo(bravoConfigStr);
} catch (error) {
console.log(`Failed to install bravo agent: ${error.message}`);
}
}

View file

@ -1,4 +1,4 @@
import { shouldDeployAgentOnSelfHosted, isAgentInstalled, isPlatformSupported, getAnnotationLogs } from "./utils"; import { shouldDeployAgentOnSelfHosted, isAgentInstalled, isPlatformSupported, getAnnotationLogs, detectThirdPartyRunnerProvider } from "./utils";
import * as fs from "fs"; import * as fs from "fs";
jest.mock("fs", () => ({ jest.mock("fs", () => ({
@ -90,3 +90,69 @@ describe("getAnnotationLogs", () => {
expect(() => getAnnotationLogs("freebsd" as NodeJS.Platform)).toThrow("platform not supported"); expect(() => getAnnotationLogs("freebsd" as NodeJS.Platform)).toThrow("platform not supported");
}); });
}); });
describe("detectThirdPartyRunnerProvider", () => {
const originalEnv = process.env;
beforeEach(() => {
process.env = { ...originalEnv };
delete process.env.DEPOT_RUNNER;
delete process.env.NAMESPACE_GITHUB_RUNTIME;
delete process.env.RUNNER_NAME;
});
afterAll(() => {
process.env = originalEnv;
});
test("returns depot when DEPOT_RUNNER=1", () => {
process.env.DEPOT_RUNNER = "1";
expect(detectThirdPartyRunnerProvider()).toBe("depot");
});
test("returns null when DEPOT_RUNNER=0", () => {
process.env.DEPOT_RUNNER = "0";
expect(detectThirdPartyRunnerProvider()).toBeNull();
});
test("returns namespace when NAMESPACE_GITHUB_RUNTIME is set", () => {
process.env.NAMESPACE_GITHUB_RUNTIME = "something";
expect(detectThirdPartyRunnerProvider()).toBe("namespace");
});
test("returns warp for RUNNER_NAME prefix warp-", () => {
process.env.RUNNER_NAME = "warp-4x-x64-abc";
expect(detectThirdPartyRunnerProvider()).toBe("warp");
});
test("returns blacksmith for RUNNER_NAME prefix blacksmith-", () => {
process.env.RUNNER_NAME = "blacksmith-01kpj-4vcpu";
expect(detectThirdPartyRunnerProvider()).toBe("blacksmith");
});
test("returns null when no env vars match", () => {
expect(detectThirdPartyRunnerProvider()).toBeNull();
});
test("returns null for a non-matching RUNNER_NAME", () => {
process.env.RUNNER_NAME = "GitHub Actions 1";
expect(detectThirdPartyRunnerProvider()).toBeNull();
});
test("depot takes precedence over namespace", () => {
process.env.DEPOT_RUNNER = "1";
process.env.NAMESPACE_GITHUB_RUNTIME = "something";
expect(detectThirdPartyRunnerProvider()).toBe("depot");
});
test("namespace takes precedence over warp runner name prefix", () => {
process.env.NAMESPACE_GITHUB_RUNTIME = "something";
process.env.RUNNER_NAME = "warp-x";
expect(detectThirdPartyRunnerProvider()).toBe("namespace");
});
test("warp takes precedence over blacksmith when both prefixes seen (warp wins on name check order)", () => {
process.env.RUNNER_NAME = "warp-x";
expect(detectThirdPartyRunnerProvider()).toBe("warp");
});
});

View file

@ -40,6 +40,17 @@ export function shouldDeployAgentOnSelfHosted(
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled; return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
} }
export type ThirdPartyRunnerProvider = "depot" | "namespace" | "warp" | "blacksmith";
export function detectThirdPartyRunnerProvider(): ThirdPartyRunnerProvider | null {
if (process.env["DEPOT_RUNNER"] === "1") return "depot";
if (process.env["NAMESPACE_GITHUB_RUNTIME"]) return "namespace";
const runnerName = process.env["RUNNER_NAME"] ?? "";
if (runnerName.startsWith("warp-")) return "warp";
if (runnerName.startsWith("blacksmith-")) return "blacksmith";
return null;
}
export function getAnnotationLogs(platform: NodeJS.Platform) { export function getAnnotationLogs(platform: NodeJS.Platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":