Skip to content

Commit 8a96626

Browse files
authored
Merge pull request #179 from actions/conorsloan/upload-attestations-to-ghcr
Upload attestations to GHCR instead of Attestations API
2 parents f213f0c + 36e729c commit 8a96626

11 files changed

Lines changed: 1699 additions & 774 deletions

File tree

__tests__/ghcr-client.test.ts

Lines changed: 367 additions & 259 deletions
Large diffs are not rendered by default.

__tests__/main.test.ts

Lines changed: 244 additions & 60 deletions
Large diffs are not rendered by default.

__tests__/oci-container.test.ts

Lines changed: 185 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,38 @@
1-
import { createActionPackageManifest, sha256Digest } from '../src/oci-container'
1+
import {
2+
createActionPackageManifest,
3+
sha256Digest,
4+
sizeInBytes,
5+
OCIImageManifest,
6+
createSigstoreAttestationManifest,
7+
OCIIndexManifest,
8+
createReferrerTagManifest
9+
} from '../src/oci-container'
210
import { FileMetadata } from '../src/fs-helper'
311

12+
const createdTimestamp = '2021-01-01T00:00:00.000Z'
13+
414
describe('sha256Digest', () => {
515
it('calculates the SHA256 digest of the provided manifest', () => {
6-
const date = new Date('2021-01-01T00:00:00Z')
7-
const repo = 'test-org/test-repo'
8-
const version = '1.2.3'
9-
const repoId = '123'
10-
const ownerId = '456'
11-
const sourceCommit = 'abc'
12-
const tarFile: FileMetadata = {
13-
path: '/test/test/test.tar.gz',
14-
sha256: 'tarSha',
15-
size: 123
16-
}
17-
const zipFile: FileMetadata = {
18-
path: '/test/test/test.zip',
19-
sha256: 'zipSha',
20-
size: 456
21-
}
22-
23-
const manifest = createActionPackageManifest(
24-
tarFile,
25-
zipFile,
26-
repo,
27-
repoId,
28-
ownerId,
29-
sourceCommit,
30-
version,
31-
date
32-
)
33-
16+
const { manifest } = testActionPackageManifest()
3417
const digest = sha256Digest(manifest)
3518
const expectedDigest =
36-
'sha256:dd8537ef913cf87e25064a074973ed2c62699f1dbd74d0dd78e85d394a5758b5'
19+
'sha256:1af9bf993bf068a51fbb54822471ab7507b07c553bcac09a7c91328740d8ed69'
3720

3821
expect(digest).toEqual(expectedDigest)
3922
})
4023
})
4124

