Add deploy-on-self-hosted-vm option for agent installation on self-hosted runners
Installs the Harden Runner agent on self-hosted Linux VMs when enabled. Skipped if running in a container or agent is already installed. Recommended only for ephemeral runners when baking the agent into the VM image is not possible. Includes unit tests.
This commit is contained in:
parent
f808768d15
commit
ac89272860
12 changed files with 251 additions and 98 deletions
|
|
@ -40,6 +40,10 @@ inputs:
|
||||||
description: "Set to true to fetch policy from the policy store using the API key. This is the preferred method over the policy input which requires id-token: write permission. Policies can be defined and attached at workflow, repo, org, or cluster (for ARC) level in the policy store. The most granular policy will apply."
|
description: "Set to true to fetch policy from the policy store using the API key. This is the preferred method over the policy input which requires id-token: write permission. Policies can be defined and attached at workflow, repo, org, or cluster (for ARC) level in the policy store. The most granular policy will apply."
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: "false"
|
||||||
|
deploy-on-self-hosted-vm:
|
||||||
|
description: "Set to true to deploy the Harden Runner agent directly on a self-hosted runner VM (Linux only). The recommended approach for self-hosted VMs is to bake the agent into the VM image; see docs.stepsecurity.io. Use this option only if baking is not possible, and only for ephemeral runners."
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
icon: "check-square"
|
icon: "check-square"
|
||||||
|
|
|
||||||
3
dist/index.js
vendored
3
dist/index.js
vendored
|
|
@ -31910,6 +31910,9 @@ function isAgentInstalled(platform) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
|
||||||
|
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
|
||||||
|
}
|
||||||
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
3
dist/post/index.js
vendored
3
dist/post/index.js
vendored
|
|
@ -31916,6 +31916,9 @@ function isAgentInstalled(platform) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
|
||||||
|
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
|
||||||
|
}
|
||||||
function getAnnotationLogs(platform) {
|
function getAnnotationLogs(platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
|
|
|
||||||
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
173
dist/pre/index.js
vendored
173
dist/pre/index.js
vendored
|
|
@ -84974,6 +84974,7 @@ __nccwpck_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
__nccwpck_require__.d(__webpack_exports__, {
|
__nccwpck_require__.d(__webpack_exports__, {
|
||||||
|
installAgentForSelfHosted: () => (/* binding */ installAgentForSelfHosted),
|
||||||
sleep: () => (/* binding */ setup_sleep)
|
sleep: () => (/* binding */ setup_sleep)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -85031,6 +85032,9 @@ function isAgentInstalled(platform) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function shouldDeployAgentOnSelfHosted(deployOnSelfHostedVm, isContainer, agentAlreadyInstalled) {
|
||||||
|
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
|
||||||
|
}
|
||||||
function utils_getAnnotationLogs(platform) {
|
function utils_getAnnotationLogs(platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
|
|
@ -85262,48 +85266,6 @@ function fetchPolicy(owner, policyName, idToken) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function fetchPolicyFromStore(owner, repo, apiKey, workflow, runId, correlationId) {
|
|
||||||
return policy_utils_awaiter(this, void 0, void 0, function* () {
|
|
||||||
if (apiKey === "") {
|
|
||||||
throw new Error("[PolicyStoreFetch]: api-key is empty");
|
|
||||||
}
|
|
||||||
let policyEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
|
||||||
let httpClient = new lib.HttpClient();
|
|
||||||
let headers = {};
|
|
||||||
headers["Authorization"] = `vm-api-key ${apiKey}`;
|
|
||||||
headers["Source"] = "github-actions";
|
|
||||||
let response = undefined;
|
|
||||||
let err = undefined;
|
|
||||||
let retry = 0;
|
|
||||||
while (retry < 3) {
|
|
||||||
try {
|
|
||||||
console.log(`Attempt: ${retry + 1}`);
|
|
||||||
response = yield httpClient.getJson(policyEndpoint, headers);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
retry += 1;
|
|
||||||
yield sleep(1000);
|
|
||||||
}
|
|
||||||
if (response === undefined && err !== undefined) {
|
|
||||||
const error = new Error(`[Policy Store Fetch] ${err}`);
|
|
||||||
if (err.statusCode !== undefined) {
|
|
||||||
error.statusCode = err.statusCode;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
if (response.statusCode === 404) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const result = response.result;
|
|
||||||
if (!result || (!result.egress_policy && (!result.allowed_endpoints || result.allowed_endpoints.length === 0))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function mergeConfigs(localConfig, remoteConfig) {
|
function mergeConfigs(localConfig, remoteConfig) {
|
||||||
if (localConfig.allowed_endpoints === "") {
|
if (localConfig.allowed_endpoints === "") {
|
||||||
localConfig.allowed_endpoints = remoteConfig.allowed_endpoints.join(" ");
|
localConfig.allowed_endpoints = remoteConfig.allowed_endpoints.join(" ");
|
||||||
|
|
@ -85666,17 +85628,6 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
||||||
var t = {};
|
|
||||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
||||||
t[p] = s[p];
|
|
||||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
||||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
||||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
||||||
t[p[i]] = s[p[i]];
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85736,47 +85687,10 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
|
||||||
is_github_hosted: isGithubHosted(),
|
is_github_hosted: isGithubHosted(),
|
||||||
is_debug: lib_core.isDebug(),
|
is_debug: lib_core.isDebug(),
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: lib_core.getInput("api-key"),
|
deploy_on_self_hosted_vm: lib_core.getBooleanInput("deploy-on-self-hosted-vm"),
|
||||||
use_policy_store: lib_core.getBooleanInput("use-policy-store"),
|
|
||||||
};
|
};
|
||||||
if (confg.api_key !== "") {
|
|
||||||
lib_core.setSecret(confg.api_key);
|
|
||||||
}
|
|
||||||
let policyName = lib_core.getInput("policy");
|
let policyName = lib_core.getInput("policy");
|
||||||
if (confg.use_policy_store) {
|
if (policyName !== "") {
|
||||||
console.log(`Fetching policy from policy store`);
|
|
||||||
if (confg.api_key === "") {
|
|
||||||
lib_core.setFailed("api-key is required when use-policy-store is set to true");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
const repoName = (process.env["GITHUB_REPOSITORY"] || "").split("/")[1] || "";
|
|
||||||
const workflowRef = process.env["GITHUB_WORKFLOW_REF"] || "";
|
|
||||||
const workflow = workflowRef.replace(/.*\.github\/workflows\//, "").replace(/@.*/, "");
|
|
||||||
let result = yield fetchPolicyFromStore(github.context.repo.owner, repoName, confg.api_key, workflow, confg.run_id, confg.correlation_id);
|
|
||||||
if (result !== null) {
|
|
||||||
lib_core.info(`Policy found: ${result.policy_name || "unnamed"}`);
|
|
||||||
confg = mergeConfigs(confg, result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lib_core.info("No policy found in policy store. Defaulting to audit mode.");
|
|
||||||
confg.egress_policy = "audit";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
lib_core.info(`[!] ${err}`);
|
|
||||||
if (err.statusCode >= 400 && err.statusCode < 500) {
|
|
||||||
lib_core.info("Policy not found in policy store. Defaulting to audit mode.");
|
|
||||||
confg.egress_policy = "audit";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lib_core.error(`Unexpected error fetching from policy store: ${err}. Falling back to audit mode.`);
|
|
||||||
confg.egress_policy = "audit";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (policyName !== "") {
|
|
||||||
console.log(`Fetching policy from API with name: ${policyName}`);
|
console.log(`Fetching policy from API with name: ${policyName}`);
|
||||||
try {
|
try {
|
||||||
let idToken = yield lib_core.getIDToken();
|
let idToken = yield lib_core.getIDToken();
|
||||||
|
|
@ -85893,6 +85807,23 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
});
|
});
|
||||||
lib_core.info(SELF_HOSTED_RUNNER_MESSAGE);
|
lib_core.info(SELF_HOSTED_RUNNER_MESSAGE);
|
||||||
|
if (shouldDeployAgentOnSelfHosted(confg.deploy_on_self_hosted_vm, isDocker(), isAgentInstalled(process.platform))) {
|
||||||
|
if (process.platform !== "linux") {
|
||||||
|
lib_core.info("deploy-on-self-hosted-vm is only supported on Linux. Skipping agent deployment.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lib_core.info("deploy-on-self-hosted-vm is enabled. Installing agent on self-hosted runner.");
|
||||||
|
yield installAgentForSelfHosted(github.context.repo.owner, confg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (confg.deploy_on_self_hosted_vm && isDocker()) {
|
||||||
|
lib_core.info("Skipping agent deployment: running inside a container.");
|
||||||
|
}
|
||||||
|
if (confg.deploy_on_self_hosted_vm && isAgentInstalled(process.platform)) {
|
||||||
|
lib_core.info("Agent already installed on self-hosted runner, skipping installation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (confg.egress_policy === "block") {
|
if (confg.egress_policy === "block") {
|
||||||
sendAllowedEndpoints(confg.allowed_endpoints);
|
sendAllowedEndpoints(confg.allowed_endpoints);
|
||||||
yield setup_sleep(5000);
|
yield setup_sleep(5000);
|
||||||
|
|
@ -85949,8 +85880,7 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
|
||||||
console.log(HARDEN_RUNNER_UNAVAILABLE_MESSAGE);
|
console.log(HARDEN_RUNNER_UNAVAILABLE_MESSAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { api_key, use_policy_store } = confg, agentConfig = __rest(confg, ["api_key", "use_policy_store"]);
|
const configStr = JSON.stringify(confg);
|
||||||
const configStr = JSON.stringify(agentConfig);
|
|
||||||
// platform specific
|
// platform specific
|
||||||
let statusFile = "";
|
let statusFile = "";
|
||||||
let logFile = "";
|
let logFile = "";
|
||||||
|
|
@ -86015,6 +85945,61 @@ function setup_sleep(ms) {
|
||||||
setTimeout(resolve, ms);
|
setTimeout(resolve, ms);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function installAgentForSelfHosted(owner, confg) {
|
||||||
|
return setup_awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
console.log("Installing Harden Runner agent for self-hosted runner");
|
||||||
|
let isTLS = yield isTLSEnabled(owner);
|
||||||
|
if (!isTLS) {
|
||||||
|
console.log("TLS is not enabled for this organization. Agent installation skipped for self-hosted runner.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selfHostedConfig = {
|
||||||
|
customer: owner,
|
||||||
|
working_directory: confg.working_directory,
|
||||||
|
api_url: confg.api_url,
|
||||||
|
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,
|
||||||
|
is_github_hosted: false,
|
||||||
|
};
|
||||||
|
const selfHostedConfigStr = JSON.stringify(selfHostedConfig);
|
||||||
|
external_child_process_.execSync("sudo mkdir -p /home/agent");
|
||||||
|
chownForFolder(process.env.USER, "/home/agent");
|
||||||
|
const agentInstalled = yield installAgent(isTLS, selfHostedConfigStr);
|
||||||
|
if (agentInstalled) {
|
||||||
|
const statusFile = "/home/agent/agent.status";
|
||||||
|
const logFile = "/home/agent/agent.log";
|
||||||
|
let counter = 0;
|
||||||
|
while (true) {
|
||||||
|
if (!external_fs_.existsSync(statusFile)) {
|
||||||
|
counter++;
|
||||||
|
if (counter > 30) {
|
||||||
|
console.log("timed out");
|
||||||
|
if (external_fs_.existsSync(logFile)) {
|
||||||
|
const content = external_fs_.readFileSync(logFile, "utf-8");
|
||||||
|
console.log(content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
yield setup_sleep(300);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const content = external_fs_.readFileSync(statusFile, "utf-8");
|
||||||
|
console.log(content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(`Failed to install agent for self-hosted runner: ${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
|
|
@ -17,6 +17,7 @@ export interface Configuration {
|
||||||
one_time_key: string;
|
one_time_key: string;
|
||||||
api_key: string;
|
api_key: string;
|
||||||
use_policy_store: boolean;
|
use_policy_store: boolean;
|
||||||
|
deploy_on_self_hosted_vm: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PolicyResponse {
|
export interface PolicyResponse {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ test("merge configs", async () => {
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: "",
|
api_key: "",
|
||||||
use_policy_store: false,
|
use_policy_store: false,
|
||||||
|
deploy_on_self_hosted_vm: false,
|
||||||
};
|
};
|
||||||
let policyResponse: PolicyResponse = {
|
let policyResponse: PolicyResponse = {
|
||||||
owner: "h0x0er",
|
owner: "h0x0er",
|
||||||
|
|
@ -75,6 +76,7 @@ test("merge configs", async () => {
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: "",
|
api_key: "",
|
||||||
use_policy_store: false,
|
use_policy_store: false,
|
||||||
|
deploy_on_self_hosted_vm: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
localConfig = mergeConfigs(localConfig, policyResponse);
|
localConfig = mergeConfigs(localConfig, policyResponse);
|
||||||
|
|
@ -314,6 +316,7 @@ test("mergeConfigs does not override local allowed_endpoints if not empty", () =
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: "",
|
api_key: "",
|
||||||
use_policy_store: false,
|
use_policy_store: false,
|
||||||
|
deploy_on_self_hosted_vm: false,
|
||||||
};
|
};
|
||||||
let policyResponse: PolicyResponse = {
|
let policyResponse: PolicyResponse = {
|
||||||
allowed_endpoints: ["remote.endpoint:443"],
|
allowed_endpoints: ["remote.endpoint:443"],
|
||||||
|
|
@ -345,6 +348,7 @@ test("mergeConfigs overrides disable_sudo_and_containers from remote", () => {
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: "",
|
api_key: "",
|
||||||
use_policy_store: false,
|
use_policy_store: false,
|
||||||
|
deploy_on_self_hosted_vm: false,
|
||||||
};
|
};
|
||||||
let policyResponse: PolicyResponse = {
|
let policyResponse: PolicyResponse = {
|
||||||
allowed_endpoints: [],
|
allowed_endpoints: [],
|
||||||
|
|
@ -375,6 +379,7 @@ test("mergeConfigs does not override fields when remote values are undefined", (
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: "",
|
api_key: "",
|
||||||
use_policy_store: false,
|
use_policy_store: false,
|
||||||
|
deploy_on_self_hosted_vm: false,
|
||||||
};
|
};
|
||||||
let policyResponse: PolicyResponse = {
|
let policyResponse: PolicyResponse = {
|
||||||
allowed_endpoints: [],
|
allowed_endpoints: [],
|
||||||
|
|
|
||||||
69
src/setup.test.ts
Normal file
69
src/setup.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { shouldDeployAgentOnSelfHosted, isAgentInstalled, isPlatformSupported, getAnnotationLogs } from "./utils";
|
||||||
|
|
||||||
|
describe("shouldDeployAgentOnSelfHosted", () => {
|
||||||
|
test("returns true when deploy flag is true, not container, agent not installed", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(true, false, false)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when deploy flag is false", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(false, false, false)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when running in a container", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(true, true, false)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when agent is already installed", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(true, false, true)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when in container and agent installed", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(true, true, true)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when all conditions are negative", () => {
|
||||||
|
expect(shouldDeployAgentOnSelfHosted(false, true, true)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isAgentInstalled", () => {
|
||||||
|
test("returns false for linux when status file does not exist", () => {
|
||||||
|
expect(isAgentInstalled("linux")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false for win32 when status file does not exist", () => {
|
||||||
|
expect(isAgentInstalled("win32")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false for darwin when status file does not exist", () => {
|
||||||
|
expect(isAgentInstalled("darwin")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false for unsupported platform", () => {
|
||||||
|
expect(isAgentInstalled("freebsd" as NodeJS.Platform)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isPlatformSupported", () => {
|
||||||
|
test("returns true for linux", () => {
|
||||||
|
expect(isPlatformSupported("linux")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns true for win32", () => {
|
||||||
|
expect(isPlatformSupported("win32")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns true for darwin", () => {
|
||||||
|
expect(isPlatformSupported("darwin")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false for unsupported platform", () => {
|
||||||
|
expect(isPlatformSupported("freebsd" as NodeJS.Platform)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getAnnotationLogs", () => {
|
||||||
|
test("throws for unsupported platform", () => {
|
||||||
|
expect(() => getAnnotationLogs("freebsd" as NodeJS.Platform)).toThrow("platform not supported");
|
||||||
|
});
|
||||||
|
});
|
||||||
77
src/setup.ts
77
src/setup.ts
|
|
@ -37,7 +37,7 @@ import {
|
||||||
installWindowsAgent,
|
installWindowsAgent,
|
||||||
} from "./install-agent";
|
} from "./install-agent";
|
||||||
|
|
||||||
import { chownForFolder, isAgentInstalled, isPlatformSupported } from "./utils";
|
import { chownForFolder, isAgentInstalled, isPlatformSupported, shouldDeployAgentOnSelfHosted } from "./utils";
|
||||||
|
|
||||||
interface MonitorResponse {
|
interface MonitorResponse {
|
||||||
runner_ip_address?: string;
|
runner_ip_address?: string;
|
||||||
|
|
@ -89,6 +89,7 @@ interface MonitorResponse {
|
||||||
one_time_key: "",
|
one_time_key: "",
|
||||||
api_key: core.getInput("api-key"),
|
api_key: core.getInput("api-key"),
|
||||||
use_policy_store: core.getBooleanInput("use-policy-store"),
|
use_policy_store: core.getBooleanInput("use-policy-store"),
|
||||||
|
deploy_on_self_hosted_vm: core.getBooleanInput("deploy-on-self-hosted-vm"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (confg.api_key !== "") {
|
if (confg.api_key !== "") {
|
||||||
|
|
@ -294,6 +295,22 @@ interface MonitorResponse {
|
||||||
|
|
||||||
core.info(common.SELF_HOSTED_RUNNER_MESSAGE);
|
core.info(common.SELF_HOSTED_RUNNER_MESSAGE);
|
||||||
|
|
||||||
|
if (shouldDeployAgentOnSelfHosted(confg.deploy_on_self_hosted_vm, isDocker(), isAgentInstalled(process.platform))) {
|
||||||
|
if (process.platform !== "linux") {
|
||||||
|
core.info("deploy-on-self-hosted-vm is only supported on Linux. Skipping agent deployment.");
|
||||||
|
} else {
|
||||||
|
core.info("deploy-on-self-hosted-vm is enabled. Installing agent on self-hosted runner.");
|
||||||
|
await installAgentForSelfHosted(context.repo.owner, confg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (confg.deploy_on_self_hosted_vm && isDocker()) {
|
||||||
|
core.info("Skipping agent deployment: running inside a container.");
|
||||||
|
}
|
||||||
|
if (confg.deploy_on_self_hosted_vm && isAgentInstalled(process.platform)) {
|
||||||
|
core.info("Agent already installed on self-hosted runner, skipping installation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (confg.egress_policy === "block") {
|
if (confg.egress_policy === "block") {
|
||||||
sendAllowedEndpoints(confg.allowed_endpoints);
|
sendAllowedEndpoints(confg.allowed_endpoints);
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
|
|
@ -449,3 +466,61 @@ export function sleep(ms: number) {
|
||||||
setTimeout(resolve, ms);
|
setTimeout(resolve, ms);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function installAgentForSelfHosted(owner: string, confg: Configuration) {
|
||||||
|
try {
|
||||||
|
console.log("Installing Harden Runner agent for self-hosted runner");
|
||||||
|
|
||||||
|
let isTLS = await isTLSEnabled(owner);
|
||||||
|
|
||||||
|
if (!isTLS) {
|
||||||
|
console.log("TLS is not enabled for this organization. Agent installation skipped for self-hosted runner.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selfHostedConfig = {
|
||||||
|
customer: owner,
|
||||||
|
working_directory: confg.working_directory,
|
||||||
|
api_url: confg.api_url,
|
||||||
|
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,
|
||||||
|
is_github_hosted: false,
|
||||||
|
};
|
||||||
|
const selfHostedConfigStr = JSON.stringify(selfHostedConfig);
|
||||||
|
|
||||||
|
cp.execSync("sudo mkdir -p /home/agent");
|
||||||
|
chownForFolder(process.env.USER, "/home/agent");
|
||||||
|
|
||||||
|
const agentInstalled = await installAgent(isTLS, selfHostedConfigStr);
|
||||||
|
|
||||||
|
if (agentInstalled) {
|
||||||
|
const statusFile = "/home/agent/agent.status";
|
||||||
|
const logFile = "/home/agent/agent.log";
|
||||||
|
let counter = 0;
|
||||||
|
while (true) {
|
||||||
|
if (!fs.existsSync(statusFile)) {
|
||||||
|
counter++;
|
||||||
|
if (counter > 30) {
|
||||||
|
console.log("timed out");
|
||||||
|
if (fs.existsSync(logFile)) {
|
||||||
|
const content = fs.readFileSync(logFile, "utf-8");
|
||||||
|
console.log(content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await sleep(300);
|
||||||
|
} else {
|
||||||
|
const content = fs.readFileSync(statusFile, "utf-8");
|
||||||
|
console.log(content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to install agent for self-hosted runner: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@ export function isAgentInstalled(platform: NodeJS.Platform) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldDeployAgentOnSelfHosted(
|
||||||
|
deployOnSelfHostedVm: boolean,
|
||||||
|
isContainer: boolean,
|
||||||
|
agentAlreadyInstalled: boolean
|
||||||
|
): boolean {
|
||||||
|
return deployOnSelfHostedVm && !isContainer && !agentAlreadyInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
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