From 5baf6a2c6274d9838556617e9ff5b66fe87ede51 Mon Sep 17 00:00:00 2001 From: Brian DeHamer Date: Fri, 30 Jun 2023 07:02:39 -0700 Subject: [PATCH] feat: SLSA 1.0 provenance statement (#6613) Generates a SLSA 1.0 compliant provenance statement for packages published from GitHub Actions. Signed-off-by: Brian DeHamer --- workspaces/libnpmpublish/lib/provenance.js | 82 ++++++++++------------ workspaces/libnpmpublish/test/publish.js | 22 +++--- 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/workspaces/libnpmpublish/lib/provenance.js b/workspaces/libnpmpublish/lib/provenance.js index 19859e9dd6f61..398db1b4cd467 100644 --- a/workspaces/libnpmpublish/lib/provenance.js +++ b/workspaces/libnpmpublish/lib/provenance.js @@ -4,12 +4,13 @@ const ci = require('ci-info') const { env } = process const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json' -const INTOTO_STATEMENT_TYPE = 'https://in-toto.io/Statement/v0.1' -const SLSA_PREDICATE_TYPE = 'https://slsa.dev/provenance/v0.2' +const INTOTO_STATEMENT_V01_TYPE = 'https://in-toto.io/Statement/v0.1' +const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1' +const SLSA_PREDICATE_V02_TYPE = 'https://slsa.dev/provenance/v0.2' +const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1' -const GITHUB_BUILDER_ID = 'https://github.com/actions/runner' -const GITHUB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gha' -const GITHUB_BUILD_TYPE_VERSION = 'v2' +const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner' +const GITHUB_BUILD_TYPE = 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1' const GITLAB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gitlab' const GITLAB_BUILD_TYPE_VERSION = 'v0alpha1' @@ -18,63 +19,54 @@ const generateProvenance = async (subject, opts) => { let payload if (ci.GITHUB_ACTIONS) { /* istanbul ignore next - not covering missing env var case */ - const [workflowPath] = (env.GITHUB_WORKFLOW_REF || '') + const [workflowPath, workflowRef] = (env.GITHUB_WORKFLOW_REF || '') .replace(env.GITHUB_REPOSITORY + '/', '') .split('@') payload = { - _type: INTOTO_STATEMENT_TYPE, + _type: INTOTO_STATEMENT_V1_TYPE, subject, - predicateType: SLSA_PREDICATE_TYPE, + predicateType: SLSA_PREDICATE_V1_TYPE, predicate: { - buildType: `${GITHUB_BUILD_TYPE_PREFIX}/${GITHUB_BUILD_TYPE_VERSION}`, - builder: { id: GITHUB_BUILDER_ID }, - invocation: { - configSource: { - uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`, - digest: { - sha1: env.GITHUB_SHA, + buildDefinition: { + buildType: GITHUB_BUILD_TYPE, + externalParameters: { + workflow: { + ref: workflowRef, + repository: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`, + path: workflowPath, }, - entryPoint: workflowPath, }, - parameters: {}, - environment: { - GITHUB_EVENT_NAME: env.GITHUB_EVENT_NAME, - GITHUB_REF: env.GITHUB_REF, - GITHUB_REPOSITORY: env.GITHUB_REPOSITORY, - GITHUB_REPOSITORY_ID: env.GITHUB_REPOSITORY_ID, - GITHUB_REPOSITORY_OWNER_ID: env.GITHUB_REPOSITORY_OWNER_ID, - GITHUB_RUN_ATTEMPT: env.GITHUB_RUN_ATTEMPT, - GITHUB_RUN_ID: env.GITHUB_RUN_ID, - GITHUB_SHA: env.GITHUB_SHA, - GITHUB_WORKFLOW_REF: env.GITHUB_WORKFLOW_REF, - GITHUB_WORKFLOW_SHA: env.GITHUB_WORKFLOW_SHA, + internalParameters: { + github: { + event_name: env.GITHUB_EVENT_NAME, + repository_id: env.GITHUB_REPOSITORY_ID, + repository_owner_id: env.GITHUB_REPOSITORY_OWNER_ID, + }, }, + resolvedDependencies: [ + { + uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`, + digest: { + gitCommit: env.GITHUB_SHA, + }, + }, + ], }, - metadata: { - buildInvocationId: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}`, - completeness: { - parameters: false, - environment: false, - materials: false, + runDetails: { + builder: { id: `${GITHUB_BUILDER_ID_PREFIX}/${env.RUNNER_ENVIRONMENT}` }, + metadata: { + /* eslint-disable-next-line max-len */ + invocationId: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}/attempts/${env.GITHUB_RUN_ATTEMPT}`, }, - reproducible: false, }, - materials: [ - { - uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`, - digest: { - sha1: env.GITHUB_SHA, - }, - }, - ], }, } } if (ci.GITLAB) { payload = { - _type: INTOTO_STATEMENT_TYPE, + _type: INTOTO_STATEMENT_V01_TYPE, subject, - predicateType: SLSA_PREDICATE_TYPE, + predicateType: SLSA_PREDICATE_V02_TYPE, predicate: { buildType: `${GITLAB_BUILD_TYPE_PREFIX}/${GITLAB_BUILD_TYPE_VERSION}`, builder: { id: `${env.CI_PROJECT_URL}/-/runners/${env.CI_RUNNER_ID}` }, diff --git a/workspaces/libnpmpublish/test/publish.js b/workspaces/libnpmpublish/test/publish.js index d83f6e7a58ace..6744443e6843c 100644 --- a/workspaces/libnpmpublish/test/publish.js +++ b/workspaces/libnpmpublish/test/publish.js @@ -610,6 +610,7 @@ t.test('publish existing package with provenance in gha', async t => { const sha = 'deadbeef' const runID = '123456' const runAttempt = '1' + const runnerEnv = 'github-hosted' // Set-up GHA environment variables mockGlobals(t, { @@ -625,6 +626,7 @@ t.test('publish existing package with provenance in gha', async t => { GITHUB_SHA: sha, GITHUB_RUN_ID: runID, GITHUB_RUN_ATTEMPT: runAttempt, + RUNNER_ENVIRONMENT: runnerEnv, }, }) @@ -635,10 +637,10 @@ t.test('publish existing package with provenance in gha', async t => { }, } - const expectedConfigSource = { - uri: `git+${serverUrl}/${repository}@${ref}`, - digest: { sha1: sha }, - entryPoint: workflowPath, + const expectedWorkflow = { + ref: ref, + repository: `${serverUrl}/${repository}`, + path: workflowPath, } const log = [] @@ -785,14 +787,14 @@ t.test('publish existing package with provenance in gha', async t => { t.hasStrict(provenance.subject[0], expectedSubject, 'provenance subject matches expectations') - t.hasStrict(provenance.predicate.buildType, - 'https://github.com/npm/cli/gha/v2', + t.hasStrict(provenance.predicate.buildDefinition.buildType, + 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1', 'buildType matches expectations') - t.hasStrict(provenance.predicate.builder.id, - 'https://github.com/actions/runner', + t.hasStrict(provenance.predicate.runDetails.builder.id, + `https://github.com/actions/runner/${runnerEnv}`, 'builder id matches expectations') - t.hasStrict(provenance.predicate.invocation.configSource, - expectedConfigSource, + t.hasStrict(provenance.predicate.buildDefinition.externalParameters.workflow, + expectedWorkflow, 'configSource matches expectations') return true }).reply(201, {})