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

feat: Record vars in BYOB workflows #3636

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 35 additions & 43 deletions .github/actions/verify-token/__tests__/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import { updateSLSAToken } from "../src/inputs";
import { rawTokenInterface } from "../src/types";

describe("updateSLSAToken", () => {
it("no inputs", async () => {
const inputs = JSON.parse("{}");
const token = createToken(inputs);
it("no inputs, no vars", async () => {
const inputs = new Map<string, string>();
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -33,11 +34,12 @@ on:
);
});

it("remove bool", async () => {
it("remove bool input", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -61,11 +63,12 @@ on:
);
});

it("remove empty string", async () => {
it("remove empty string input", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -89,11 +92,12 @@ on:
);
});

it("remove integer", async () => {
it("remove integer input", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -117,11 +121,12 @@ on:
);
});

it("remove string", async () => {
it("remove string input", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -146,22 +151,12 @@ on:
});

it("no 'on' field", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const inputs = new Map<string, string>();
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
ona:
workflow_call:
secrets:
registry-password:
inputs:
name1:
required: false
name3:
required: false
name4:
required: false
`;

expect(() => {
Expand All @@ -170,22 +165,12 @@ ona:
});

it("no 'workflow_call' field", async () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const inputs = new Map<string, string>();
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_calla:
secrets:
registry-password:
inputs:
name1:
required: false
name3:
required: false
name4:
required: false
`;

expect(() => {
Expand All @@ -197,7 +182,8 @@ on:
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -214,7 +200,8 @@ on:
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const token = createToken(inputs);
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -229,8 +216,9 @@ on:
});

it("no 'inputs' field no workflow inputs", async () => {
const inputs = JSON.parse("{}");
const token = createToken(inputs);
const inputs = new Map<string, string>();
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -244,8 +232,9 @@ on:
});

it("empty 'inputs' field no workflow inputs", async () => {
const inputs = JSON.parse("{}");
const token = createToken(inputs);
const inputs = new Map<string, string>();
const vars = new Map<string, string>();
const token = createToken(inputs, vars);
const content = `
on:
workflow_call:
Expand All @@ -262,6 +251,7 @@ on:

function createToken(
inputs: Map<string, string | number | boolean>,
vars: Map<string, string>,
): rawTokenInterface {
const token: rawTokenInterface = {
version: 1,
Expand Down Expand Up @@ -314,6 +304,8 @@ function createToken(
},
inputs: inputs,
masked_inputs: [],
vars: vars,
masked_vars: [],
},
};
return token;
Expand Down
2 changes: 2 additions & 0 deletions .github/actions/verify-token/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ function createToken(obj: githubObj, sha1: string): rawTokenInterface {
},
inputs: new Map(),
masked_inputs: [],
vars: new Map(),
masked_vars: [],
},
};
return token;
Expand Down
32 changes: 22 additions & 10 deletions .github/actions/verify-token/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ describe("validateFieldNonEmpty", () => {

function createToken(
inputs: Map<string, string | number | boolean>,
masked: string[],
maskedInputs: string[],
vars: Map<string, string>,
maskedVars: string[],
): rawTokenInterface {
const token: rawTokenInterface = {
version: 1,
Expand Down Expand Up @@ -199,7 +201,9 @@ function createToken(
},
},
inputs: inputs,
masked_inputs: masked,
masked_inputs: maskedInputs,
vars: vars,
masked_vars: maskedVars,
},
};
return token;
Expand All @@ -210,8 +214,10 @@ describe("validateAndMaskInputs", () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const masked = ["name2", "name3", "name4"];
const token = createToken(inputs, masked);
const maskedInputs = ["name2", "name3", "name4"];
const vars = new Map<string, string>();
const maskedVars: string[] = [];
const token = createToken(inputs, maskedInputs, vars, maskedVars);
expect(validateAndMaskInputs(token).tool.inputs).toEqual(
new Map<string, string | number | boolean>([
["name1", "value1"],
Expand All @@ -226,8 +232,10 @@ describe("validateAndMaskInputs", () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const masked = ["name2"];
const token = createToken(inputs, masked);
const maskedInputs = ["name2"];
const vars = new Map<string, string>();
const maskedVars: string[] = [];
const token = createToken(inputs, maskedInputs, vars, maskedVars);
expect(validateAndMaskInputs(token).tool.inputs).toEqual(
new Map<string, string | number | boolean>([
["name1", "value1"],
Expand All @@ -242,8 +250,10 @@ describe("validateAndMaskInputs", () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const masked = [""];
const token = createToken(inputs, masked);
const maskedInputs = [""];
const vars = new Map<string, string>();
const maskedVars: string[] = [];
const token = createToken(inputs, maskedInputs, vars, maskedVars);
expect(validateAndMaskInputs(token).tool.inputs).toEqual(
new Map<string, string | number | boolean>([
["name1", "value1"],
Expand All @@ -258,8 +268,10 @@ describe("validateAndMaskInputs", () => {
const inputs = JSON.parse(
'{"name1": "value1", "name2": 2, "name3": "", "name4": true}',
);
const masked = ["does-not-exist"];
const token = createToken(inputs, masked);
const maskedInputs = ["does-not-exist"];
const vars = new Map<string, string>();
const maskedVars: string[] = [];
const token = createToken(inputs, maskedInputs, vars, maskedVars);
expect(() => {
validateAndMaskInputs(token);
}).toThrow();
Expand Down
8 changes: 6 additions & 2 deletions .github/actions/verify-token/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ function createPredicate(rawTokenObj, toolURI, token) {
parameters: {
// NOTE: the Map object needs to be converted to an object to serialize to JSON.
inputs: Object.fromEntries(rawTokenObj.tool.inputs),
// BYOB TRW workflows could potentially allow arbitrary untrusted code to
// be run which may make use of vars if used in the TCA.
vars: Object.fromEntries(rawTokenObj.tool.vars),
},
environment: {
GITHUB_ACTOR_ID: rawTokenObj.github.actor_id,
Expand Down Expand Up @@ -592,8 +595,9 @@ function createPredicate(rawTokenObj, toolURI, token, isGenerator) {
// BYOB framework. Some of these values may be masked by the TRW.
// NOTE: the Map object needs to be converted to an object to serialize to JSON.
inputs: Object.fromEntries(rawTokenObj.tool.inputs),
// Variables are always empty for BYOB / builders.
vars: {},
// BYOB TRW workflows could potentially allow arbitrary untrusted code to
// be run which may make use of vars if used in the TCA.
vars: Object.fromEntries(rawTokenObj.tool.vars),
};
}
// Put GitHub event payload into internalParameters.
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/verify-token/dist/index.js.map

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions .github/actions/verify-token/src/predicate02.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,12 @@ export async function createPredicate(
],
};

// BYOB TRW workflows could potentially use vars.
if (rawTokenObj.tool.vars !== undefined) {
predicate.invocation.parameters.vars = Object.fromEntries(
rawTokenObj.tool.vars,
);
}

return predicate;
}
22 changes: 17 additions & 5 deletions .github/actions/verify-token/src/predicate1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,31 +102,43 @@ export async function createPredicate(
};

// Construct the predicate according to the type of builder.
// TODO(#2186): Add source:URI under externalParameters
if (isGenerator) {
// TODO(#2202): Re-visit generator's source reporting
predicate.buildDefinition.externalParameters = {
workflow: {
ref: triggerRef,
repository: `git+https://github.com/${triggerRepository}`,
path: triggerPath,
},
// TODO(#1555): record the vars.
vars: {},

// Generators should always record the vars as generators do not have
// insight into which, if any, vars affect the build.
vars: Object.fromEntries(rawTokenObj.tool.vars),

// TODO(#2164): record the inputs, depending on the type of trigger events.
inputs: {},
};

// TODO(#2164): Support generators in BYOB.
// Throw an error for now. We have no generators using v1.0 yet
// and it's not supported in the slsa-verifier.
throw new Error("not supported: #2164, #1555, #2202, #2186");
throw new Error("not supported");
} else {
// NOTE: the workflow information is available in the internalParameters.GITHUB_WORKFLOW_REF.
predicate.buildDefinition.externalParameters = {
// Inputs to the TRW, which define the interface of the builder for the
// BYOB framework. Some of these values may be masked by the TRW.
// NOTE: the Map object needs to be converted to an object to serialize to JSON.
inputs: Object.fromEntries(rawTokenObj.tool.inputs),
// Variables are always empty for BYOB / builders.
vars: {},
};

// BYOB TRW workflows could potentially use vars.
if (rawTokenObj.tool.vars !== undefined) {
predicate.buildDefinition.externalParameters.vars = Object.fromEntries(
rawTokenObj.tool.vars,
);
}
}

// Put GitHub event payload into internalParameters.
Expand Down
5 changes: 5 additions & 0 deletions .github/actions/verify-token/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export interface rawTokenInterface {
inputs: Map<string, string | number | boolean>;
// masked_inputs is a list of input names who's value should be masked in the provenance.
masked_inputs: string[];

// vars holds the vars context.
vars: Map<string, string>;
// masked_vars is a list of var names who's value should be masked in the provenance.
masked_vars: string[];
};
}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/builder_bazel_slsa3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ jobs:
slsa-runner-label: "ubuntu-latest"
slsa-build-action-path: "./internal/builders/bazel"
slsa-workflow-inputs: ${{ toJson(inputs) }}
# NOTE: vars not recorded because they aren't used in this TRW
# or in the callback action.

slsa-run:
needs: [slsa-setup]
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/builder_gradle_slsa3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ jobs:
slsa-runner-label: "ubuntu-latest"
slsa-build-action-path: "./internal/builders/gradle"
slsa-workflow-inputs: ${{ toJson(inputs) }}
# NOTE: vars not recorded because they aren't used in this TRW
# or in the callback action.

slsa-run:
needs: [slsa-setup]
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/builder_maven_slsa3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ jobs:
slsa-runner-label: "ubuntu-latest"
slsa-build-action-path: "./internal/builders/maven"
slsa-workflow-inputs: "${{ toJson(inputs) }}"
# NOTE: vars not recorded because they aren't used in this TRW
# or in the callback action.

slsa-run:
needs: slsa-setup
Expand Down
Loading
Loading