1
0
Fork 0
mirror of synced 2026-06-05 11:08:19 +00:00

combined windows and macos changes

This commit is contained in:
Jatin 2026-02-02 15:04:58 +05:30
commit ef9c6372b3
No known key found for this signature in database
GPG key ID: 0C17698EE30CA603
16 changed files with 130385 additions and 122113 deletions

12433
dist/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

12685
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

32597
dist/pre/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -10,12 +10,17 @@ const CHECKSUMS = {
non_tls: {
amd64: "336093af8ebe969567b66fd035af3bd4f7e1c723ce680d6b4b5b2a1f79bc329e", // v0.14.2
},
darwin: "caaacc24bbf6a39ba7560e5e4701353c537883cb3ab9553359bd5caf5097246f", // v0.0.1
windows: {
amd64: "9e4fde66331be3261ae6ff954e531e94335b5774ac7e105f0126b391ee1c6d66", // v1.0.0-int
},
};
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 +30,28 @@ 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:
throw new Error(`Unsupported platform: ${platform}`);
}
if (checksum !== expectedChecksum) {
core.setFailed(
`Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`
`Checksum verification failed, expected ${expectedChecksum} instead got ${checksum}`
);
return;
}
core.debug("Checksum verification passed.");
core.info(`✅ Checksum verification passed. checksum=${checksum}`);
}

View file

@ -1,10 +1,13 @@
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 } from "./utils";
(async () => {
console.log("[harden-runner] post-step");
@ -14,8 +17,8 @@ import { context } from "@actions/github";
return;
}
if (process.platform !== "linux") {
console.log(common.UBUNTU_MESSAGE);
if (!isPlatformSupported(process.platform)) {
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
return;
}
if (isGithubHosted() && isDocker()) {
@ -36,10 +39,6 @@ import { context } from "@actions/github";
return;
}
if (process.env.STATE_isTLS === "false" && process.arch === "arm64") {
return;
}
if (
String(process.env.STATE_monitorStatusCode) ===
common.STATUS_HARDEN_RUNNER_UNAVAILABLE
@ -48,6 +47,30 @@ import { context } from "@actions/github";
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;
@ -69,8 +92,8 @@ import { context } from "@actions/github";
break;
}
await sleep(1000);
} // The file *does* exist
else {
} else {
// The file *does* exist
break;
}
}
@ -114,13 +137,152 @@ import { context } from "@actions/github";
console.log("Warning: Could not fetch service logs:", error.message);
}
}
}
async function handleMacosCleanup() {
fs.writeFileSync(
"/opt/step-security/post_event.json",
JSON.stringify({ event: "post" })
);
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 macAgenLog = "/opt/step-security/agent.log";
if (fs.existsSync(macAgenLog)) {
console.log("macAgenLog:");
var content = fs.readFileSync(macAgenLog, "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: 10000, // 10 second 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" }));
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) => {

View file

@ -97,23 +97,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;
@ -128,8 +128,8 @@ export const STATUS_HARDEN_RUNNER_UNAVAILABLE = "409";
export 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.";
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.";

View file

@ -4,6 +4,8 @@ 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");
@ -13,8 +15,8 @@ import { context } from "@actions/github";
return;
}
if (process.platform !== "linux") {
console.log(common.UBUNTU_MESSAGE);
if (!isPlatformSupported(process.platform)) {
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
return;
}
if (isGithubHosted() && isDocker()) {

View file

@ -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,
@ -41,7 +42,7 @@ export async function installAgent(
);
}
verifyChecksum(downloadPath, isTLS, variant);
verifyChecksum(downloadPath, isTLS, variant, "linux");
const extractPath = await tc.extractTar(downloadPath);
@ -65,3 +66,149 @@ export async function installAgent(
cp.execSync("sudo service agent start", { timeout: 15000 });
return true;
}
export async function installMacosAgent(confgStr: 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", confgStr);
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/v1.0.0-int/macos-installer-0.0.1.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...");
verifyChecksum(downloadPath, false, "", "darwin");
// 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");
core.info(
`Copying Installer from ${installerSourcePath} to /opt/step-security...`
);
cp.execSync(`cp "${installerSourcePath}" /opt/step-security/`);
core.info("✓ Successfully copied Installer to /opt/step-security");
const installerBinaryPath = "/opt/step-security/Installer";
// 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-int/harden-runner-agent-windows_int_windows_amd64.tar.gz`,
undefined,
auth
);
verifyChecksum(downloadPath, false, variant, process.platform);
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) {
core.setFailed(`Failed to start Windows agent process: ${error.message}`);
return false;
}
}

View file

@ -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;
@ -45,8 +51,8 @@ interface MonitorResponse {
return;
}
if (process.platform !== "linux") {
console.log(common.UBUNTU_MESSAGE);
if (!isPlatformSupported(process.platform)) {
console.log(common.UNSUPPORTED_RUNNER_MESSAGE);
return;
}
if (isGithubHosted() && isDocker()) {
@ -263,7 +269,7 @@ interface MonitorResponse {
return;
}
if (isGithubHosted() && fs.existsSync("/home/agent/agent.status")) {
if (isGithubHosted() && isAgentInstalled(process.platform)) {
console.log("Agent already installed, skipping installation");
return;
}
@ -322,17 +328,46 @@ interface MonitorResponse {
}
const confgStr = JSON.stringify(confg);
cp.execSync("sudo mkdir -p /home/agent");
chownForFolder(process.env.USER, "/home/agent");
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, confgStr);
break;
case "win32":
core.info("Installing Windows Agent...");
agentInstalled = await installWindowsAgent(confgStr);
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(confgStr);
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)) {
@ -367,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);
}

33
src/utils.ts Normal file
View file

@ -0,0 +1,33 @@
import * as cp from "child_process";
import * as fs from "fs";
export function isPlatformSupported(platform: string) {
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: string) {
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;
}
}