Merge pull request #186 from step-security/cache-solve

Add cache endpoint to allowed list automatically
This commit is contained in:
Varun Sharma 2022-09-29 10:35:13 -07:00 committed by GitHub
commit 2e205a28d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 66025 additions and 3857 deletions

12
dist/index.js vendored
View file

@ -484,8 +484,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.OidcClient = void 0;
const http_client_1 = __nccwpck_require__(925);
const auth_1 = __nccwpck_require__(702);
const http_client_1 = __nccwpck_require__(59);
const auth_1 = __nccwpck_require__(402);
const core_1 = __nccwpck_require__(186);
class OidcClient {
static createHttpClient(allowRetry = true, maxRetry = 10) {
@ -599,7 +599,7 @@ exports.toCommandProperties = toCommandProperties;
/***/ }),
/***/ 702:
/***/ 402:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
@ -665,7 +665,7 @@ exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHand
/***/ }),
/***/ 925:
/***/ 59:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
"use strict";
@ -673,7 +673,7 @@ exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHand
Object.defineProperty(exports, "__esModule", ({ value: true }));
const http = __nccwpck_require__(605);
const https = __nccwpck_require__(211);
const pm = __nccwpck_require__(443);
const pm = __nccwpck_require__(437);
let tunnel;
var HttpCodes;
(function (HttpCodes) {
@ -1210,7 +1210,7 @@ exports.HttpClient = HttpClient;
/***/ }),
/***/ 443:
/***/ 437:
/***/ ((__unused_webpack_module, exports) => {
"use strict";

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

1
dist/post/cache.txt vendored Normal file
View file

@ -0,0 +1 @@
# This is sample cache file

59822
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

2015
dist/pre/index.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

927
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "step-security-harden-runner",
"version": "1.4.3",
"version": "1.5.0",
"description": "Security agent for GitHub-hosted runner to monitor the build process",
"main": "index.js",
"scripts": {
@ -22,10 +22,11 @@
},
"homepage": "https://github.com/step-security/harden-runner#readme",
"dependencies": {
"@actions/cache": "^3.0.4",
"@actions/core": "^1.5.0",
"@actions/exec": "^1.1.0",
"@actions/github": "^5.0.0",
"@actions/http-client": "^1.0.11",
"@actions/http-client": "^2.0.1",
"@actions/tool-cache": "^1.7.1",
"ansi-regex": ">=5.0.1",
"is-docker": "^3.0.0",

121
src/cache.ts Normal file
View file

@ -0,0 +1,121 @@
import * as core from "@actions/core";
import { HttpClient } from "@actions/http-client";
import { RequestOptions } from "@actions/http-client/lib/interfaces";
import { BearerCredentialHandler } from "@actions/http-client/lib/auth";
import * as crypto from "crypto";
const versionSalt = "1.0";
export const cacheKey = "harden-runner-cacheKey";
export const cacheFile = "/home/agent/cache.txt";
function getCacheApiUrl(resource: string): string {
const baseUrl: string = process.env["ACTIONS_CACHE_URL"] || "";
if (!baseUrl) {
throw new Error("Cache Service Url not found, unable to restore cache.");
}
const url = `${baseUrl}_apis/artifactcache/${resource}`;
core.debug(`Resource Url: ${url}`);
return url;
}
function createAcceptHeader(type: string, apiVersion: string): string {
return `${type};api-version=${apiVersion}`;
}
function getRequestOptions(): RequestOptions {
const token = process.env["ACTIONS_RUNTIME_TOKEN"] || "";
const requestOptions: RequestOptions = {
headers: {
Accept: createAcceptHeader("application/json", "6.0-preview.1"),
Authorization: `Bearer ${token}`,
},
};
return requestOptions;
}
function createHttpClient(): HttpClient {
const token = process.env["ACTIONS_RUNTIME_TOKEN"] || "";
const bhandler = new BearerCredentialHandler(token);
return new HttpClient("actions/cache", [bhandler], getRequestOptions());
}
export function getCacheVersion(
paths: string[],
compressionMethod?: CompressionMethod
): string {
const components = paths.concat(
!compressionMethod || compressionMethod === CompressionMethod.Gzip
? []
: [compressionMethod]
);
// Add salt to cache version to support breaking changes in cache entry
components.push(versionSalt);
return crypto.createHash("sha256").update(components.join("|")).digest("hex");
}
export async function getCacheEntry(
keys: string[],
paths: string[],
options?: InternalCacheOptions
): Promise<ArtifactCacheEntry | null> {
const httpClient = createHttpClient();
const version = getCacheVersion(paths, options?.compressionMethod);
const resource = `cache?keys=${encodeURIComponent(
keys.join(",")
)}&version=${version}`;
const response = await httpClient.getJson<ArtifactCacheEntry>(
getCacheApiUrl(resource)
);
if (response.statusCode === 204) {
throw new Error("Request returned 204 status");
}
if (!isSuccessStatusCode(response.statusCode)) {
throw new Error(`Cache service responded with ${response.statusCode}`);
}
const cacheResult = response.result;
const cacheDownloadUrl = cacheResult?.archiveLocation;
if (!cacheDownloadUrl) {
throw new Error("Cache still be done, but not found.");
}
return cacheResult;
}
export interface InternalCacheOptions {
compressionMethod?: CompressionMethod;
cacheSize?: number;
}
export interface ArtifactCacheEntry {
cacheKey?: string;
scope?: string;
creationTime?: string;
archiveLocation?: string;
}
function isSuccessStatusCode(statusCode?: number): boolean {
if (!statusCode) {
return false;
}
return statusCode >= 200 && statusCode < 300;
}
export enum CompressionMethod {
Gzip = "gzip",
// Long range mode was added to zstd in v1.3.2.
// This enum is for earlier version of zstd that does not have --long support
ZstdWithoutLong = "zstd-without-long",
Zstd = "zstd",
}
// Refer: https://github.com/actions/cache/blob/12681847c623a9274356751fdf0a63576ff3f846/src/utils/actionUtils.ts#L53
const RefKey = "GITHUB_REF";
export function isValidEvent(): boolean {
return RefKey in process.env && Boolean(process.env[RefKey]);
}

View file

@ -10,7 +10,7 @@ export function verifyChecksum(downloadPath: string) {
.digest("hex"); // checksum of downloaded file
const expectedChecksum: string =
"2a65aa1423e36c53b0ccea732e280de72cd2f1584d876e385402abecac3c6807"; // checksum for v0.10.3
"7027c15a988395f3dde5e77d9a58889669adbda52fbd527ae8216e6d81dd8b1a"; // checksum for v0.11.0
if (checksum !== expectedChecksum) {
core.setFailed(

View file

@ -3,6 +3,9 @@ import * as cp from "child_process";
import * as core from "@actions/core";
import * as common from "./common";
import isDocker from "is-docker";
import * as cache from "@actions/cache";
import { cacheFile, cacheKey, isValidEvent } from "./cache";
import path from "path";
(async () => {
if (process.platform !== "linux") {
@ -59,12 +62,23 @@ import isDocker from "is-docker";
});
}
if (!fs.existsSync(doneFile)) {
var journalLog = cp.execSync("sudo journalctl -u agent.service", {
encoding: "utf8",
});
console.log("Service log:");
console.log(journalLog);
// Always log the service log
var journalLog = cp.execSync("sudo journalctl -u agent.service", {
encoding: "utf8",
});
console.log("Service log:");
console.log(journalLog);
if (isValidEvent()) {
try {
const cmd = "cp";
const args = [path.join(__dirname, "cache.txt"), cacheFile];
cp.execFileSync(cmd, args);
const cacheResult = await cache.saveCache([cacheFile], cacheKey);
console.log(cacheResult);
} catch (exception) {
console.log(exception);
}
}
})();

View file

@ -8,6 +8,13 @@ import * as common from "./common";
import * as tc from "@actions/tool-cache";
import { verifyChecksum } from "./checksum";
import isDocker from "is-docker";
import {
cacheFile,
cacheKey,
CompressionMethod,
getCacheEntry,
isValidEvent,
} from "./cache";
(async () => {
try {
@ -25,6 +32,8 @@ import isDocker from "is-docker";
var api_url = `https://${env}.api.stepsecurity.io/v1`;
var web_url = "https://app.stepsecurity.io";
console.log(`Step Security Job Correlation ID: ${correlation_id}`);
const confg = {
repo: process.env["GITHUB_REPOSITORY"],
run_id: process.env["GITHUB_RUN_ID"],
@ -36,6 +45,24 @@ import isDocker from "is-docker";
disable_telemetry: core.getBooleanInput("disable-telemetry"),
};
if (isValidEvent()) {
try {
const cacheEntry = await getCacheEntry([cacheKey], [cacheFile], {
compressionMethod: CompressionMethod.ZstdWithoutLong,
});
const url = new URL(cacheEntry.archiveLocation);
core.info(`Adding cacheHost: ${url.hostname}:443 to allowed-endpoints`);
confg.allowed_endpoints += ` ${url.hostname}:443`;
} catch (exception) {
// some exception has occurred.
core.info("Unable to fetch cacheURL");
if (confg.egress_policy === "block") {
core.info("Switching egress-policy to audit mode");
confg.egress_policy = "audit";
}
}
}
if (confg.egress_policy !== "audit" && confg.egress_policy !== "block") {
core.setFailed("egress-policy must be either audit or block");
}
@ -71,7 +98,7 @@ import isDocker from "is-docker";
let auth = `token ${token}`;
const downloadPath: string = await tc.downloadTool(
"https://github.com/step-security/agent/releases/download/v0.10.3/agent_0.10.3_linux_amd64.tar.gz",
"https://github.com/step-security/agent/releases/download/v0.11.0/agent_0.11.0_linux_amd64.tar.gz",
undefined,
auth
);
@ -79,8 +106,6 @@ import isDocker from "is-docker";
verifyChecksum(downloadPath); // NOTE: verifying agent's checksum, before extracting
const extractPath = await tc.extractTar(downloadPath);
console.log(`Step Security Job Correlation ID: ${correlation_id}`);
if (!confg.disable_telemetry || confg.egress_policy === "audit") {
common.printInfo(web_url);
}