Skip to content

Commit e0251f0

Browse files
committed
libnpmpublish: Add GitLab CI provenance.
This is a first pass at provenance generation for GitLab CI. This is based loosely off of existing GitLab provenance documents: https://about.gitlab.com/blog/2022/11/30/achieve-slsa-level-2-compliance-with-gitlab/ https://gist.github.com/wlynch/c7fd8f53adc77d3c0ec82356e4d43cb5 Currently this pulls values from environment variables. I'm aware we want to pull this data from authenticated JWTs for GitHub provenance, but I don't know what is in flight so I am starting here for now, marking as v1alpha1 until we have more confidence in the provenance spec.
1 parent fae5e00 commit e0251f0

File tree

5 files changed

+401
-24
lines changed

5 files changed

+401
-24
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15291,7 +15291,7 @@
1529115291
"npm-registry-fetch": "^14.0.3",
1529215292
"proc-log": "^3.0.0",
1529315293
"semver": "^7.3.7",
15294-
"sigstore": "^1.0.0",
15294+
"sigstore": "^1.3.0",
1529515295
"ssri": "^10.0.1"
1529615296
},
1529715297
"devDependencies": {

workspaces/libnpmpublish/lib/provenance.js

Lines changed: 161 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
11
const { sigstore } = require('sigstore')
2+
const ci = require('ci-info')
3+
const { env } = process
24

35
const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
46
const INTOTO_STATEMENT_TYPE = 'https://in-toto.io/Statement/v0.1'
57
const SLSA_PREDICATE_TYPE = 'https://slsa.dev/provenance/v0.2'
68

7-
const BUILDER_ID = 'https://github.com/actions/runner'
8-
const BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gha'
9-
const BUILD_TYPE_VERSION = 'v2'
9+
const GITHUB_BUILDER_ID = 'https://github.com/actions/runner'
10+
const GITHUB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gha'
11+
const GITHUB_BUILD_TYPE_VERSION = 'v2'
1012

1113
const generateProvenance = async (subject, opts) => {
12-
const { env } = process
14+
var provenance
15+
switch (true) {
16+
case ci.GITHUB_ACTIONS:
17+
provenance = githubProvenance(subject)
18+
break
19+
case ci.GITLAB:
20+
provenance = gitlabProvenance(subject)
21+
break
22+
default:
23+
throw Object.assign(
24+
new Error('Unsupported provenance type ' + ci.name),
25+
{ code: 'EUSAGE' }
26+
)
27+
}
28+
return sigstore.attest(provenance, INTOTO_PAYLOAD_TYPE, opts)
29+
}
30+
31+
const githubProvenance = (subject) => {
1332
/* istanbul ignore next - not covering missing env var case */
1433
const [workflowPath] = (env.GITHUB_WORKFLOW_REF || '')
1534
.replace(env.GITHUB_REPOSITORY + '/', '')
@@ -19,8 +38,8 @@ const generateProvenance = async (subject, opts) => {
1938
subject,
2039
predicateType: SLSA_PREDICATE_TYPE,
2140
predicate: {
22-
buildType: `${BUILD_TYPE_PREFIX}/${BUILD_TYPE_VERSION}`,
23-
builder: { id: BUILDER_ID },
41+
buildType: `${GITHUB_BUILD_TYPE_PREFIX}/${GITHUB_BUILD_TYPE_VERSION}`,
42+
builder: { id: GITHUB_BUILDER_ID },
2443
invocation: {
2544
configSource: {
2645
uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`,
@@ -63,7 +82,142 @@ const generateProvenance = async (subject, opts) => {
6382
},
6483
}
6584

66-
return sigstore.attest(Buffer.from(JSON.stringify(payload)), INTOTO_PAYLOAD_TYPE, opts)
85+
return Buffer.from(JSON.stringify(payload))
86+
}
87+
88+
const GITLAB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gitlab'
89+
const GITLAB_BUILD_TYPE_VERSION = 'v1alpha1'
90+
91+
const gitlabProvenance = (subject, opts) => {
92+
// TODO: pull values from authenticated JWT rather than environment variables.
93+
const payload = {
94+
_type: INTOTO_STATEMENT_TYPE,
95+
subject,
96+
predicateType: SLSA_PREDICATE_TYPE,
97+
predicate: {
98+
buildType: `${GITLAB_BUILD_TYPE_PREFIX}/${GITLAB_BUILD_TYPE_VERSION}`,
99+
builder: { id: `${env.CI_PROJECT_URL}/-/runners/${env.CI_RUNNER_ID}` },
100+
invocation: {
101+
configSource: {
102+
uri: `git+${env.CI_PROJECT_URL}@${env.CI_BUILD_REF}`,
103+
digest: {
104+
sha1: env.CI_COMMIT_SHA,
105+
},
106+
entryPoint: env.CI_JOB_NAME,
107+
},
108+
parameters: {
109+
CI: env.CI,
110+
CI_API_GRAPHQL_URL: env.CI_API_GRAPHQL_URL,
111+
CI_API_V4_URL: env.CI_API_V4_URL,
112+
CI_BUILD_BEFORE_SHA: env.CI_BUILD_BEFORE_SHA,
113+
CI_BUILD_ID: env.CI_BUILD_ID,
114+
CI_BUILD_NAME: env.CI_BUILD_NAME,
115+
CI_BUILD_REF: env.CI_BUILD_REF,
116+
CI_BUILD_REF_NAME: env.CI_BUILD_REF_NAME,
117+
CI_BUILD_REF_SLUG: env.CI_BUILD_REF_SLUG,
118+
CI_BUILD_STAGE: env.CI_BUILD_STAGE,
119+
CI_COMMIT_AUTHOR: env.CI_COMMIT_AUTHOR,
120+
CI_COMMIT_BEFORE_SHA: env.CI_COMMIT_BEFORE_SHA,
121+
CI_COMMIT_BRANCH: env.CI_COMMIT_BRANCH,
122+
CI_COMMIT_DESCRIPTION: env.CI_COMMIT_DESCRIPTION,
123+
CI_COMMIT_MESSAGE: env.CI_COMMIT_MESSAGE,
124+
CI_COMMIT_REF_NAME: env.CI_COMMIT_REF_NAME,
125+
CI_COMMIT_REF_PROTECTED: env.CI_COMMIT_REF_PROTECTED,
126+
CI_COMMIT_REF_SLUG: env.CI_COMMIT_REF_SLUG,
127+
CI_COMMIT_SHA: env.CI_COMMIT_SHA,
128+
CI_COMMIT_SHORT_SHA: env.CI_COMMIT_SHORT_SHA,
129+
CI_COMMIT_TIMESTAMP: env.CI_COMMIT_TIMESTAMP,
130+
CI_COMMIT_TITLE: env.CI_COMMIT_TITLE,
131+
CI_CONFIG_PATH: env.CI_CONFIG_PATH,
132+
CI_DEFAULT_BRANCH: env.CI_DEFAULT_BRANCH,
133+
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:
134+
env.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,
135+
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: env.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,
136+
CI_DEPENDENCY_PROXY_SERVER: env.CI_DEPENDENCY_PROXY_SERVER,
137+
CI_DEPENDENCY_PROXY_USER: env.CI_DEPENDENCY_PROXY_USER,
138+
CI_JOB_ID: env.CI_JOB_ID,
139+
CI_JOB_NAME: env.CI_JOB_NAME,
140+
CI_JOB_NAME_SLUG: env.CI_JOB_NAME_SLUG,
141+
CI_JOB_STAGE: env.CI_JOB_STAGE,
142+
CI_JOB_STARTED_AT: env.CI_JOB_STARTED_AT,
143+
CI_JOB_URL: env.CI_JOB_URL,
144+
CI_NODE_TOTAL: env.CI_NODE_TOTAL,
145+
CI_PAGES_DOMAIN: env.CI_PAGES_DOMAIN,
146+
CI_PAGES_URL: env.CI_PAGES_URL,
147+
CI_PIPELINE_CREATED_AT: env.CI_PIPELINE_CREATED_AT,
148+
CI_PIPELINE_ID: env.CI_PIPELINE_ID,
149+
CI_PIPELINE_IID: env.CI_PIPELINE_IID,
150+
CI_PIPELINE_SOURCE: env.CI_PIPELINE_SOURCE,
151+
CI_PIPELINE_URL: env.CI_PIPELINE_URL,
152+
CI_PROJECT_CLASSIFICATION_LABEL: env.CI_PROJECT_CLASSIFICATION_LABEL,
153+
CI_PROJECT_DESCRIPTION: env.CI_PROJECT_DESCRIPTION,
154+
CI_PROJECT_ID: env.CI_PROJECT_ID,
155+
CI_PROJECT_NAME: env.CI_PROJECT_NAME,
156+
CI_PROJECT_NAMESPACE: env.CI_PROJECT_NAMESPACE,
157+
CI_PROJECT_NAMESPACE_ID: env.CI_PROJECT_NAMESPACE_ID,
158+
CI_PROJECT_PATH: env.CI_PROJECT_PATH,
159+
CI_PROJECT_PATH_SLUG: env.CI_PROJECT_PATH_SLUG,
160+
CI_PROJECT_REPOSITORY_LANGUAGES: env.CI_PROJECT_REPOSITORY_LANGUAGES,
161+
CI_PROJECT_ROOT_NAMESPACE: env.CI_PROJECT_ROOT_NAMESPACE,
162+
CI_PROJECT_TITLE: env.CI_PROJECT_TITLE,
163+
CI_PROJECT_URL: env.CI_PROJECT_URL,
164+
CI_PROJECT_VISIBILITY: env.CI_PROJECT_VISIBILITY,
165+
CI_REGISTRY: env.CI_REGISTRY,
166+
CI_REGISTRY_IMAGE: env.CI_REGISTRY_IMAGE,
167+
CI_REGISTRY_USER: env.CI_REGISTRY_USER,
168+
CI_REPOSITORY_URL: env.CI_REPOSITORY_URL,
169+
CI_RUNNER_DESCRIPTION: env.CI_RUNNER_DESCRIPTION,
170+
CI_RUNNER_ID: env.CI_RUNNER_ID,
171+
CI_RUNNER_TAGS: env.CI_RUNNER_TAGS,
172+
CI_SERVER_HOST: env.CI_SERVER_HOST,
173+
CI_SERVER_NAME: env.CI_SERVER_NAME,
174+
CI_SERVER_PORT: env.CI_SERVER_PORT,
175+
CI_SERVER_PROTOCOL: env.CI_SERVER_PROTOCOL,
176+
CI_SERVER_REVISION: env.CI_SERVER_REVISION,
177+
CI_SERVER_SHELL_SSH_HOST: env.CI_SERVER_SHELL_SSH_HOST,
178+
CI_SERVER_SHELL_SSH_PORT: env.CI_SERVER_SHELL_SSH_PORT,
179+
CI_SERVER_URL: env.CI_SERVER_URL,
180+
CI_SERVER_VERSION: env.CI_SERVER_VERSION,
181+
CI_SERVER_VERSION_MAJOR: env.CI_SERVER_VERSION_MAJOR,
182+
CI_SERVER_VERSION_MINOR: env.CI_SERVER_VERSION_MINOR,
183+
CI_SERVER_VERSION_PATCH: env.CI_SERVER_VERSION_PATCH,
184+
CI_TEMPLATE_REGISTRY_HOST: env.CI_TEMPLATE_REGISTRY_HOST,
185+
GITLAB_CI: env.GITLAB_CI,
186+
GITLAB_FEATURES: env.GITLAB_FEATURES,
187+
GITLAB_USER_EMAIL: env.GITLAB_USER_EMAIL,
188+
GITLAB_USER_ID: env.GITLAB_USER_ID,
189+
GITLAB_USER_LOGIN: env.GITLAB_USER_LOGIN,
190+
GITLAB_USER_NAME: env.GITLAB_USER_NAME,
191+
RUNNER_GENERATE_ARTIFACTS_METADATA: env.RUNNER_GENERATE_ARTIFACTS_METADATA,
192+
},
193+
environment: {
194+
name: env.CI_RUNNER_DESCRIPTION,
195+
architecture: env.CI_RUNNER_EXECUTABLE_ARCH,
196+
job: {
197+
id: env.CI_JOB_ID,
198+
},
199+
},
200+
},
201+
metadata: {
202+
buildInvocationId: `${env.CI_JOB_URL}`,
203+
completeness: {
204+
parameters: true,
205+
environment: true,
206+
materials: false,
207+
},
208+
reproducible: false,
209+
},
210+
materials: [
211+
{
212+
uri: `git+${env.CI_PROJECT_URL}@${env.CI_BUILD_REF}`,
213+
digest: {
214+
sha1: env.CI_COMMIT_SHA,
215+
},
216+
},
217+
],
218+
},
219+
}
220+
return Buffer.from(JSON.stringify(payload))
67221
}
68222

69223
module.exports = {

workspaces/libnpmpublish/lib/publish.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,24 @@ const buildMetadata = async (registry, manifest, tarballData, spec, opts) => {
144144
digest: { sha512: integrity.sha512[0].hexDigest() },
145145
}
146146

147-
// Ensure that we're running in GHA, currently the only supported build environment
148-
if (ciInfo.name !== 'GitHub Actions') {
149-
throw Object.assign(
150-
new Error('Automatic provenance generation not supported outside of GitHub Actions'),
151-
{ code: 'EUSAGE' }
152-
)
153-
}
154-
155-
// Ensure that the GHA OIDC token is available
156-
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
157-
throw Object.assign(
158-
/* eslint-disable-next-line max-len */
159-
new Error('Provenance generation in GitHub Actions requires "write" access to the "id-token" permission'),
160-
{ code: 'EUSAGE' }
161-
)
147+
switch (true) {
148+
case ciInfo.GITHUB_ACTIONS:
149+
// Ensure that the GHA OIDC token is available
150+
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
151+
throw Object.assign(
152+
/* eslint-disable-next-line max-len */
153+
new Error('Provenance generation in GitHub Actions requires "write" access to the "id-token" permission'),
154+
{ code: 'EUSAGE' }
155+
)
156+
}
157+
break
158+
case ciInfo.GITLAB:
159+
break
160+
default:
161+
throw Object.assign(
162+
new Error('Automatic provenance generation not supported for provider: ' + ciInfo.name),
163+
{ code: 'EUSAGE' }
164+
)
162165
}
163166

164167
const visibility =

workspaces/libnpmpublish/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"npm-registry-fetch": "^14.0.3",
4545
"proc-log": "^3.0.0",
4646
"semver": "^7.3.7",
47-
"sigstore": "^1.0.0",
47+
"sigstore": "^1.3.0",
4848
"ssri": "^10.0.1"
4949
},
5050
"engines": {

0 commit comments

Comments
 (0)