Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure no self-hosted runners for generic generators #3298

Draft
wants to merge 62 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
2d96347
init
ramonpetgrave64 Feb 20, 2024
ddcc650
add octokit/rest
ramonpetgrave64 Feb 23, 2024
5d88260
add ensureOnlyGithubHostedRunners
ramonpetgrave64 Feb 23, 2024
13d7944
add tests
ramonpetgrave64 Feb 23, 2024
085b7fc
cleanup
ramonpetgrave64 Feb 23, 2024
b03db34
typo
ramonpetgrave64 Feb 23, 2024
0da0e24
more cleanup
ramonpetgrave64 Feb 23, 2024
6b62976
stronger error check
ramonpetgrave64 Feb 23, 2024
f46f333
use my forl
ramonpetgrave64 Feb 23, 2024
8f40c6b
rebuild
ramonpetgrave64 Feb 23, 2024
6274116
console log
ramonpetgrave64 Feb 23, 2024
037fc49
redo tests
ramonpetgrave64 Feb 23, 2024
f38f55d
guidance on version tags
ramonpetgrave64 Feb 24, 2024
4d4019d
change builder-repo
ramonpetgrave64 Feb 24, 2024
7010c15
use my fork
ramonpetgrave64 Feb 24, 2024
6facd38
@v1.0.1
ramonpetgrave64 Feb 24, 2024
e38b943
console log
ramonpetgrave64 Feb 27, 2024
2944894
console log
ramonpetgrave64 Feb 27, 2024
ca56348
1.05 check common labels
ramonpetgrave64 Feb 27, 2024
4e08579
all labels and token as secret
ramonpetgrave64 Feb 28, 2024
f679c36
feat: Create pull_request_template.md (#3268)
ramonpetgrave64 Feb 22, 2024
501a3da
Empty-Commit
ramonpetgrave64 Feb 28, 2024
1668fab
with tests
ramonpetgrave64 Feb 28, 2024
6f3bb4c
release ready
ramonpetgrave64 Feb 28, 2024
9a47ed5
better comment
ramonpetgrave64 Feb 28, 2024
4193213
include container workflow
ramonpetgrave64 Feb 28, 2024
225f091
lint
ramonpetgrave64 Feb 28, 2024
b556d7b
lint
ramonpetgrave64 Feb 28, 2024
f897af7
Merge branch 'main' into ramonpetgrave-slsa-build3-check-runners
ramonpetgrave64 Feb 28, 2024
fe7609d
use || github.token
ramonpetgrave64 Feb 29, 2024
4678ead
debug messages
ramonpetgrave64 Feb 29, 2024
7a74ced
checkout the repo in e2e tests
ramonpetgrave64 Feb 29, 2024
fe955ad
checkout this pr
ramonpetgrave64 Feb 29, 2024
41fcc01
my repo for checkout
ramonpetgrave64 Feb 29, 2024
cd06320
typo
ramonpetgrave64 Feb 29, 2024
b95587e
really use my repo
ramonpetgrave64 Feb 29, 2024
6150a30
ref in an env
ramonpetgrave64 Feb 29, 2024
9d14b5f
literal, not env
ramonpetgrave64 Feb 29, 2024
b833b02
no space
ramonpetgrave64 Feb 29, 2024
7b59ecc
actions tester fo rPRs
ramonpetgrave64 Feb 29, 2024
3fc7e0e
core.info, await the ensure, typos
ramonpetgrave64 Feb 29, 2024
2eb7e73
cleanup
ramonpetgrave64 Feb 29, 2024
3428f55
test for main.run
ramonpetgrave64 Feb 29, 2024
e80265d
lint and release
ramonpetgrave64 Feb 29, 2024
93a1ca5
back to main for ../../workflows/pre-submit.e2e.generic.default.yml
ramonpetgrave64 Feb 29, 2024
e3e1988
helpful messages, cleanup
ramonpetgrave64 Feb 29, 2024
484e2e3
cleanup
ramonpetgrave64 Feb 29, 2024
bad40fb
cleanup
ramonpetgrave64 Feb 29, 2024
6de0b8a
init: new action
ramonpetgrave64 Mar 4, 2024
9aa60ff
reorganize messages
ramonpetgrave64 Mar 5, 2024
f7bc052
more obvious errors
ramonpetgrave64 Mar 5, 2024
b06eb0e
same^
ramonpetgrave64 Mar 5, 2024
6e481ee
use the separate action
ramonpetgrave64 Mar 5, 2024
11156a7
revert original checker to main
ramonpetgrave64 Mar 5, 2024
de3a273
celanup
ramonpetgrave64 Mar 5, 2024
a6b84e1
lint
ramonpetgrave64 Mar 5, 2024
aa88858
e2e
ramonpetgrave64 Mar 5, 2024
2a79896
also for is-fork
ramonpetgrave64 Mar 5, 2024
6199195
back to futue main
ramonpetgrave64 Mar 5, 2024
23a4d0b
docs
ramonpetgrave64 Mar 5, 2024
169c4f6
changelog
ramonpetgrave64 Mar 5, 2024
285cf5e
lint
ramonpetgrave64 Mar 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 94 additions & 6 deletions .github/actions/detect-workflow-js/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { create } from "domain";
const core = require("@actions/core");
const detect = require("../src/detect");
const github = require("@actions/github");
const OctokitRest = require("@octokit/rest");

describe("decodeToken", () => {
it("return email and job_workflow_ref", () => {
Expand Down Expand Up @@ -52,9 +53,8 @@ describe("detectWorkflowFromOIDC", () => {
core.getIDToken.mockClear();
core.getIDToken.mockReturnValueOnce(jwt);

const [repo, ref, workflow] = await detect.detectWorkflowFromOIDC(
"some/audience",
);
const [repo, ref, workflow] =
await detect.detectWorkflowFromOIDC("some/audience");
expect(repo).toBe("octo-org/octo-automation");
expect(ref).toBe("refs/heads/main");
expect(workflow).toBe(".github/workflows/oidc.yml");
Expand All @@ -72,9 +72,8 @@ describe("detectWorkflowFromOIDC", () => {
core.getIDToken.mockClear();
core.getIDToken.mockReturnValueOnce(jwt);

const [repo, ref, workflow] = await detect.detectWorkflowFromOIDC(
"some/audience",
);
const [repo, ref, workflow] =
await detect.detectWorkflowFromOIDC("some/audience");
expect(repo).toBe("vitejs/vite");
expect(ref).toBe("refs/tags/[email protected]");
expect(workflow).toBe(".github/workflows/publish.yml");
Expand Down Expand Up @@ -125,6 +124,8 @@ function createOctokitMock() {
listWorkflowRuns: jest.fn(),
getWorkflowRun: jest.fn(),
reRunWorkflow: jest.fn(),
listJobsForWorkflowRun: jest.fn(),
listSelfHostedRunnersForRepo: jest.fn(),
},
pulls: {
get: jest.fn(),
Expand Down Expand Up @@ -264,3 +265,90 @@ describe("detectWorkflowFromContext", () => {
);
});
});

function createOctokitRestMock() {
return {
...createOctokitMock(),
paginate: jest.fn(),
};
}

jest.mock("@octokit/rest", () => ({ Octokit: jest.fn() }));

describe("ensureOnlyGithubHostedRunners", () => {
OctokitRest.Octokit.mockClear();
const octokitRest = createOctokitRestMock();
OctokitRest.Octokit.mockReturnValue(octokitRest);

const customRunnerLabel = "cloudtop";
const goodListJobsResp = [
{
id: 399444496,
run_id: 29679449,
name: "myjob",
labels: ["foo", "bar"],
},
];
const badListJobsResp = [
...goodListJobsResp,
{
...goodListJobsResp[0],
labels: [customRunnerLabel, "baz"],
},
];
const listRunnersResp = [
{
id: 24,
name: "my-gh-runner",
os: "Linux",
status: "online",
busy: true,
labels: [
{ id: 1, name: "self-hosted", type: "read-only" },
{ id: 2, name: "Linux", type: "read-only" },
{ id: 3, name: "X64", type: "read-only" },
{ id: 7, name: customRunnerLabel, type: "custom" },
],
},
];

it("no workflow run", async () => {
octokitRest.paginate.mockRejectedValue(new Error("any"));

expect(
detect.ensureOnlyGithubHostedRunners("unused", "unused"),
).rejects.toThrow();
});

it("success: no jobs not using self-hosted runner", async () => {
octokitRest.paginate.mockImplementation((method: any, args: any) => {
switch (method) {
case octokitRest.rest.actions.listJobsForWorkflowRun:
return Promise.resolve(goodListJobsResp);
case octokitRest.rest.actions.listSelfHostedRunnersForRepo:
return Promise.resolve(listRunnersResp);
}
});
expect(
detect.ensureOnlyGithubHostedRunners("unused", "unused"),
).resolves.toBeUndefined();
});

it("failure: some jobs using self-hosted runner", async () => {
octokitRest.paginate.mockImplementation((method: any, args: any) => {
switch (method) {
case octokitRest.rest.actions.listJobsForWorkflowRun:
return Promise.resolve(badListJobsResp);
case octokitRest.rest.actions.listSelfHostedRunnersForRepo:
return Promise.resolve(listRunnersResp);
}
});
expect(
detect.ensureOnlyGithubHostedRunners("unused", "unused"),
).rejects.toThrow(
new Error(
`Self-hosted runners are not allowed in SLSA Level 3 workflows. labels: ${customRunnerLabel}`,
),
);
});
});
112 changes: 112 additions & 0 deletions .github/actions/detect-workflow-js/__tests__/main_run.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const core = require("@actions/core");
const detect = require("../src/detect");
const main = require("../src/main");

jest.mock("../src/detect");
jest.mock("@actions/core");

beforeEach(() => {
jest.resetModules();
core.setFailed.mockClear();
core.setOutput.mockClear();
detect.ensureOnlyGithubHostedRunners.mockClear();
detect.detectWorkflowFromContext.mockClear();
});

describe("main.run", () => {
const [repo, ref, builderWorkflow] = ["slsa-framework/gundam", "123", "xyz"];
const genericGeneratorWorkflow =
".github/workflows/generator_generic_slsa3.yml";
process.env.GITHUB_REPOSITORY = repo;

it("run succeeds: workflow is NOT generic generator", async () => {
detect.detectWorkflowFromContext.mockReturnValue(
Promise.resolve([repo, ref, builderWorkflow]),
);

await main.run();

expect(detect.detectWorkflowFromContext).toHaveBeenCalled();
expect(detect.ensureOnlyGithubHostedRunners).not.toHaveBeenCalled();

expect(core.setFailed).not.toHaveBeenCalled();

expect(core.setOutput).toHaveBeenCalledWith("repository", repo);
expect(core.setOutput).toHaveBeenCalledWith("ref", ref);
expect(core.setOutput).toHaveBeenCalledWith("workflow", builderWorkflow);
});

it("run succeeds: workflow is a generic generator, no self-hosted runners", async () => {
detect.detectWorkflowFromContext.mockReturnValue(
Promise.resolve([repo, ref, genericGeneratorWorkflow]),
);
detect.ensureOnlyGithubHostedRunners.mockReturnValue(Promise.resolve(null));

await main.run();

expect(detect.detectWorkflowFromContext).toHaveBeenCalled();
expect(detect.ensureOnlyGithubHostedRunners).toHaveBeenCalled();

expect(core.setFailed).not.toHaveBeenCalled();

expect(core.setOutput).toHaveBeenCalledWith("repository", repo);
expect(core.setOutput).toHaveBeenCalledWith("ref", ref);
expect(core.setOutput).toHaveBeenCalledWith(
"workflow",
genericGeneratorWorkflow,
);
});

it("run fails: can't get workflow details", async () => {
const errMsg = "can't get workflow details";
detect.detectWorkflowFromContext.mockRejectedValue(new Error(errMsg));
detect.ensureOnlyGithubHostedRunners.mockReturnValue(Promise.resolve(null));

await main.run();

expect(detect.detectWorkflowFromContext).toHaveBeenCalled();
expect(detect.ensureOnlyGithubHostedRunners).not.toHaveBeenCalled();

expect(core.setFailed).toHaveBeenCalledWith(errMsg);

expect(core.setOutput).not.toHaveBeenCalled();
expect(core.setOutput).not.toHaveBeenCalled();
expect(core.setOutput).not.toHaveBeenCalled();
});

it("run fails: generic workflow, but using self-hosted runner", async () => {
const errMsg = "no self-hosted runners allowed";
detect.detectWorkflowFromContext.mockReturnValue(
Promise.resolve([repo, ref, genericGeneratorWorkflow]),
);
detect.ensureOnlyGithubHostedRunners.mockRejectedValue(new Error(errMsg));

await main.run();

expect(detect.detectWorkflowFromContext).toHaveBeenCalled();
expect(detect.ensureOnlyGithubHostedRunners).toHaveBeenCalled();

expect(core.setFailed).toHaveBeenCalledWith(errMsg);

expect(core.setOutput).toHaveBeenCalledWith("repository", repo);
expect(core.setOutput).toHaveBeenCalledWith("ref", ref);
expect(core.setOutput).toHaveBeenCalledWith(
"workflow",
genericGeneratorWorkflow,
);
});
});
Loading
Loading