1
0
Fork 0

Handle tags containing "@" character in `buildSLSAProvenancePredicate`

When using some monorepo-related tools (like [changesets](https://github.com/changesets/changesets)),
the produced tags have a special format that includes `@` character.

For example, a `foo` package on a monorepo will produce Git tags looking
like `foo@1.0.0` if using changesets.

When used in combination with `actions/attest-build-provenance`, the
action was not properly re-crafting the tag in `buildSLSAProvenancePredicate` because
it was always splitting the workflow ref by `@` and taking the second
element.

This result in this error on CI:

```
Error: Error: Failed to persist attestation: Invalid Argument - values do not match: refs/tags/foo != refs/tags/foo@1.0.0 - https://docs.github.com/rest/repos/repos#create-an-attestation
````

This PR slightly update the logic there, and rather take "everything
located after the first '@'". This shouldn't introduce any breaking
change, while giving support for custom tags.

I've added the corresponding test case, it passes, however I couldn't
successfully run the full test suite (neither on `main`). Looking
forward for CI outcome.

Thanks in advance for the review 🙏.
pull/1863/head
Meriadec Pillet 2024-10-30 14:02:29 +01:00
parent 7f5921cddd
commit 717ba9d9a4
No known key found for this signature in database
GPG Key ID: A4B40783209C889D
3 changed files with 68 additions and 10 deletions

View File

@ -1,5 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`provenance functions buildSLSAProvenancePredicate handle tags including "@" character 1`] = `
{
"params": {
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "foo@1.0.0",
"repository": "https://foo.ghe.com/owner/repo",
},
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "repo-id",
"repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
},
},
"resolvedDependencies": [
{
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
},
"metadata": {
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
},
"type": "https://slsa.dev/provenance/v1",
}
`;
exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = ` exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = `
{ {
"params": { "params": {

View File

@ -33,15 +33,7 @@ describe('provenance functions', () => {
runner_environment: 'github-hosted' runner_environment: 'github-hosted'
} }
beforeEach(async () => { const mockIssuer = async (claims: jose.JWTPayload): Promise<void> => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
// Generate JWT signing key // Generate JWT signing key
const key = await jose.generateKeyPair('PS256') const key = await jose.generateKeyPair('PS256')
@ -60,6 +52,18 @@ describe('provenance functions', () => {
// Mock OIDC token endpoint for populating the provenance // Mock OIDC token endpoint for populating the provenance
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt}) nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
}
beforeEach(async () => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
await mockIssuer(claims)
}) })
afterEach(() => { afterEach(() => {
@ -71,6 +75,16 @@ describe('provenance functions', () => {
const predicate = await buildSLSAProvenancePredicate() const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot() expect(predicate).toMatchSnapshot()
}) })
it('handle tags including "@" character', async () => {
nock.cleanAll()
await mockIssuer({
...claims,
workflow_ref: 'owner/repo/.github/workflows/main.yml@foo@1.0.0'
})
const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot()
})
}) })
describe('attestProvenance', () => { describe('attestProvenance', () => {

View File

@ -30,9 +30,11 @@ export const buildSLSAProvenancePredicate = async (
// Split just the path and ref from the workflow string. // Split just the path and ref from the workflow string.
// owner/repo/.github/workflows/main.yml@main => // owner/repo/.github/workflows/main.yml@main =>
// .github/workflows/main.yml, main // .github/workflows/main.yml, main
const [workflowPath, workflowRef] = claims.workflow_ref const [workflowPath, ...workflowRefChunks] = claims.workflow_ref
.replace(`${claims.repository}/`, '') .replace(`${claims.repository}/`, '')
.split('@') .split('@')
// Handle case where tag contains `@` (e.g: when using changesets in a monorepo context),
const workflowRef = workflowRefChunks.join('@')
return { return {
type: SLSA_PREDICATE_V1_TYPE, type: SLSA_PREDICATE_V1_TYPE,