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:
parent
7b9fcb2585
commit
a480e0054e
7 changed files with 286 additions and 40 deletions
44
dist/pre/index.js
vendored
44
dist/pre/index.js
vendored
|
|
@ -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);
|
||||
|
|
|
|||
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
81
src/bravo-config.test.ts
Normal file
81
src/bravo-config.test.ts
Normal 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
21
src/bravo-config.ts
Normal 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
87
src/checksum.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
23
src/setup.ts
23
src/setup.ts
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue