Compare commits
10 commits
main
...
pr/660-ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b9c34e9f9 |
||
|
|
02e8ea858c |
||
|
|
e871c3595b |
||
|
|
e17c10bfac |
||
|
|
a480e0054e |
||
|
|
7b9fcb2585 |
||
|
|
7e1533e993 |
||
|
|
2f199dceb1 |
||
|
|
fd9b4982b0 |
||
|
|
20c37511ec |
15 changed files with 691 additions and 35 deletions
13
dist/index.js
vendored
13
dist/index.js
vendored
|
|
@ -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
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
50
dist/post/index.js
vendored
50
dist/post/index.js
vendored
|
|
@ -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":
|
||||||
|
if (thirdPartyProvider) {
|
||||||
|
yield handleAgentBravoCleanup();
|
||||||
|
}
|
||||||
|
else {
|
||||||
yield handleLinuxCleanup();
|
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") {
|
||||||
|
|
|
||||||
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
169
dist/pre/index.js
vendored
169
dist/pre/index.js
vendored
|
|
@ -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":
|
||||||
|
if (agentType === "bravo") {
|
||||||
|
expectedChecksum = CHECKSUMS["bravo"][variant];
|
||||||
|
}
|
||||||
|
else {
|
||||||
expectedChecksum = isTLS
|
expectedChecksum = isTLS
|
||||||
? CHECKSUMS["tls"][variant]
|
? CHECKSUMS["tls"][variant]
|
||||||
: CHECKSUMS["non_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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
98
src/checksum.test.ts
Normal file
98
src/checksum.test.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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":
|
||||||
|
if (agentType === "bravo") {
|
||||||
|
expectedChecksum = CHECKSUMS["bravo"][variant];
|
||||||
|
} else {
|
||||||
expectedChecksum = isTLS
|
expectedChecksum = isTLS
|
||||||
? CHECKSUMS["tls"][variant]
|
? CHECKSUMS["tls"][variant]
|
||||||
: CHECKSUMS["non_tls"][variant];
|
: CHECKSUMS["non_tls"][variant];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "darwin":
|
case "darwin":
|
||||||
expectedChecksum = CHECKSUMS["darwin"];
|
expectedChecksum = CHECKSUMS["darwin"];
|
||||||
|
|
|
||||||
|
|
@ -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":
|
||||||
|
if (thirdPartyProvider) {
|
||||||
|
await handleAgentBravoCleanup();
|
||||||
|
} else {
|
||||||
await handleLinuxCleanup();
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
68
src/setup.ts
68
src/setup.ts
|
|
@ -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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
11
src/utils.ts
11
src/utils.ts
|
|
@ -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":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue