1
0
Fork 0
mirror of synced 2026-06-05 18:28:19 +00:00

Fix policy store to use correct API endpoint and auth scheme

Use /actions/policies/workflow-policy with query params (workflow,
run_id, correlationId) and vm-api-key auth header to match the
existing backend API. Update action.yml descriptions to clarify
policy store is the preferred method.
This commit is contained in:
Varun Sharma 2026-04-03 20:45:39 -07:00
commit 85b3620336
6 changed files with 56 additions and 27 deletions

View file

@ -29,15 +29,15 @@ inputs:
required: false
default: "false"
policy:
description: "Policy name to be used from the policy store"
description: "Policy name to be used from the policy store. Requires id-token: write permission."
required: false
default: ""
api-key:
description: "StepSecurity API key for authenticating with the policy store"
description: "StepSecurity API key for authenticating with the policy store. Required when use-policy-store is set to true."
required: false
default: ""
use-policy-store:
description: "Set to true to fetch policy from the policy store using the API key"
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
default: "false"

9
dist/pre/index.js vendored
View file

@ -85262,15 +85262,15 @@ function fetchPolicy(owner, policyName, idToken) {
}
});
}
function fetchPolicyFromStore(owner, repo, apiKey) {
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/policy-store/policy`;
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"] = `api-key ${apiKey}`;
headers["Authorization"] = `vm-api-key ${apiKey}`;
headers["Source"] = "github-actions";
let response = undefined;
let err = undefined;
@ -85733,7 +85733,8 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
else {
try {
const repoName = (process.env["GITHUB_REPOSITORY"] || "").split("/")[1] || "";
let result = yield fetchPolicyFromStore(github.context.repo.owner, repoName, confg.api_key);
const workflow = process.env["GITHUB_WORKFLOW"] || "";
let result = yield fetchPolicyFromStore(github.context.repo.owner, repoName, confg.api_key, workflow, confg.run_id, confg.correlation_id);
if (result !== null) {
confg = mergeConfigs(confg, result);
}

File diff suppressed because one or more lines are too long

View file

@ -146,9 +146,15 @@ test("fetchPolicy preserves statusCode from error", async () => {
import { fetchPolicyFromStore } from "./policy-utils";
const policyStoreQueryString = (workflow: string, runId: string, correlationId: string) =>
`workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
test("success: fetches policy from store", async () => {
const owner = "test-owner";
const repo = "test-repo";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
const response = {
allowed_endpoints: ["registry.npmjs.org:443", "github.com:443"],
egress_policy: "block",
@ -157,78 +163,90 @@ test("success: fetches policy from store", async () => {
};
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.reply(200, response);
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
expect(result).toStrictEqual(response);
});
test("fetchPolicyFromStore throws when apiKey is empty", async () => {
await expect(
fetchPolicyFromStore("owner", "repo", "")
fetchPolicyFromStore("owner", "repo", "", "ci.yml", "123", "abc")
).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";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.reply(404, { message: "not found" });
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
expect(result).toBeNull();
});
test("fetchPolicyFromStore retries on failure and succeeds", async () => {
const owner = "test-owner";
const repo = "test-repo";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
const response = {
allowed_endpoints: ["example.com:443"],
egress_policy: "audit",
};
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.replyWithError("timeout");
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.reply(200, response);
const result = await fetchPolicyFromStore(owner, repo, "my-api-key");
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
expect(result).toStrictEqual(response);
});
test("fetchPolicyFromStore throws after all retries exhausted", async () => {
const owner = "test-owner";
const repo = "test-repo";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.times(3)
.replyWithError("connection refused");
await expect(
fetchPolicyFromStore(owner, repo, "my-api-key")
fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId)
).rejects.toThrow("[Policy Store Fetch]");
});
test("fetchPolicyFromStore preserves statusCode from error", async () => {
const owner = "test-owner";
const repo = "test-repo";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
const errorWithStatus = new Error("Unauthorized");
(errorWithStatus as any).statusCode = 401;
nock(`${STEPSECURITY_API_URL}`)
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.times(3)
.replyWithError(errorWithStatus);
try {
await fetchPolicyFromStore(owner, repo, "my-api-key");
await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
fail("should have thrown");
} catch (err) {
expect(err.message).toContain("[Policy Store Fetch]");
@ -239,17 +257,20 @@ test("fetchPolicyFromStore sends correct authorization header", async () => {
const owner = "test-owner";
const repo = "test-repo";
const apiKey = "secret-key-123";
const workflow = "ci.yml";
const runId = "12345";
const correlationId = "abc-def";
nock(`${STEPSECURITY_API_URL}`, {
reqheaders: {
Authorization: `api-key ${apiKey}`,
Authorization: `vm-api-key ${apiKey}`,
Source: "github-actions",
},
})
.get(`/github/${owner}/${repo}/actions/policy-store/policy`)
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
.reply(200, { allowed_endpoints: [], egress_policy: "audit" });
const result = await fetchPolicyFromStore(owner, repo, apiKey);
const result = await fetchPolicyFromStore(owner, repo, apiKey, workflow, runId, correlationId);
expect(result).toStrictEqual({
allowed_endpoints: [],
egress_policy: "audit",

View file

@ -53,18 +53,21 @@ export async function fetchPolicy(
export async function fetchPolicyFromStore(
owner: string,
repo: string,
apiKey: string
apiKey: string,
workflow: string,
runId: string,
correlationId: 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 policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
let httpClient = new HttpClient();
let headers = {};
headers["Authorization"] = `api-key ${apiKey}`;
headers["Authorization"] = `vm-api-key ${apiKey}`;
headers["Source"] = "github-actions";
let response = undefined;

View file

@ -99,10 +99,14 @@ interface MonitorResponse {
} else {
try {
const repoName = (process.env["GITHUB_REPOSITORY"] || "").split("/")[1] || "";
const workflow = process.env["GITHUB_WORKFLOW"] || "";
let result: PolicyResponse | null = await fetchPolicyFromStore(
context.repo.owner,
repoName,
confg.api_key
confg.api_key,
workflow,
confg.run_id,
confg.correlation_id
);
if (result !== null) {
confg = mergeConfigs(confg, result);