1
0
Fork 0
mirror of synced 2026-06-05 12:38:19 +00:00

Add policy store support with api-key authentication

Adds use-policy-store and api-key inputs to allow fetching policies
from the StepSecurity policy store. Defaults to audit mode when no
policy is found. Includes unit tests with 100% coverage.
This commit is contained in:
Varun Sharma 2026-04-03 19:59:54 -07:00
commit bf693cb624
7 changed files with 440 additions and 4 deletions

View file

@ -32,6 +32,14 @@ inputs:
description: "Policy name to be used from the policy store"
required: false
default: ""
api-key:
description: "StepSecurity API key for authenticating with the policy store"
required: false
default: ""
use-policy-store:
description: "Set to true to fetch policy from the policy store using the API key"
required: false
default: "false"
branding:
icon: "check-square"

72
dist/pre/index.js vendored
View file

@ -85262,6 +85262,44 @@ function fetchPolicy(owner, policyName, idToken) {
}
});
}
function fetchPolicyFromStore(owner, repo, apiKey) {
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/policy-store/policy`;
let httpClient = new lib.HttpClient();
let headers = {};
headers["Authorization"] = `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;
}
return response.result;
});
}
function mergeConfigs(localConfig, remoteConfig) {
if (localConfig.allowed_endpoints === "") {
localConfig.allowed_endpoints = remoteConfig.allowed_endpoints.join(" ");
@ -85683,9 +85721,41 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
is_github_hosted: isGithubHosted(),
is_debug: lib_core.isDebug(),
one_time_key: "",
api_key: lib_core.getInput("api-key"),
use_policy_store: lib_core.getBooleanInput("use-policy-store"),
};
let policyName = lib_core.getInput("policy");
if (policyName !== "") {
if (confg.use_policy_store) {
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] || "";
let result = yield fetchPolicyFromStore(github.context.repo.owner, repoName, confg.api_key);
if (result !== null) {
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}`);
try {
let idToken = yield lib_core.getIDToken();

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,8 @@ export interface Configuration {
private: string;
is_debug: boolean;
one_time_key: string;
api_key: string;
use_policy_store: boolean;
}
export interface PolicyResponse {

View file

@ -43,6 +43,8 @@ test("merge configs", async () => {
is_github_hosted: true,
is_debug: false,
one_time_key: "",
api_key: "",
use_policy_store: false,
};
let policyResponse: PolicyResponse = {
owner: "h0x0er",
@ -71,8 +73,280 @@ test("merge configs", async () => {
is_github_hosted: true,
is_debug: false,
one_time_key: "",
api_key: "",
use_policy_store: false,
};
localConfig = mergeConfigs(localConfig, policyResponse);
expect(localConfig).toStrictEqual(expectedConfiguration);
});
// ==================== additional fetchPolicy tests ====================
test("fetchPolicy throws when idToken is empty", async () => {
await expect(fetchPolicy("owner", "policy1", "")).rejects.toThrow(
"[PolicyFetch]: id-token in empty"
);
});
test("fetchPolicy retries on failure and succeeds", async () => {
const owner = "test-owner";
const policyName = "test-policy";
const response = {
allowed_endpoints: ["example.com:443"],
egress_policy: "block",
};
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/actions/policies/${policyName}`)
.replyWithError("connection timeout");
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/actions/policies/${policyName}`)
.reply(200, response);
const policy = await fetchPolicy(owner, policyName, "token123");
expect(policy).toStrictEqual(response);
});
test("fetchPolicy throws after all retries exhausted", async () => {
const owner = "test-owner";
const policyName = "test-policy";
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/actions/policies/${policyName}`)
.times(3)
.replyWithError("connection timeout");
await expect(
fetchPolicy(owner, policyName, "token123")
).rejects.toThrow("[Policy Fetch]");
});
test("fetchPolicy preserves statusCode from error", async () => {
const owner = "test-owner";
const policyName = "test-policy";
const errorWithStatus = new Error("Not Found");
(errorWithStatus as any).statusCode = 404;
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/actions/policies/${policyName}`)
.times(3)
.replyWithError(errorWithStatus);
try {
await fetchPolicy(owner, policyName, "token123");
fail("should have thrown");
} catch (err) {
expect(err.message).toContain("[Policy Fetch]");
}
});
// ==================== fetchPolicyFromStore ====================
import { fetchPolicyFromStore } from "./policy-utils";
test("success: fetches policy from store", async () => {
const owner = "test-owner";
const repo = "test-repo";
const response = {
allowed_endpoints: ["registry.npmjs.org:443", "github.com:443"],
egress_policy: "block",
disable_sudo: true,
disable_file_monitoring: false,
};
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.reply(200, response);
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
expect(result).toStrictEqual(response);
});
test("fetchPolicyFromStore throws when apiKey is empty", async () => {
await expect(
fetchPolicyFromStore("owner", "repo", "")
).rejects.toThrow("[PolicyStoreFetch]: api-key is empty");
});
test("fetchPolicyFromStore returns null when policy not found (404)", async () => {
const owner = "test-owner";
const repo = "test-repo";
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.reply(404, { message: "not found" });
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
expect(result).toBeNull();
});
test("fetchPolicyFromStore retries on failure and succeeds", async () => {
const owner = "test-owner";
const repo = "test-repo";
const response = {
allowed_endpoints: ["example.com:443"],
egress_policy: "audit",
};
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.replyWithError("timeout");
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.reply(200, response);
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
expect(result).toStrictEqual(response);
});
test("fetchPolicyFromStore throws after all retries exhausted", async () => {
const owner = "test-owner";
const repo = "test-repo";
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.times(3)
.replyWithError("connection refused");
await expect(
fetchPolicyFromStore(owner, repo, "my-api-key")
).rejects.toThrow("[Policy Store Fetch]");
});
test("fetchPolicyFromStore preserves statusCode from error", async () => {
const owner = "test-owner";
const repo = "test-repo";
const errorWithStatus = new Error("Unauthorized");
(errorWithStatus as any).statusCode = 401;
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.times(3)
.replyWithError(errorWithStatus);
try {
await fetchPolicyFromStore(owner, repo, "my-api-key");
fail("should have thrown");
} catch (err) {
expect(err.message).toContain("[Policy Store Fetch]");
}
});
test("fetchPolicyFromStore sends correct authorization header", async () => {
const owner = "test-owner";
const repo = "test-repo";
const apiKey = "secret-key-123";
nock(`${STEPSECURITY_API_URL}`, {
reqheaders: {
Authorization: `api-key ${apiKey}`,
Source: "github-actions",
},
})
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.reply(200, { allowed_endpoints: [], egress_policy: "audit" });
const result = await fetchPolicyFromStore(owner, repo, apiKey);
expect(result).toStrictEqual({
allowed_endpoints: [],
egress_policy: "audit",
});
});
// ==================== additional mergeConfigs tests ====================
test("mergeConfigs does not override local allowed_endpoints if not empty", () => {
let localConfig: Configuration = {
repo: "test/repo",
run_id: "xyx",
correlation_id: "aaaaa",
working_directory: "/xyz",
api_url: "xyz",
telemetry_url: "xyz",
allowed_endpoints: "local.endpoint:443",
egress_policy: "audit",
disable_telemetry: false,
disable_sudo: false,
disable_sudo_and_containers: false,
disable_file_monitoring: false,
private: "true",
is_github_hosted: true,
is_debug: false,
one_time_key: "",
api_key: "",
use_policy_store: false,
};
let policyResponse: PolicyResponse = {
allowed_endpoints: ["remote.endpoint:443"],
egress_policy: "block",
};
localConfig = mergeConfigs(localConfig, policyResponse);
expect(localConfig.allowed_endpoints).toBe("local.endpoint:443");
expect(localConfig.egress_policy).toBe("block");
});
test("mergeConfigs overrides disable_sudo_and_containers from remote", () => {
let localConfig: Configuration = {
repo: "test/repo",
run_id: "xyx",
correlation_id: "aaaaa",
working_directory: "/xyz",
api_url: "xyz",
telemetry_url: "xyz",
allowed_endpoints: "",
egress_policy: "audit",
disable_telemetry: false,
disable_sudo: false,
disable_sudo_and_containers: false,
disable_file_monitoring: false,
private: "true",
is_github_hosted: true,
is_debug: false,
one_time_key: "",
api_key: "",
use_policy_store: false,
};
let policyResponse: PolicyResponse = {
allowed_endpoints: [],
disable_sudo_and_containers: true,
};
localConfig = mergeConfigs(localConfig, policyResponse);
expect(localConfig.disable_sudo_and_containers).toBe(true);
});
test("mergeConfigs does not override fields when remote values are undefined", () => {
let localConfig: Configuration = {
repo: "test/repo",
run_id: "xyx",
correlation_id: "aaaaa",
working_directory: "/xyz",
api_url: "xyz",
telemetry_url: "xyz",
allowed_endpoints: "",
egress_policy: "block",
disable_telemetry: false,
disable_sudo: true,
disable_sudo_and_containers: true,
disable_file_monitoring: true,
private: "true",
is_github_hosted: true,
is_debug: false,
one_time_key: "",
api_key: "",
use_policy_store: false,
};
let policyResponse: PolicyResponse = {
allowed_endpoints: [],
};
localConfig = mergeConfigs(localConfig, policyResponse);
expect(localConfig.disable_sudo).toBe(true);
expect(localConfig.disable_sudo_and_containers).toBe(true);
expect(localConfig.disable_file_monitoring).toBe(true);
expect(localConfig.egress_policy).toBe("block");
});

View file

@ -50,6 +50,57 @@ export async function fetchPolicy(
}
}
export async function fetchPolicyFromStore(
owner: string,
repo: string,
apiKey: string
): Promise<PolicyResponse | null> {
if (apiKey === "") {
throw new Error("[PolicyStoreFetch]: api-key is empty");
}
let policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policy-store/policy`;
let httpClient = new HttpClient();
let headers = {};
headers["Authorization"] = `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 = await httpClient.getJson<PolicyResponse>(
policyEndpoint,
headers
);
break;
} catch (e) {
err = e;
}
retry += 1;
await sleep(1000);
}
if (response === undefined && err !== undefined) {
const error = new Error(`[Policy Store Fetch] ${err}`);
if (err.statusCode !== undefined) {
(error as any).statusCode = err.statusCode;
}
throw error;
}
if (response.statusCode === 404) {
return null;
}
return response.result;
}
export function mergeConfigs(
localConfig: Configuration,
remoteConfig: PolicyResponse

View file

@ -16,7 +16,7 @@ import {
isValidEvent,
} from "./cache";
import { Configuration, PolicyResponse } from "./interfaces";
import { fetchPolicy, mergeConfigs } from "./policy-utils";
import { fetchPolicy, fetchPolicyFromStore, mergeConfigs } from "./policy-utils";
import * as cache from "@actions/cache";
import { getCacheEntry } from "@actions/cache/lib/internal/cacheHttpClient";
import * as cacheTwirpClient from "@actions/cache/lib/internal/shared/cacheTwirpClient";
@ -87,10 +87,41 @@ interface MonitorResponse {
is_github_hosted: isGithubHosted(),
is_debug: core.isDebug(),
one_time_key: "",
api_key: core.getInput("api-key"),
use_policy_store: core.getBooleanInput("use-policy-store"),
};
let policyName = core.getInput("policy");
if (policyName !== "") {
if (confg.use_policy_store) {
console.log(`Fetching policy from policy store`);
if (confg.api_key === "") {
core.setFailed("api-key is required when use-policy-store is set to true");
} else {
try {
const repoName = (process.env["GITHUB_REPOSITORY"] || "").split("/")[1] || "";
let result: PolicyResponse | null = await fetchPolicyFromStore(
context.repo.owner,
repoName,
confg.api_key
);
if (result !== null) {
confg = mergeConfigs(confg, result);
} else {
core.info("No policy found in policy store. Defaulting to audit mode.");
confg.egress_policy = "audit";
}
} catch (err) {
core.info(`[!] ${err}`);
if (err.statusCode >= 400 && err.statusCode < 500) {
core.info("Policy not found in policy store. Defaulting to audit mode.");
confg.egress_policy = "audit";
} else {
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}`);
try {
let idToken: string = await core.getIDToken();