1
0
Fork 0
mirror of synced 2026-06-05 12:35:14 +00:00

add unit tests for third-party runner support

- detectThirdPartyRunnerProvider: env-var matrix + precedence ordering
- verifyChecksum: bravo agentType branch, default branch, mismatches
- buildBravoConfig: extracted as pure function; tests lock in the
  shape (no api_key, no customer, is_github_hosted=true, telemetry_url
  forwarded, one_time_key forwarded) that the server-side auth and
  correlation paths depend on

Also capitalize the third-party provider name in the "Detected <X>
runner environment" log line.
This commit is contained in:
Varun Sharma 2026-04-19 07:50:11 -07:00
commit a480e0054e
No known key found for this signature in database
7 changed files with 286 additions and 40 deletions

44
dist/pre/index.js vendored
View file

@ -85730,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
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); }); }
@ -85771,6 +85792,7 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
(() => setup_awaiter(void 0, void 0, void 0, function* () {
@ -85966,7 +85988,8 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
if (!isGithubHosted()) {
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (thirdPartyProvider) {
lib_core.info(`Detected ${thirdPartyProvider} runner environment. Installing agent-bravo.`);
const providerLabel = thirdPartyProvider.charAt(0).toUpperCase() + thirdPartyProvider.slice(1);
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);
@ -86202,24 +86225,7 @@ function installAgentForBravo(owner, confg) {
console.log("TLS is not enabled for this organization. Bravo agent installation skipped.");
return;
}
const bravoConfig = {
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,
};
const bravoConfigStr = JSON.stringify(bravoConfig);
const bravoConfigStr = JSON.stringify(buildBravoConfig(confg));
external_child_process_.execSync("sudo mkdir -p /home/agent");
chownForFolder(process.env.USER, "/home/agent");
yield installAgentBravo(bravoConfigStr);

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,
};
}

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

@ -0,0 +1,87 @@
import * as fs from "fs";
import * as crypto from "crypto";
import * as core from "@actions/core";
import { verifyChecksum } 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 BRAVO_AMD64 = "2eeaa1b3cfb05adea0a4e2a36e342ccaf95b41aeb82a6a6e217d2971c15f5553";
const BRAVO_ARM64 = "8d7035ffbda165ad86de8bd00bf861c038e4a9e6d501adadc53a265945882533";
const TLS_AMD64 = "6105000c6c61f4a3ca27ed3a2796baa206bdb1eb83f0463adb0ec7e565af6e1c";
const NON_TLS_AMD64 = "4aaaeebbe10e619d8ce13e8cc4a1acbafc8f891e8cdd319984480b9ec08407b8";
describe("verifyChecksum", () => {
beforeEach(() => {
jest.clearAllMocks();
mockReadFile.mockReturnValue(Buffer.from("test-payload"));
});
describe("agentType=bravo", () => {
test("passes with matching bravo amd64 checksum", () => {
stubHash(BRAVO_AMD64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux", "bravo")).toBe(true);
expect(mockSetFailed).not.toHaveBeenCalled();
});
test("passes with matching bravo arm64 checksum", () => {
stubHash(BRAVO_ARM64);
expect(verifyChecksum("/tmp/f", true, "arm64", "linux", "bravo")).toBe(true);
});
test("uses bravo checksum even when isTLS=false", () => {
stubHash(BRAVO_AMD64);
expect(verifyChecksum("/tmp/f", false, "amd64", "linux", "bravo")).toBe(true);
});
test("fails on mismatched bravo checksum", () => {
stubHash("0".repeat(64));
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(TLS_AMD64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux")).toBe(true);
});
test("uses non_tls checksum when isTLS=false", () => {
stubHash(NON_TLS_AMD64);
expect(verifyChecksum("/tmp/f", false, "amd64", "linux")).toBe(true);
});
test("TLS mismatch fails", () => {
stubHash(BRAVO_AMD64);
expect(verifyChecksum("/tmp/f", true, "amd64", "linux")).toBe(false);
expect(mockSetFailed).toHaveBeenCalled();
});
});
describe("unsupported platform", () => {
test("returns false without calling setFailed", () => {
stubHash(BRAVO_AMD64);
expect(verifyChecksum("/tmp/f", true, "amd64", "freebsd")).toBe(false);
expect(mockSetFailed).not.toHaveBeenCalled();
});
});
});

View file

@ -39,6 +39,7 @@ import {
} from "./install-agent";
import { chownForFolder, detectThirdPartyRunnerProvider, isAgentInstalled, isPlatformSupported, shouldDeployAgentOnSelfHosted } from "./utils";
import { buildBravoConfig } from "./bravo-config";
interface MonitorResponse {
runner_ip_address?: string;
@ -292,7 +293,8 @@ interface MonitorResponse {
if (!isGithubHosted()) {
const thirdPartyProvider = detectThirdPartyRunnerProvider();
if (thirdPartyProvider) {
core.info(`Detected ${thirdPartyProvider} runner environment. Installing agent-bravo.`);
const providerLabel = thirdPartyProvider.charAt(0).toUpperCase() + thirdPartyProvider.slice(1);
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);
@ -571,24 +573,7 @@ export async function installAgentForBravo(owner: string, confg: Configuration)
return;
}
const bravoConfig = {
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,
};
const bravoConfigStr = JSON.stringify(bravoConfig);
const bravoConfigStr = JSON.stringify(buildBravoConfig(confg));
cp.execSync("sudo mkdir -p /home/agent");
chownForFolder(process.env.USER, "/home/agent");

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";
jest.mock("fs", () => ({
@ -90,3 +90,69 @@ describe("getAnnotationLogs", () => {
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");
});
});