25+
describe('size', () => {
26+
it('returns the total size of the provided manifest', () => {
27+
const { manifest } = testActionPackageManifest()
28+
const size = sizeInBytes(manifest)
29+
expect(size).toBe(991)
30+
})
31+
})
32+
4233
describe('createActionPackageManifest', () => {
4334
it('creates a manifest containing the provided information', () => {
44-
const date = new Date()
45-
const repo = 'test-org/test-repo'
46-
const sanitizedRepo = 'test-org-test-repo'
47-
const version = '1.2.3'
48-
const repoId = '123'
49-
const ownerId = '456'
50-
const sourceCommit = 'abc'
51-
const tarFile: FileMetadata = {
52-
path: '/test/test/test.tar.gz',
53-
sha256: 'tarSha',
54-
size: 123
55-
}
56-
const zipFile: FileMetadata = {
57-
path: '/test/test/test.zip',
58-
sha256: 'zipSha',
59-
size: 456
60-
}
35+
const { manifest, zipFile, tarFile } = testActionPackageManifest()
6136

6237
const expectedJSON = `{
6338
"schemaVersion": 2,
@@ -69,30 +44,25 @@ describe('createActionPackageManifest', () => {
6944
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
7045
},
7146
"layers":[
72-
{
73-
"mediaType":"application/vnd.oci.empty.v1+json",
74-
"size":2,
75-
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
76-
},
7747
{
7848
"mediaType":"application/vnd.github.actions.package.layer.v1.tar+gzip",
7949
"size":${tarFile.size},
8050
"digest":"${tarFile.sha256}",
8151
"annotations":{
82-
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.tar.gz"
52+
"org.opencontainers.image.title":"test-org-test-repo_1.2.3.tar.gz"
8353
}
8454
},
8555
{
8656
"mediaType":"application/vnd.github.actions.package.layer.v1.zip",
8757
"size":${zipFile.size},
8858
"digest":"${zipFile.sha256}",
8959
"annotations":{
90-
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
60+
"org.opencontainers.image.title":"test-org-test-repo_1.2.3.zip"
9161
}
9262
}
9363
],
9464
"annotations":{
95-
"org.opencontainers.image.created":"${date.toISOString()}",
65+
"org.opencontainers.image.created":"${createdTimestamp}",
9666
"action.tar.gz.digest":"${tarFile.sha256}",
9767
"action.zip.digest":"${zipFile.sha256}",
9868
"com.github.package.type":"actions_oci_pkg",
@@ -103,26 +73,168 @@ describe('createActionPackageManifest', () => {
10373
}
10474
}`
10575

106-
const manifest = createActionPackageManifest(
107-
{
108-
path: 'test.tar.gz',
109-
size: tarFile.size,
110-
sha256: tarFile.sha256
111-
},
112-
{
113-
path: 'test.zip',
114-
size: zipFile.size,
115-
sha256: zipFile.sha256
116-
},
117-
repo,
118-
repoId,
119-
ownerId,
120-
sourceCommit,
121-
version,
122-
date
123-
)
76+
const manifestJSON = JSON.stringify(manifest)
77+
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
78+
})
79+
80+
it('uses the current time if no created date is provided', () => {
81+
const { manifest } = testActionPackageManifest(false)
82+
expect(
83+
manifest.annotations['org.opencontainers.image.created']
84+
).toBeDefined()
85+
})
86+
})
87+
88+
describe('createSigstoreAttestationManifest', () => {
89+
it('creates a manifest containing the provided information', () => {
90+
const manifest = testAttestationManifest()
91+
92+
const expectedJSON = `{
93+
"schemaVersion": 2,
94+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
95+
"artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json",
96+
"config": {
97+
"mediaType": "application/vnd.oci.empty.v1+json",
98+
"size": 2,
99+
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
100+
},
101+
"layers": [
102+
{
103+
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
104+
"size": 10,
105+
"digest": "bundleDigest"
106+
}
107+
],
108+
"subject": {
109+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
110+
"size": 100,
111+
"digest": "subjectDigest"
112+
},
113+
"annotations": {
114+
"dev.sigstore.bundle.content": "dsse-envelope",
115+
"dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1",
116+
"com.github.package.type": "actions_oci_pkg_attestation",
117+
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z"
118+
}
119+
}
120+
`
124121

125122
const manifestJSON = JSON.stringify(manifest)
123+
126124
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
127125
})
126+
127+
it('uses the current time if no created date is provided', () => {
128+
const manifest = testAttestationManifest(false)
129+
expect(
130+
manifest.annotations['org.opencontainers.image.created']
131+
).toBeDefined()
132+
})
128133
})
134+
135+
describe('createReferrerIndexManifest', () => {
136+
it('creates a manifest containing the provided information', () => {
137+
const manifest = testReferrerIndexManifest()
138+
139+
const expectedJSON = `
140+
{
141+
"schemaVersion": 2,
142+
"mediaType": "application/vnd.oci.image.index.v1+json",
143+
"manifests": [
144+
{
145+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
146+
"artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json",
147+
"size": 100,
148+
"digest": "attDigest",
149+
"annotations": {
150+
"com.github.package.type": "actions_oci_pkg_attestation",
151+
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z",
152+
"dev.sigstore.bundle.content": "dsse-envelope",
153+
"dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1"
154+
}
155+
}
156+
],
157+
"annotations": {
158+
"com.github.package.type": "actions_oci_pkg_referrer_index",
159+
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z"
160+
}
161+
}
162+
`
163+
164+
const manifestJSON = JSON.stringify(manifest)
165+
166+
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
167+
})
168+
169+
it('uses the current time if no created date is provided', () => {
170+
const manifest = testReferrerIndexManifest(false)
171+
expect(
172+
manifest.annotations['org.opencontainers.image.created']
173+
).toBeDefined()
174+
})
175+
})
176+
177+
function testActionPackageManifest(setCreated = true): {
178+
manifest: OCIImageManifest
179+
tarFile: FileMetadata
180+
zipFile: FileMetadata
181+
} {
182+
const date = new Date('2021-01-01T00:00:00Z')
183+
const repo = 'test-org/test-repo'
184+
const version = '1.2.3'
185+
const repoId = '123'
186+
const ownerId = '456'
187+
const sourceCommit = 'abc'
188+
const tarFile: FileMetadata = {
189+
path: '/test/test/test.tar.gz',
190+
sha256: 'tarSha',
191+
size: 123
192+
}
193+
const zipFile: FileMetadata = {
194+
path: '/test/test/test.zip',
195+
sha256: 'zipSha',
196+
size: 456
197+
}
198+
199+
const manifest = createActionPackageManifest(
200+
tarFile,
201+
zipFile,
202+
repo,
203+
repoId,
204+
ownerId,
205+
sourceCommit,
206+
version,
207+
setCreated ? date : undefined
208+
)
209+
210+
return {
211+
manifest,
212+
tarFile,
213+
zipFile
214+
}
215+
}
216+
217+
function testAttestationManifest(setCreated = true): OCIImageManifest {
218+
const date = new Date(createdTimestamp)
219+
return createSigstoreAttestationManifest(
220+
10,
221+
'bundleDigest',
222+
'application/vnd.dev.sigstore.bundle.v0.3+json',
223+
'https://slsa.dev/provenance/v1',
224+
100,
225+
'subjectDigest',
226+
setCreated ? date : undefined
227+
)
228+
}
229+
230+
function testReferrerIndexManifest(setCreated = true): OCIIndexManifest {
231+
const date = new Date(createdTimestamp)
232+
return createReferrerTagManifest(
233+
'attDigest',
234+
100,
235+
'application/vnd.dev.sigstore.bundle.v0.3+json',
236+
'https://slsa.dev/provenance/v1',
237+
date,
238+
setCreated ? date : undefined
239+
)
240+
}

action.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ inputs:
1111
description: 'The GitHub actions token used to authenticate with GitHub APIs'
1212

1313
outputs:
14-
package-url:
15-
description: 'The name of package published to GHCR along with semver. For example, https://ghcr.io/actions/package-action:1.0.1'
16-
package-manifest:
17-
description: 'The package manifest of the published package in JSON format'
1814
package-manifest-sha:
1915
description: 'A sha256 hash of the package manifest'
20-
attestation-id:
21-
description: 'The attestation id of the generated provenance attestation. This is not present if the package is not attested, e.g. in enterprise environments.'
16+
attestation-manifest-sha:
17+
description: 'The sha256 of the provenance attestation uploaded to GHCR. This is not present if the package is not attested, e.g. in enterprise environments.'
18+
referrer-index-manifest-sha:
19+
description: 'The sha256 of the referrer index uploaded to GHCR. This is not present if the package is not attested, e.g. in enterprise environments.'
2220

2321
runs:
2422
using: node20

badges/coverage.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)