mirror of
https://github.com/step-security/harden-runner
synced 2026-08-09 13:11:02 +00:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da995c3c2 | ||
|
|
853e846a24 | ||
|
|
0d7fdc082e | ||
|
|
25b1b6b099 | ||
|
|
5d1eb77a99 | ||
|
|
bc791947a3 | ||
|
|
5ef0c079ce | ||
|
|
eb43c7b3fd | ||
|
|
ec2fee99eb | ||
|
|
e823d39c9a | ||
|
|
9a13b2d01e | ||
|
|
6061152f29 | ||
|
|
ac1419447b | ||
|
|
ef9c6372b3 | ||
|
|
e3f713f2d8 | ||
|
|
423acdda6f | ||
|
|
0ddb86cf03 | ||
|
|
20cf305ff2 | ||
|
|
c51e8eeb6c | ||
|
|
e152b90204 | ||
|
|
ee1faec052 | ||
|
|
1dc7c17646 | ||
|
|
df199fb7be | ||
|
|
03d096a772 | ||
|
|
40901073af | ||
|
|
95d9a5deda | ||
|
|
87e429d3fb | ||
|
|
ef891c3a30 | ||
|
|
1fa8c8a8b1 |
@@ -2,8 +2,7 @@ name: RunsOn Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -12,7 +11,7 @@ jobs:
|
||||
runs-on:
|
||||
- runs-on=${{ github.run_id }}
|
||||
- runner=2cpu-linux-x64
|
||||
- image=ubuntu22-stepsecurity-x64
|
||||
- image=ubuntu24-stepsecurity-x64
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@rc
|
||||
@@ -41,7 +40,7 @@ jobs:
|
||||
runs-on:
|
||||
- runs-on=${{ github.run_id }}
|
||||
- runner=2cpu-linux-x64
|
||||
- image=ubuntu22-stepsecurity-x64
|
||||
- image=ubuntu24-stepsecurity-x64
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@rc
|
||||
|
||||
Vendored
+4341
-6
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4553
-56
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+256
-35
@@ -85149,6 +85149,49 @@ const validate = dist/* validate */.tf;
|
||||
const stringify = dist/* stringify */.As;
|
||||
const parse = dist/* parse */.qg;
|
||||
|
||||
;// CONCATENATED MODULE: ./src/utils.ts
|
||||
|
||||
|
||||
function isPlatformSupported(platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
case "win32":
|
||||
case "darwin":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function chownForFolder(newOwner, target) {
|
||||
let cmd = "sudo";
|
||||
let args = ["chown", "-R", newOwner, target];
|
||||
external_child_process_.execFileSync(cmd, args);
|
||||
}
|
||||
function isAgentInstalled(platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
return external_fs_.existsSync("/home/agent/agent.status");
|
||||
case "win32":
|
||||
return external_fs_.existsSync("C:\\agent\\agent.status");
|
||||
case "darwin":
|
||||
return external_fs_.existsSync("/opt/step-security/agent.status");
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function utils_getAnnotationLogs(platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
return fs.readFileSync("/home/agent/annotation.log", "utf8");
|
||||
case "win32":
|
||||
return fs.readFileSync("C:\\agent\\annotation.log", "utf8");
|
||||
case "darwin":
|
||||
return fs.readFileSync("/opt/step-security/annotation.log", "utf8");
|
||||
default:
|
||||
throw new Error("platform not supported");
|
||||
}
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/common.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
@@ -85201,8 +85244,9 @@ function addSummary() {
|
||||
}
|
||||
let needsSubscription = false;
|
||||
try {
|
||||
let data = fs.readFileSync("/home/agent/annotation.log", "utf8");
|
||||
if (data.includes("StepSecurity Harden Runner is disabled")) {
|
||||
let data = getAnnotationLogs(process.platform);
|
||||
if (data !== undefined &&
|
||||
data.includes("StepSecurity Harden Runner is disabled")) {
|
||||
needsSubscription = true;
|
||||
}
|
||||
}
|
||||
@@ -85248,8 +85292,8 @@ function addSummary() {
|
||||
});
|
||||
}
|
||||
const STATUS_HARDEN_RUNNER_UNAVAILABLE = "409";
|
||||
const CONTAINER_MESSAGE = "This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
const UBUNTU_MESSAGE = "This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
const CONTAINER_MESSAGE = "This job is running in a container. Such jobs can be monitored by installing Harden Runner in a custom VM image for GitHub-hosted runners.";
|
||||
const UNSUPPORTED_RUNNER_MESSAGE = "This job is not running in a GitHub Actions Hosted Runner. Harden Runner is only supported on GitHub-hosted runners (Ubuntu, Windows, and macOS). This job will not be monitored.";
|
||||
const SELF_HOSTED_RUNNER_MESSAGE = "This job is running on a self-hosted runner.";
|
||||
const HARDEN_RUNNER_UNAVAILABLE_MESSAGE = "Sorry, we are currently experiencing issues with the Harden Runner installation process. It is currently unavailable.";
|
||||
const ARC_RUNNER_MESSAGE = "Workflow is currently being executed in ARC based runner.";
|
||||
@@ -85495,29 +85539,46 @@ var external_crypto_ = __nccwpck_require__(6982);
|
||||
|
||||
const CHECKSUMS = {
|
||||
tls: {
|
||||
amd64: "2430b850e0e4d67a2f3b626f02d2827226ee16406da6af0c47ae7b18e18bd2b8",
|
||||
arm64: "a3c89271e697ab39557ba8011cac7a2df690b5d27b4584d5d5abdf8845a6ce6c",
|
||||
amd64: "19c35eee1347077eb71306b122ad4a1cf83f36ef0f69fd91b0c0d79ffd0eabdd",
|
||||
arm64: "f9192788e86b2e44b795f072e8cc03eec9852649609aeedac0761d3b67c991fa",
|
||||
},
|
||||
non_tls: {
|
||||
amd64: "336093af8ebe969567b66fd035af3bd4f7e1c723ce680d6b4b5b2a1f79bc329e", // v0.14.2
|
||||
amd64: "23715f2485c16e2a2ad116abf0fe8443788c62e4f5f224c5858b0b41b591fc89", // v0.14.3
|
||||
},
|
||||
darwin: "797399a3a3f6f9c4c000a02e0d8c7b16499129c9bdc2ad9cf2a10072c10654fb",
|
||||
windows: {
|
||||
amd64: "e98f8b9cf9ecf6566f1e16a470fbe4aef01610a644fd8203a1bab3ff142186c8", // v1.0.0
|
||||
},
|
||||
};
|
||||
function verifyChecksum(downloadPath, isTLS, variant) {
|
||||
// verifyChecksum returns true if checksum is valid
|
||||
function verifyChecksum(downloadPath, isTLS, variant, platform) {
|
||||
const fileBuffer = external_fs_.readFileSync(downloadPath);
|
||||
const checksum = external_crypto_.createHash("sha256")
|
||||
.update(fileBuffer)
|
||||
.digest("hex"); // checksum of downloaded file
|
||||
let expectedChecksum = "";
|
||||
if (isTLS) {
|
||||
expectedChecksum = CHECKSUMS["tls"][variant];
|
||||
}
|
||||
else {
|
||||
expectedChecksum = CHECKSUMS["non_tls"][variant];
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
expectedChecksum = isTLS
|
||||
? CHECKSUMS["tls"][variant]
|
||||
: CHECKSUMS["non_tls"][variant];
|
||||
break;
|
||||
case "darwin":
|
||||
expectedChecksum = CHECKSUMS["darwin"];
|
||||
break;
|
||||
case "win32":
|
||||
expectedChecksum = CHECKSUMS["windows"][variant];
|
||||
break;
|
||||
default:
|
||||
console.log(`Unsupported platform: ${platform}`);
|
||||
return false;
|
||||
}
|
||||
if (checksum !== expectedChecksum) {
|
||||
lib_core.setFailed(`Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`);
|
||||
lib_core.setFailed(`❌ Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`);
|
||||
return false;
|
||||
}
|
||||
lib_core.debug("Checksum verification passed.");
|
||||
lib_core.info(`✅ Checksum verification passed. checksum=${checksum}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/install-agent.ts
|
||||
@@ -85538,6 +85599,7 @@ var install_agent_awaiter = (undefined && undefined.__awaiter) || function (this
|
||||
|
||||
|
||||
|
||||
|
||||
function installAgent(isTLS, configStr) {
|
||||
return install_agent_awaiter(this, void 0, void 0, function* () {
|
||||
// Note: to avoid github rate limiting
|
||||
@@ -85549,16 +85611,18 @@ function installAgent(isTLS, configStr) {
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (isTLS) {
|
||||
downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-ebpf/releases/download/v1.6.23/harden-runner_1.6.23_linux_${variant}.tar.gz`, undefined, auth);
|
||||
downloadPath = yield tool_cache.downloadTool(`https://github.com/step-security/agent-ebpf/releases/download/v1.7.10/harden-runner_1.7.10_linux_${variant}.tar.gz`, undefined, auth);
|
||||
}
|
||||
else {
|
||||
if (variant === "arm64") {
|
||||
console.log(ARM64_RUNNER_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.14.2/agent_0.14.2_linux_amd64.tar.gz", undefined, auth);
|
||||
downloadPath = yield tool_cache.downloadTool("https://github.com/step-security/agent/releases/download/v0.14.3/agent_0.14.3_linux_amd64.tar.gz", undefined, auth);
|
||||
}
|
||||
if (!verifyChecksum(downloadPath, isTLS, variant, "linux")) {
|
||||
return false;
|
||||
}
|
||||
verifyChecksum(downloadPath, isTLS, variant);
|
||||
const extractPath = yield tool_cache.extractTar(downloadPath);
|
||||
let cmd = "cp", args = [external_path_.join(extractPath, "agent"), "/home/agent/agent"];
|
||||
external_child_process_.execFileSync(cmd, args);
|
||||
@@ -85576,6 +85640,125 @@ function installAgent(isTLS, configStr) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
function installMacosAgent(configStr) {
|
||||
return install_agent_awaiter(this, void 0, void 0, function* () {
|
||||
const token = lib_core.getInput("token", { required: true });
|
||||
const auth = `token ${token}`;
|
||||
try {
|
||||
// Create working directory
|
||||
lib_core.info("Creating /opt/step-security directory...");
|
||||
external_child_process_.execSync("sudo mkdir -p /opt/step-security");
|
||||
chownForFolder(process.env.USER, "/opt/step-security");
|
||||
lib_core.info("✓ Successfully created /opt/step-security directory");
|
||||
// Create agent configuration file
|
||||
lib_core.info("Creating agent.json");
|
||||
external_fs_.writeFileSync("/opt/step-security/agent.json", configStr);
|
||||
lib_core.info("✓ Successfully created agent.json at /opt/step-security/agent.json");
|
||||
// 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";
|
||||
lib_core.info(`Downloading macOS installer.. : ${downloadUrl}`);
|
||||
const downloadPath = yield tool_cache.downloadTool(downloadUrl, undefined, auth);
|
||||
lib_core.info(`✓ Successfully downloaded installer to: ${downloadPath}`);
|
||||
// Verify SHA256 checksum
|
||||
lib_core.info("Verifying SHA256 checksum of downloaded tar file...");
|
||||
if (!verifyChecksum(downloadPath, false, "", "darwin")) {
|
||||
return false;
|
||||
}
|
||||
// Extract installer package
|
||||
lib_core.info("Extracting installer...");
|
||||
const extractPath = yield tool_cache.extractTar(downloadPath);
|
||||
lib_core.info(`✓ Successfully extracted installer to: ${extractPath}`);
|
||||
// Copy Installer binary to /opt/step-security
|
||||
const installerSourcePath = external_path_.join(extractPath, "Installer");
|
||||
const installerBinaryPath = "/opt/step-security/Installer";
|
||||
lib_core.info(`Copying Installer from ${installerSourcePath} to /opt/step-security...`);
|
||||
external_child_process_.execFileSync("cp", [installerSourcePath, installerBinaryPath]);
|
||||
lib_core.info("✓ Successfully copied Installer to /opt/step-security");
|
||||
// Verify installer binary exists
|
||||
if (!external_fs_.existsSync(installerBinaryPath)) {
|
||||
throw new Error("Installer binary not found at /opt/step-security/Installer");
|
||||
}
|
||||
lib_core.info("✓ Installer binary verified");
|
||||
// Make installer executable
|
||||
lib_core.info("Making installer executable...");
|
||||
external_child_process_.execSync(`chmod +x "${installerBinaryPath}"`);
|
||||
lib_core.info("✓ Installer is now executable");
|
||||
// Run installer
|
||||
lib_core.info("Running installer...");
|
||||
external_child_process_.execSync(`sudo "${installerBinaryPath}" -workdir /opt/step-security >> /opt/step-security/agent.log 2>&1`, {
|
||||
shell: "/bin/bash",
|
||||
timeout: 10000, // 10 second timeout
|
||||
});
|
||||
lib_core.info("✓ Installer completed successfully");
|
||||
lib_core.info("✅ macOS agent installation completed successfully");
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
lib_core.error(`❌ Failed to install macOS agent: ${error}`);
|
||||
if (error instanceof Error && error.stack) {
|
||||
lib_core.debug(error.stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
function installWindowsAgent(configStr) {
|
||||
return install_agent_awaiter(this, void 0, void 0, function* () {
|
||||
const token = lib_core.getInput("token", { required: true });
|
||||
const auth = `token ${token}`;
|
||||
const variant = process.arch === "x64" ? "amd64" : "arm64";
|
||||
if (variant === "arm64") {
|
||||
console.log(ARM64_RUNNER_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
const agentDir = "C:\\agent";
|
||||
lib_core.info(`Creating agent directory: ${agentDir}`);
|
||||
if (!external_fs_.existsSync(agentDir)) {
|
||||
external_fs_.mkdirSync(agentDir, { recursive: true });
|
||||
}
|
||||
external_fs_.appendFileSync(process.env.GITHUB_STATE, `agentDir=${agentDir}${external_os_.EOL}`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
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);
|
||||
// validate the checksum
|
||||
if (!verifyChecksum(downloadPath, false, variant, process.platform)) {
|
||||
return false;
|
||||
}
|
||||
const extractPath = yield tool_cache.extractTar(downloadPath);
|
||||
const extractedAgentPath = external_path_.join(extractPath, "agent.exe");
|
||||
external_fs_.copyFileSync(extractedAgentPath, agentExePath);
|
||||
lib_core.info(`Copied agent from ${extractedAgentPath} to ${agentExePath}`);
|
||||
const configPath = external_path_.join(agentDir, "config.json");
|
||||
external_fs_.writeFileSync(configPath, configStr);
|
||||
lib_core.info(`Created config file: ${configPath}`);
|
||||
lib_core.info("Starting Windows Agent...");
|
||||
try {
|
||||
const logPath = external_path_.join(agentDir, "agent.log");
|
||||
const logStream = external_fs_.openSync(logPath, "a");
|
||||
lib_core.info(`Agent logs will be written to: ${logPath}`);
|
||||
const agentProcess = external_child_process_.spawn(agentExePath, [], {
|
||||
cwd: agentDir,
|
||||
detached: true,
|
||||
stdio: ["ignore", logStream, logStream],
|
||||
windowsHide: false,
|
||||
shell: false,
|
||||
});
|
||||
const pidFile = external_path_.join(agentDir, "agent.pid");
|
||||
external_fs_.writeFileSync(pidFile, agentProcess.pid.toString());
|
||||
lib_core.info(`Agent process started with PID: ${agentProcess.pid}`);
|
||||
lib_core.info(`PID saved to: ${pidFile}`);
|
||||
agentProcess.unref();
|
||||
lib_core.info("Windows Agent process started successfully");
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
lib_core.setFailed(`Failed to start Windows agent process: ${errorMessage}`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
;// CONCATENATED MODULE: ./src/setup.ts
|
||||
var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
@@ -85606,14 +85789,20 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(() => setup_awaiter(void 0, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
var _a, _b, _c, _d;
|
||||
try {
|
||||
console.log("[harden-runner] pre-step");
|
||||
if (process.platform !== "linux") {
|
||||
console.log(UBUNTU_MESSAGE);
|
||||
const customProperties = ((_b = (_a = github.context === null || github.context === void 0 ? void 0 : github.context.payload) === null || _a === void 0 ? void 0 : _a.repository) === null || _b === void 0 ? void 0 : _b.custom_properties) || {};
|
||||
if (customProperties["skip-harden-runner"] === "true") {
|
||||
console.log("Skipping harden-runner: custom property 'skip-harden-runner' is set to 'true'");
|
||||
return;
|
||||
}
|
||||
if (!isPlatformSupported(process.platform)) {
|
||||
console.log(UNSUPPORTED_RUNNER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && isDocker()) {
|
||||
@@ -85635,7 +85824,7 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
|
||||
disable_sudo: lib_core.getBooleanInput("disable-sudo"),
|
||||
disable_sudo_and_containers: lib_core.getBooleanInput("disable-sudo-and-containers"),
|
||||
disable_file_monitoring: lib_core.getBooleanInput("disable-file-monitoring"),
|
||||
private: ((_b = (_a = github.context === null || github.context === void 0 ? void 0 : github.context.payload) === null || _a === void 0 ? void 0 : _a.repository) === null || _b === void 0 ? void 0 : _b.private) || false,
|
||||
private: ((_d = (_c = github.context === null || github.context === void 0 ? void 0 : github.context.payload) === null || _c === void 0 ? void 0 : _c.repository) === null || _d === void 0 ? void 0 : _d.private) || false,
|
||||
is_github_hosted: isGithubHosted(),
|
||||
is_debug: lib_core.isDebug(),
|
||||
one_time_key: "",
|
||||
@@ -85764,6 +85953,21 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && process.env.STEP_SECURITY_HARDEN_RUNNER === "true") {
|
||||
external_fs_.appendFileSync(process.env.GITHUB_STATE, `customVMImage=true${external_os_.EOL}`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
lib_core.info("This job is running on a custom VM image with Harden Runner installed.");
|
||||
if (confg.egress_policy === "block") {
|
||||
sendAllowedEndpoints(confg.allowed_endpoints);
|
||||
yield setup_sleep(5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && isAgentInstalled(process.platform)) {
|
||||
console.log("Agent already installed, skipping installation");
|
||||
return;
|
||||
}
|
||||
let _http = new lib.HttpClient();
|
||||
let statusCode;
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
@@ -85799,15 +86003,37 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
|
||||
console.log(HARDEN_RUNNER_UNAVAILABLE_MESSAGE);
|
||||
return;
|
||||
}
|
||||
const confgStr = JSON.stringify(confg);
|
||||
external_child_process_.execSync("sudo mkdir -p /home/agent");
|
||||
chownForFolder(process.env.USER, "/home/agent");
|
||||
let isTLS = yield isTLSEnabled(github.context.repo.owner);
|
||||
const agentInstalled = yield installAgent(isTLS, confgStr);
|
||||
const configStr = JSON.stringify(confg);
|
||||
// platform specific
|
||||
let statusFile = "";
|
||||
let logFile = "";
|
||||
let agentInstalled = false;
|
||||
switch (process.platform) {
|
||||
case "linux":
|
||||
statusFile = "/home/agent/agent.status";
|
||||
logFile = "/home/agent/agent.log";
|
||||
external_child_process_.execSync("sudo mkdir -p /home/agent");
|
||||
chownForFolder(process.env.USER, "/home/agent");
|
||||
let isTLS = yield isTLSEnabled(github.context.repo.owner);
|
||||
agentInstalled = yield installAgent(isTLS, configStr);
|
||||
break;
|
||||
case "win32":
|
||||
lib_core.info("Installing Windows Agent...");
|
||||
agentInstalled = yield installWindowsAgent(configStr);
|
||||
const agentDir = process.env.STATE_agentDir || "C:\\agent";
|
||||
statusFile = external_path_.join(agentDir, "agent.status");
|
||||
logFile = external_path_.join(agentDir, "agent.log");
|
||||
break;
|
||||
case "darwin":
|
||||
const installed = yield installMacosAgent(configStr);
|
||||
if (!installed) {
|
||||
lib_core.warning("😭 macos agent installation failed");
|
||||
}
|
||||
return; // early return
|
||||
default:
|
||||
throw new Error(`Setup failed because of unsupported platform: ${process.platform}`);
|
||||
}
|
||||
if (agentInstalled) {
|
||||
// Check that the file exists locally
|
||||
var statusFile = "/home/agent/agent.status";
|
||||
var logFile = "/home/agent/agent.log";
|
||||
var counter = 0;
|
||||
while (true) {
|
||||
if (!external_fs_.existsSync(statusFile)) {
|
||||
@@ -85842,11 +86068,6 @@ function setup_sleep(ms) {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
function chownForFolder(newOwner, target) {
|
||||
let cmd = "sudo";
|
||||
let args = ["chown", "-R", newOwner, target];
|
||||
external_child_process_.execFileSync(cmd, args);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -3,7 +3,7 @@
|
||||
### GitHub-Hosted Runners
|
||||
|
||||
* Only Ubuntu VM is supported. Windows and MacOS GitHub-hosted runners are not supported. There is a discussion about that [here](https://github.com/step-security/harden-runner/discussions/121).
|
||||
* Harden-Runner is not supported when [job is run in a container](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) as it needs sudo access on the Ubuntu VM to run. It can be used to monitor jobs that use containers to run steps. The limitation is if the entire job is run in a container. That is not common for GitHub Actions workflows, as most of them run directly on `ubuntu-latest`. Note: This is not a limitation for Self-Hosted runners.
|
||||
* Harden-Runner is not supported when [job is run in a container](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) with built-in labels such as `ubuntu-latest`, as it needs sudo access on the Ubuntu VM to run. The limitation is if the entire job is run in a container. However, such jobs can be monitored when using custom VM images with GitHub-hosted runners. This is also not a limitation for Self-Hosted runners.
|
||||
|
||||
### Self-Hosted Actions Runner Controller (ARC) Runners
|
||||
|
||||
|
||||
Generated
+22
-19
@@ -3171,10 +3171,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
@@ -5935,10 +5936,11 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -6045,10 +6047,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
@@ -9896,9 +9899,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"diff-sequences": {
|
||||
@@ -11956,9 +11959,9 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
@@ -12039,9 +12042,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.memoize": {
|
||||
|
||||
+29
-10
@@ -4,18 +4,24 @@ import * as fs from "fs";
|
||||
|
||||
const CHECKSUMS = {
|
||||
tls: {
|
||||
amd64: "2430b850e0e4d67a2f3b626f02d2827226ee16406da6af0c47ae7b18e18bd2b8", // v1.6.23
|
||||
arm64: "a3c89271e697ab39557ba8011cac7a2df690b5d27b4584d5d5abdf8845a6ce6c",
|
||||
amd64: "19c35eee1347077eb71306b122ad4a1cf83f36ef0f69fd91b0c0d79ffd0eabdd", // v1.7.10
|
||||
arm64: "f9192788e86b2e44b795f072e8cc03eec9852649609aeedac0761d3b67c991fa",
|
||||
},
|
||||
non_tls: {
|
||||
amd64: "336093af8ebe969567b66fd035af3bd4f7e1c723ce680d6b4b5b2a1f79bc329e", // v0.14.2
|
||||
amd64: "23715f2485c16e2a2ad116abf0fe8443788c62e4f5f224c5858b0b41b591fc89", // v0.14.3
|
||||
},
|
||||
darwin: "797399a3a3f6f9c4c000a02e0d8c7b16499129c9bdc2ad9cf2a10072c10654fb", // v0.0.4
|
||||
windows: {
|
||||
amd64: "e98f8b9cf9ecf6566f1e16a470fbe4aef01610a644fd8203a1bab3ff142186c8", // v1.0.0
|
||||
},
|
||||
};
|
||||
|
||||
// verifyChecksum returns true if checksum is valid
|
||||
export function verifyChecksum(
|
||||
downloadPath: string,
|
||||
isTLS: boolean,
|
||||
variant: string
|
||||
variant: string,
|
||||
platform: string
|
||||
) {
|
||||
const fileBuffer: Buffer = fs.readFileSync(downloadPath);
|
||||
const checksum: string = crypto
|
||||
@@ -25,17 +31,30 @@ export function verifyChecksum(
|
||||
|
||||
let expectedChecksum: string = "";
|
||||
|
||||
if (isTLS) {
|
||||
expectedChecksum = CHECKSUMS["tls"][variant];
|
||||
} else {
|
||||
expectedChecksum = CHECKSUMS["non_tls"][variant];
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
expectedChecksum = isTLS
|
||||
? CHECKSUMS["tls"][variant]
|
||||
: CHECKSUMS["non_tls"][variant];
|
||||
break;
|
||||
case "darwin":
|
||||
expectedChecksum = CHECKSUMS["darwin"];
|
||||
break;
|
||||
case "win32":
|
||||
expectedChecksum = CHECKSUMS["windows"][variant];
|
||||
break;
|
||||
default:
|
||||
console.log(`Unsupported platform: ${platform}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checksum !== expectedChecksum) {
|
||||
core.setFailed(
|
||||
`Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`
|
||||
`❌ Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
core.debug("Checksum verification passed.");
|
||||
core.info(`✅ Checksum verification passed. checksum=${checksum}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
+198
-10
@@ -1,14 +1,24 @@
|
||||
import * as fs from "fs";
|
||||
import * as cp from "child_process";
|
||||
import * as common from "./common";
|
||||
import * as cp from "child_process";
|
||||
import * as path from "path";
|
||||
import isDocker from "is-docker";
|
||||
import { isARCRunner } from "./arc-runner";
|
||||
import { isGithubHosted } from "./tls-inspect";
|
||||
import { context } from "@actions/github";
|
||||
import { isPlatformSupported, isAgentInstalled } from "./utils";
|
||||
|
||||
(async () => {
|
||||
console.log("[harden-runner] post-step");
|
||||
|
||||
if (process.platform !== "linux") {
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
const customProperties = context?.payload?.repository?.custom_properties || {};
|
||||
if (customProperties["skip-harden-runner"] === "true") {
|
||||
console.log("Skipping harden-runner: custom property 'skip-harden-runner' is set to 'true'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPlatformSupported(process.platform)) {
|
||||
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && isDocker()) {
|
||||
@@ -25,7 +35,7 @@ import { isGithubHosted } from "./tls-inspect";
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.STATE_isTLS === "false" && process.arch === "arm64") {
|
||||
if (process.env.STATE_customVMImage === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,6 +47,35 @@ import { isGithubHosted } from "./tls-inspect";
|
||||
return;
|
||||
}
|
||||
|
||||
switch (process.platform) {
|
||||
case "linux":
|
||||
await handleLinuxCleanup();
|
||||
break;
|
||||
case "win32":
|
||||
await handleWindowsCleanup();
|
||||
break;
|
||||
case "darwin":
|
||||
await handleMacosCleanup();
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
await common.addSummary();
|
||||
} catch (exception) {
|
||||
console.log(exception);
|
||||
}
|
||||
})();
|
||||
|
||||
async function handleLinuxCleanup() {
|
||||
if (process.env.STATE_isTLS === "false" && process.arch === "arm64") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isGithubHosted() && fs.existsSync("/home/agent/post_event.json")) {
|
||||
console.log("Post step already executed, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
"/home/agent/post_event.json",
|
||||
JSON.stringify({ event: "post" })
|
||||
@@ -53,8 +92,8 @@ import { isGithubHosted } from "./tls-inspect";
|
||||
break;
|
||||
}
|
||||
await sleep(1000);
|
||||
} // The file *does* exist
|
||||
else {
|
||||
} else {
|
||||
// The file *does* exist
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -98,13 +137,162 @@ import { isGithubHosted } from "./tls-inspect";
|
||||
console.log("Warning: Could not fetch service logs:", error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMacosCleanup() {
|
||||
const post_event = "/opt/step-security/post_event.json";
|
||||
|
||||
if (isGithubHosted() && fs.existsSync(post_event)) {
|
||||
console.log("Post step already executed, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(post_event, JSON.stringify({ event: "post" }));
|
||||
|
||||
// if agent is installed; wait for it to create done.json
|
||||
if (isAgentInstalled(process.platform)) {
|
||||
let macDone = "/opt/step-security/done.json";
|
||||
let counter = 0;
|
||||
while (true) {
|
||||
if (!fs.existsSync(macDone)) {
|
||||
counter++;
|
||||
if (counter > 10) {
|
||||
console.log("timed out");
|
||||
break;
|
||||
}
|
||||
await sleep(1000);
|
||||
} else {
|
||||
// The file *does* exist
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let macAgentLog = "/opt/step-security/agent.log";
|
||||
if (fs.existsSync(macAgentLog)) {
|
||||
console.log("macAgentLog:");
|
||||
var content = fs.readFileSync(macAgentLog, "utf-8");
|
||||
console.log(content);
|
||||
} else {
|
||||
console.log("😭 macos agent.log file not found");
|
||||
}
|
||||
|
||||
// Capture system log stream for harden-runner subsystem
|
||||
try {
|
||||
console.log("\nSystem log stream for io.stepsecurity.harden-runner:");
|
||||
const logStreamOutput = cp.execSync(
|
||||
"log show --predicate 'subsystem == \"io.stepsecurity.harden-runner\"' --info --last 10m",
|
||||
{
|
||||
encoding: "utf8",
|
||||
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
|
||||
timeout: 5000, // 5 seconds timeout
|
||||
}
|
||||
);
|
||||
console.log(logStreamOutput);
|
||||
} catch (error) {
|
||||
console.log("Warning: Could not fetch system log stream:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWindowsCleanup() {
|
||||
// windows cleanup
|
||||
const agentDir = process.env.STATE_agentDir || "C:\\agent";
|
||||
const postEventFile = path.join(agentDir, "post_event.json");
|
||||
|
||||
if (isGithubHosted() && fs.existsSync(postEventFile)) {
|
||||
console.log("Windows post step already executed, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
const p = cp.spawn(
|
||||
"powershell.exe",
|
||||
[
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
"query user; exit $LASTEXITCODE",
|
||||
],
|
||||
{ stdio: ["ignore", "pipe", "pipe"], shell: false, windowsHide: true }
|
||||
);
|
||||
p.unref();
|
||||
|
||||
fs.writeFileSync(postEventFile, JSON.stringify({ event: "post" }));
|
||||
|
||||
// if agent is installed; wait for it to create done.json
|
||||
if (isAgentInstalled(process.platform)) {
|
||||
const doneFile = path.join(agentDir, "done.json");
|
||||
let counter = 0;
|
||||
while (true) {
|
||||
if (!fs.existsSync(doneFile)) {
|
||||
counter++;
|
||||
if (counter > 10) {
|
||||
console.log("timed out");
|
||||
break;
|
||||
}
|
||||
await sleep(1000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("stopping windows agent process...");
|
||||
const pidFile = path.join(agentDir, "agent.pid");
|
||||
|
||||
try {
|
||||
await common.addSummary();
|
||||
} catch (exception) {
|
||||
console.log(exception);
|
||||
if (!fs.existsSync(pidFile)) {
|
||||
console.log("PID file not found. Agent may not be running.");
|
||||
return;
|
||||
}
|
||||
|
||||
const pid = parseInt(fs.readFileSync(pidFile, "utf8").trim());
|
||||
console.log(`agent PID from file: ${pid}`);
|
||||
|
||||
try {
|
||||
process.kill(pid, 0); // signal 0 just checks if process exists
|
||||
} catch {
|
||||
console.log("agent process not running.");
|
||||
fs.unlinkSync(pidFile);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`stopping agent process (PID: ${pid})...`);
|
||||
process.kill(pid, "SIGINT");
|
||||
|
||||
let gracefulShutdown = false;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await sleep(1000);
|
||||
|
||||
try {
|
||||
process.kill(pid, 0); // check if still exists
|
||||
} catch {
|
||||
gracefulShutdown = true;
|
||||
console.log("agent process stopped gracefully");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gracefulShutdown) {
|
||||
console.log("graceful shutdown timeout (10s), forcing termination...");
|
||||
process.kill(pid, "SIGKILL");
|
||||
console.log("agent process terminated forcefully");
|
||||
}
|
||||
|
||||
if (fs.existsSync(pidFile)) {
|
||||
fs.unlinkSync(pidFile);
|
||||
console.log("PID file cleaned up");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("warning: error stopping agent process:", error.message);
|
||||
}
|
||||
})();
|
||||
|
||||
const log = path.join(agentDir, "agent.log");
|
||||
if (fs.existsSync(log)) {
|
||||
console.log("agent log:");
|
||||
const content = fs.readFileSync(log, "utf-8");
|
||||
console.log(content);
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
+13
-10
@@ -1,6 +1,6 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as fs from "fs";
|
||||
import { STEPSECURITY_API_URL, STEPSECURITY_WEB_URL } from "./configs";
|
||||
import { getAnnotationLogs } from "./utils";
|
||||
|
||||
export function printInfo(web_url) {
|
||||
console.log(
|
||||
@@ -67,8 +67,11 @@ export async function addSummary() {
|
||||
|
||||
let needsSubscription = false;
|
||||
try {
|
||||
let data = fs.readFileSync("/home/agent/annotation.log", "utf8");
|
||||
if (data.includes("StepSecurity Harden Runner is disabled")) {
|
||||
let data = getAnnotationLogs(process.platform);
|
||||
if (
|
||||
data !== undefined &&
|
||||
data.includes("StepSecurity Harden Runner is disabled")
|
||||
) {
|
||||
needsSubscription = true;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -97,23 +100,23 @@ export async function addSummary() {
|
||||
// Extract owner and repo from GITHUB_REPOSITORY (format: owner/repo)
|
||||
const [owner, repo] = process.env["GITHUB_REPOSITORY"]?.split("/") || [];
|
||||
const run_id = process.env["GITHUB_RUN_ID"];
|
||||
|
||||
|
||||
if (!owner || !repo || !run_id || !correlation_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch job summary from API
|
||||
const apiUrl = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/runs/${run_id}/correlation/${correlation_id}/job-markdown-summary`;
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch job summary: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const markdownSummary = await response.text();
|
||||
|
||||
|
||||
// Render the markdown summary using core.summary.addRaw
|
||||
await core.summary.addRaw(markdownSummary).write();
|
||||
return;
|
||||
@@ -126,10 +129,10 @@ export async function addSummary() {
|
||||
export const STATUS_HARDEN_RUNNER_UNAVAILABLE = "409";
|
||||
|
||||
export const CONTAINER_MESSAGE =
|
||||
"This job is running in a container. Harden Runner does not run in a container as it needs sudo access to run. This job will not be monitored.";
|
||||
"This job is running in a container. Such jobs can be monitored by installing Harden Runner in a custom VM image for GitHub-hosted runners.";
|
||||
|
||||
export const UBUNTU_MESSAGE =
|
||||
"This job is not running in a GitHub Actions Hosted Runner Ubuntu VM. Harden Runner is only supported on Ubuntu VM. This job will not be monitored.";
|
||||
export const UNSUPPORTED_RUNNER_MESSAGE =
|
||||
"This job is not running in a GitHub Actions Hosted Runner. Harden Runner is only supported on GitHub-hosted runners (Ubuntu, Windows, and macOS). This job will not be monitored.";
|
||||
|
||||
export const SELF_HOSTED_RUNNER_MESSAGE =
|
||||
"This job is running on a self-hosted runner.";
|
||||
|
||||
+11
-2
@@ -3,11 +3,20 @@ import * as core from "@actions/core";
|
||||
import isDocker from "is-docker";
|
||||
import { STEPSECURITY_WEB_URL } from "./configs";
|
||||
import { isGithubHosted } from "./tls-inspect";
|
||||
import { context } from "@actions/github";
|
||||
import { isPlatformSupported } from "./utils";
|
||||
|
||||
(async () => {
|
||||
console.log("[harden-runner] main-step");
|
||||
|
||||
if (process.platform !== "linux") {
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
const customProperties = context?.payload?.repository?.custom_properties || {};
|
||||
if (customProperties["skip-harden-runner"] === "true") {
|
||||
console.log("Skipping harden-runner: custom property 'skip-harden-runner' is set to 'true'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPlatformSupported(process.platform)) {
|
||||
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && isDocker()) {
|
||||
|
||||
+160
-3
@@ -6,6 +6,7 @@ import * as fs from "fs";
|
||||
import { verifyChecksum } from "./checksum";
|
||||
import { EOL } from "os";
|
||||
import { ARM64_RUNNER_MESSAGE } from "./common";
|
||||
import { chownForFolder } from "./utils";
|
||||
|
||||
export async function installAgent(
|
||||
isTLS: boolean,
|
||||
@@ -25,7 +26,7 @@ export async function installAgent(
|
||||
|
||||
if (isTLS) {
|
||||
downloadPath = await tc.downloadTool(
|
||||
`https://github.com/step-security/agent-ebpf/releases/download/v1.6.23/harden-runner_1.6.23_linux_${variant}.tar.gz`,
|
||||
`https://github.com/step-security/agent-ebpf/releases/download/v1.7.10/harden-runner_1.7.10_linux_${variant}.tar.gz`,
|
||||
undefined,
|
||||
auth
|
||||
);
|
||||
@@ -35,13 +36,15 @@ export async function installAgent(
|
||||
return false;
|
||||
}
|
||||
downloadPath = await tc.downloadTool(
|
||||
"https://github.com/step-security/agent/releases/download/v0.14.2/agent_0.14.2_linux_amd64.tar.gz",
|
||||
"https://github.com/step-security/agent/releases/download/v0.14.3/agent_0.14.3_linux_amd64.tar.gz",
|
||||
undefined,
|
||||
auth
|
||||
);
|
||||
}
|
||||
|
||||
verifyChecksum(downloadPath, isTLS, variant);
|
||||
if (!verifyChecksum(downloadPath, isTLS, variant, "linux")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extractPath = await tc.extractTar(downloadPath);
|
||||
|
||||
@@ -65,3 +68,157 @@ export async function installAgent(
|
||||
cp.execSync("sudo service agent start", { timeout: 15000 });
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function installMacosAgent(configStr: string): Promise<boolean> {
|
||||
const token = core.getInput("token", { required: true });
|
||||
const auth = `token ${token}`;
|
||||
|
||||
try {
|
||||
// Create working directory
|
||||
core.info("Creating /opt/step-security directory...");
|
||||
cp.execSync("sudo mkdir -p /opt/step-security");
|
||||
chownForFolder(process.env.USER, "/opt/step-security");
|
||||
core.info("✓ Successfully created /opt/step-security directory");
|
||||
|
||||
// Create agent configuration file
|
||||
core.info("Creating agent.json");
|
||||
fs.writeFileSync("/opt/step-security/agent.json", configStr);
|
||||
core.info(
|
||||
"✓ Successfully created agent.json at /opt/step-security/agent.json"
|
||||
);
|
||||
|
||||
// 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";
|
||||
core.info(`Downloading macOS installer.. : ${downloadUrl}`);
|
||||
const downloadPath = await tc.downloadTool(downloadUrl, undefined, auth);
|
||||
core.info(`✓ Successfully downloaded installer to: ${downloadPath}`);
|
||||
|
||||
// Verify SHA256 checksum
|
||||
core.info("Verifying SHA256 checksum of downloaded tar file...");
|
||||
if (!verifyChecksum(downloadPath, false, "", "darwin")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract installer package
|
||||
core.info("Extracting installer...");
|
||||
const extractPath = await tc.extractTar(downloadPath);
|
||||
core.info(`✓ Successfully extracted installer to: ${extractPath}`);
|
||||
|
||||
// Copy Installer binary to /opt/step-security
|
||||
const installerSourcePath = path.join(extractPath, "Installer");
|
||||
const installerBinaryPath = "/opt/step-security/Installer";
|
||||
|
||||
core.info(
|
||||
`Copying Installer from ${installerSourcePath} to /opt/step-security...`
|
||||
);
|
||||
cp.execFileSync("cp", [installerSourcePath, installerBinaryPath]);
|
||||
core.info("✓ Successfully copied Installer to /opt/step-security");
|
||||
|
||||
|
||||
// Verify installer binary exists
|
||||
if (!fs.existsSync(installerBinaryPath)) {
|
||||
throw new Error(
|
||||
"Installer binary not found at /opt/step-security/Installer"
|
||||
);
|
||||
}
|
||||
core.info("✓ Installer binary verified");
|
||||
|
||||
// Make installer executable
|
||||
core.info("Making installer executable...");
|
||||
cp.execSync(`chmod +x "${installerBinaryPath}"`);
|
||||
core.info("✓ Installer is now executable");
|
||||
|
||||
// Run installer
|
||||
core.info("Running installer...");
|
||||
cp.execSync(
|
||||
`sudo "${installerBinaryPath}" -workdir /opt/step-security >> /opt/step-security/agent.log 2>&1`,
|
||||
{
|
||||
shell: "/bin/bash",
|
||||
timeout: 10000, // 10 second timeout
|
||||
}
|
||||
);
|
||||
core.info("✓ Installer completed successfully");
|
||||
|
||||
core.info("✅ macOS agent installation completed successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
core.error(`❌ Failed to install macOS agent: ${error}`);
|
||||
if (error instanceof Error && error.stack) {
|
||||
core.debug(error.stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function installWindowsAgent(configStr: string): Promise<boolean> {
|
||||
const token = core.getInput("token", { required: true });
|
||||
const auth = `token ${token}`;
|
||||
|
||||
const variant = process.arch === "x64" ? "amd64" : "arm64";
|
||||
if (variant === "arm64") {
|
||||
console.log(ARM64_RUNNER_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
const agentDir = "C:\\agent";
|
||||
core.info(`Creating agent directory: ${agentDir}`);
|
||||
if (!fs.existsSync(agentDir)) {
|
||||
fs.mkdirSync(agentDir, { recursive: true });
|
||||
}
|
||||
fs.appendFileSync(process.env.GITHUB_STATE, `agentDir=${agentDir}${EOL}`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
const agentExePath = path.join(agentDir, "agent.exe");
|
||||
|
||||
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`,
|
||||
undefined,
|
||||
auth
|
||||
);
|
||||
|
||||
// validate the checksum
|
||||
if (!verifyChecksum(downloadPath, false, variant, process.platform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extractPath = await tc.extractTar(downloadPath);
|
||||
|
||||
const extractedAgentPath = path.join(extractPath, "agent.exe");
|
||||
fs.copyFileSync(extractedAgentPath, agentExePath);
|
||||
core.info(`Copied agent from ${extractedAgentPath} to ${agentExePath}`);
|
||||
|
||||
const configPath = path.join(agentDir, "config.json");
|
||||
fs.writeFileSync(configPath, configStr);
|
||||
core.info(`Created config file: ${configPath}`);
|
||||
|
||||
core.info("Starting Windows Agent...");
|
||||
|
||||
try {
|
||||
const logPath = path.join(agentDir, "agent.log");
|
||||
const logStream = fs.openSync(logPath, "a");
|
||||
core.info(`Agent logs will be written to: ${logPath}`);
|
||||
|
||||
const agentProcess = cp.spawn(agentExePath, [], {
|
||||
cwd: agentDir,
|
||||
detached: true,
|
||||
stdio: ["ignore", logStream, logStream],
|
||||
windowsHide: false,
|
||||
shell: false,
|
||||
});
|
||||
|
||||
const pidFile = path.join(agentDir, "agent.pid");
|
||||
fs.writeFileSync(pidFile, agentProcess.pid.toString());
|
||||
core.info(`Agent process started with PID: ${agentProcess.pid}`);
|
||||
core.info(`PID saved to: ${pidFile}`);
|
||||
|
||||
agentProcess.unref();
|
||||
|
||||
core.info("Windows Agent process started successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
core.setFailed(`Failed to start Windows agent process: ${errorMessage}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+71
-17
@@ -27,7 +27,13 @@ import * as utils from "@actions/cache/lib/internal/cacheUtils";
|
||||
import { isARCRunner, sendAllowedEndpoints } from "./arc-runner";
|
||||
import { STEPSECURITY_API_URL, STEPSECURITY_WEB_URL } from "./configs";
|
||||
import { isGithubHosted, isTLSEnabled } from "./tls-inspect";
|
||||
import { installAgent } from "./install-agent";
|
||||
import {
|
||||
installAgent,
|
||||
installMacosAgent,
|
||||
installWindowsAgent,
|
||||
} from "./install-agent";
|
||||
|
||||
import { chownForFolder, isAgentInstalled, isPlatformSupported } from "./utils";
|
||||
|
||||
interface MonitorResponse {
|
||||
runner_ip_address?: string;
|
||||
@@ -39,8 +45,14 @@ interface MonitorResponse {
|
||||
try {
|
||||
console.log("[harden-runner] pre-step");
|
||||
|
||||
if (process.platform !== "linux") {
|
||||
console.log(common.UBUNTU_MESSAGE);
|
||||
const customProperties = context?.payload?.repository?.custom_properties || {};
|
||||
if (customProperties["skip-harden-runner"] === "true") {
|
||||
console.log("Skipping harden-runner: custom property 'skip-harden-runner' is set to 'true'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPlatformSupported(process.platform)) {
|
||||
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && isDocker()) {
|
||||
@@ -243,6 +255,25 @@ interface MonitorResponse {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isGithubHosted() && process.env.STEP_SECURITY_HARDEN_RUNNER === "true") {
|
||||
fs.appendFileSync(process.env.GITHUB_STATE, `customVMImage=true${EOL}`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
core.info("This job is running on a custom VM image with Harden Runner installed.");
|
||||
|
||||
if (confg.egress_policy === "block") {
|
||||
sendAllowedEndpoints(confg.allowed_endpoints);
|
||||
await sleep(5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isGithubHosted() && isAgentInstalled(process.platform)) {
|
||||
console.log("Agent already installed, skipping installation");
|
||||
return;
|
||||
}
|
||||
|
||||
let _http = new httpm.HttpClient();
|
||||
let statusCode: number | undefined;
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
@@ -296,18 +327,47 @@ interface MonitorResponse {
|
||||
return;
|
||||
}
|
||||
|
||||
const confgStr = JSON.stringify(confg);
|
||||
cp.execSync("sudo mkdir -p /home/agent");
|
||||
chownForFolder(process.env.USER, "/home/agent");
|
||||
const configStr = JSON.stringify(confg);
|
||||
|
||||
let isTLS = await isTLSEnabled(context.repo.owner);
|
||||
// platform specific
|
||||
let statusFile = "";
|
||||
let logFile = "";
|
||||
let agentInstalled = false;
|
||||
|
||||
const agentInstalled = await installAgent(isTLS, confgStr);
|
||||
switch (process.platform) {
|
||||
case "linux":
|
||||
statusFile = "/home/agent/agent.status";
|
||||
logFile = "/home/agent/agent.log";
|
||||
|
||||
cp.execSync("sudo mkdir -p /home/agent");
|
||||
chownForFolder(process.env.USER, "/home/agent");
|
||||
|
||||
let isTLS = await isTLSEnabled(context.repo.owner);
|
||||
agentInstalled = await installAgent(isTLS, configStr);
|
||||
|
||||
break;
|
||||
case "win32":
|
||||
core.info("Installing Windows Agent...");
|
||||
agentInstalled = await installWindowsAgent(configStr);
|
||||
|
||||
const agentDir = process.env.STATE_agentDir || "C:\\agent";
|
||||
statusFile = path.join(agentDir, "agent.status");
|
||||
logFile = path.join(agentDir, "agent.log");
|
||||
|
||||
break;
|
||||
case "darwin":
|
||||
const installed = await installMacosAgent(configStr);
|
||||
if (!installed) {
|
||||
core.warning("😭 macos agent installation failed");
|
||||
}
|
||||
return; // early return
|
||||
default:
|
||||
throw new Error(
|
||||
`Setup failed because of unsupported platform: ${process.platform}`
|
||||
);
|
||||
}
|
||||
|
||||
if (agentInstalled) {
|
||||
// Check that the file exists locally
|
||||
var statusFile = "/home/agent/agent.status";
|
||||
var logFile = "/home/agent/agent.log";
|
||||
var counter = 0;
|
||||
while (true) {
|
||||
if (!fs.existsSync(statusFile)) {
|
||||
@@ -342,9 +402,3 @@ export function sleep(ms: number) {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function chownForFolder(newOwner: string, target: string) {
|
||||
let cmd = "sudo";
|
||||
let args = ["chown", "-R", newOwner, target];
|
||||
cp.execFileSync(cmd, args);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as cp from "child_process";
|
||||
import * as fs from "fs";
|
||||
|
||||
export function isPlatformSupported(platform: NodeJS.Platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
case "win32":
|
||||
case "darwin":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function chownForFolder(newOwner: string, target: string) {
|
||||
let cmd = "sudo";
|
||||
let args = ["chown", "-R", newOwner, target];
|
||||
cp.execFileSync(cmd, args);
|
||||
}
|
||||
|
||||
export function isAgentInstalled(platform: NodeJS.Platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
return fs.existsSync("/home/agent/agent.status");
|
||||
case "win32":
|
||||
return fs.existsSync("C:\\agent\\agent.status");
|
||||
case "darwin":
|
||||
return fs.existsSync("/opt/step-security/agent.status");
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAnnotationLogs(platform: NodeJS.Platform) {
|
||||
switch (platform) {
|
||||
case "linux":
|
||||
return fs.readFileSync("/home/agent/annotation.log", "utf8");
|
||||
case "win32":
|
||||
return fs.readFileSync("C:\\agent\\annotation.log", "utf8");
|
||||
case "darwin":
|
||||
return fs.readFileSync("/opt/step-security/annotation.log", "utf8");
|
||||
default:
|
||||
throw new Error("platform not supported");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user