Skip to content

Commit

Permalink
feat: SLSA 1.0 provenance statement (#6613)
Browse files Browse the repository at this point in the history
Generates a SLSA 1.0 compliant provenance statement for packages
published from GitHub Actions.

Signed-off-by: Brian DeHamer <bdehamer@github.com>
  • Loading branch information
bdehamer authored Jun 30, 2023
1 parent 89b2741 commit 5baf6a2
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 55 deletions.
82 changes: 37 additions & 45 deletions workspaces/libnpmpublish/lib/provenance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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}` },
Expand Down
22 changes: 12 additions & 10 deletions workspaces/libnpmpublish/test/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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,
},
})

Expand All @@ -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 = []
Expand Down Expand Up @@ -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, {})
Expand Down

0 comments on commit 5baf6a2

Please sign in to comment.