Skip to content

Commit 36e729c

Browse files
committed
grab attestation media type and predicate type from attestation bundle
1 parent 432126c commit 36e729c

8 files changed

Lines changed: 118 additions & 34 deletions

File tree

__tests__/ghcr-client.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,8 @@ function testIndexManifest(): {
606606
const manifest = ociContainer.createReferrerTagManifest(
607607
'attestation-digest',
608608
1234,
609+
'bundle-media-type',
610+
'bundle-predicate-type',
609611
new Date(),
610612
new Date()
611613
)

__tests__/main.test.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import * as ghcr from '../src/ghcr-client'
1515
import * as ociContainer from '../src/oci-container'
1616

1717
const ghcrUrl = new URL('https://ghcr.io')
18+
const predicateType = 'https://slsa.dev/provenance/v1'
19+
const bundleMediaType = 'application/vnd.dev.sigstore.bundle.v0.3+json'
1820

1921
// Mock the GitHub Actions core library
2022
let setFailedMock: jest.SpyInstance
@@ -302,11 +304,14 @@ describe('run', () => {
302304
attestationID: 'test-attestation-id',
303305
certificate: 'test',
304306
bundle: {
305-
mediaType: 'application/vnd.cncf.notary.v2+jwt',
307+
mediaType: bundleMediaType,
306308
verificationMaterial: {
307309
publicKey: {
308310
hint: 'test-hint'
309311
}
312+
},
313+
dsseEnvelope: {
314+
payload: btoa(`{"predicateType": "${predicateType}"}`)
310315
}
311316
}
312317
}
@@ -360,11 +365,14 @@ describe('run', () => {
360365
attestationID: 'test-attestation-id',
361366
certificate: 'test',
362367
bundle: {
363-
mediaType: 'application/vnd.cncf.notary.v2+jwt',
368+
mediaType: bundleMediaType,
364369
verificationMaterial: {
365370
publicKey: {
366371
hint: 'test-hint'
367372
}
373+
},
374+
dsseEnvelope: {
375+
payload: btoa(`{"predicateType": "${predicateType}"}`)
368376
}
369377
}
370378
}
@@ -426,11 +434,14 @@ describe('run', () => {
426434
attestationID: 'test-attestation-id',
427435
certificate: 'test',
428436
bundle: {
429-
mediaType: 'application/vnd.cncf.notary.v2+jwt',
437+
mediaType: bundleMediaType,
430438
verificationMaterial: {
431439
publicKey: {
432440
hint: 'test-hint'
433441
}
442+
},
443+
dsseEnvelope: {
444+
payload: btoa(`{"predicateType": "${predicateType}"}`)
434445
}
435446
}
436447
}
@@ -568,11 +579,14 @@ describe('run', () => {
568579
attestationID: 'test-attestation-id',
569580
certificate: 'test',
570581
bundle: {
571-
mediaType: 'application/vnd.cncf.notary.v2+jwt',
582+
mediaType: bundleMediaType,
572583
verificationMaterial: {
573584
publicKey: {
574585
hint: 'test-hint'
575586
}
587+
},
588+
dsseEnvelope: {
589+
payload: btoa(`{"predicateType": "${predicateType}"}`)
576590
}
577591
}
578592
}
@@ -583,6 +597,21 @@ describe('run', () => {
583597
expect(repository).toBe(options.nameWithOwner)
584598
expect(tag).toBe('sha256-my-test-digest')
585599
expect(manifest.mediaType).toBe(ociContainer.imageIndexMediaType)
600+
expect(manifest.annotations['com.github.package.type']).toBe(
601+
ociContainer.actionPackageReferrerTagAnnotationValue
602+
)
603+
expect(manifest.manifests.length).toBe(1)
604+
expect(manifest.manifests[0].mediaType).toBe(
605+
ociContainer.imageManifestMediaType
606+
)
607+
expect(manifest.manifests[0].artifactType).toBe(bundleMediaType)
608+
expect(
609+
manifest.manifests[0].annotations['dev.sigstore.bundle.predicateType']
610+
).toBe(predicateType)
611+
expect(
612+
manifest.manifests[0].annotations['com.github.package.type']
613+
).toBe(ociContainer.actionPackageAttestationAnnotationValue)
614+
586615
return 'sha256:referrer-index-digest'
587616
}
588617
)
@@ -593,16 +622,23 @@ describe('run', () => {
593622
let expectedAnnotationValue = ''
594623
let expectedTagValue: string | undefined = undefined
595624
let returnValue = ''
625+
let expectedPredicateTypeValue: string | undefined = undefined
626+
627+
let expectedSubjectMediaType: string | undefined = undefined
596628

597629
if (tag === undefined) {
598630
expectedAnnotationValue =
599631
ociContainer.actionPackageAttestationAnnotationValue
600632
const sigStoreLayer = manifest.layers.find(
601633
(layer: ociContainer.Descriptor) =>
602-
layer.mediaType === ociContainer.sigstoreBundleMediaType
634+
layer.mediaType === bundleMediaType
603635
)
636+
expectedPredicateTypeValue = predicateType
604637

605638
expectedBlobKeys = [sigStoreLayer.digest, ociContainer.emptyConfigSha]
639+
640+
expectedSubjectMediaType = ociContainer.imageManifestMediaType
641+
606642
returnValue = 'sha256:attestation-digest'
607643
} else {
608644
expectedAnnotationValue = ociContainer.actionPackageAnnotationValue
@@ -616,7 +652,12 @@ describe('run', () => {
616652
expect(manifest.annotations['com.github.package.type']).toBe(
617653
expectedAnnotationValue
618654
)
655+
expect(manifest.annotations['dev.sigstore.bundle.predicateType']).toBe(
656+
expectedPredicateTypeValue
657+
)
619658
expect(tag).toBe(expectedTagValue)
659+
expect(manifest.subject?.mediaType).toBe(expectedSubjectMediaType)
660+
620661
expect(manifest.layers.length).toBe(expectedBlobKeys.length - 1) // Minus config layer
621662
expect(blobs.size).toBe(expectedBlobKeys.length)
622663
for (const expectedBlobKey of expectedBlobKeys) {

__tests__/oci-container.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ function testAttestationManifest(setCreated = true): OCIImageManifest {
219219
return createSigstoreAttestationManifest(
220220
10,
221221
'bundleDigest',
222+
'application/vnd.dev.sigstore.bundle.v0.3+json',
223+
'https://slsa.dev/provenance/v1',
222224
100,
223225
'subjectDigest',
224226
setCreated ? date : undefined
@@ -230,6 +232,8 @@ function testReferrerIndexManifest(setCreated = true): OCIIndexManifest {
230232
return createReferrerTagManifest(
231233
'attDigest',
232234
100,
235+
'application/vnd.dev.sigstore.bundle.v0.3+json',
236+
'https://slsa.dev/provenance/v1',
233237
date,
234238
setCreated ? date : undefined
235239
)

badges/coverage.svg

Lines changed: 1 addition & 1 deletion
Loading

dist/index.js

Lines changed: 27 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ghcr-client.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ export class Client {
238238
).toString()
239239
}
240240

241-
// TODO: Add retries with backoff
242241
private async fetchWithDebug(
243242
url: string,
244243
config: RequestInit = {}

src/main.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,26 @@ export async function run(): Promise<void> {
6060

6161
// Attestations are not supported in GHES.
6262
if (!options.isEnterprise) {
63-
const { bundle, bundleDigest } = await generateAttestation(
64-
manifestDigest,
65-
semverTag.raw,
66-
options
67-
)
63+
const { bundle, bundleDigest, bundleMediaType, bundlePredicateType } =
64+
await generateAttestation(manifestDigest, semverTag.raw, options)
6865

6966
const attestationCreated = new Date()
7067
const attestationManifest =
7168
ociContainer.createSigstoreAttestationManifest(
7269
bundle.length,
7370
bundleDigest,
71+
bundleMediaType,
72+
bundlePredicateType,
7473
ociContainer.sizeInBytes(manifest),
7574
manifestDigest,
7675
attestationCreated
7776
)
77+
7878
const referrerIndexManifest = ociContainer.createReferrerTagManifest(
7979
ociContainer.sha256Digest(attestationManifest),
8080
ociContainer.sizeInBytes(attestationManifest),
81+
bundleMediaType,
82+
bundlePredicateType,
8183
attestationCreated
8284
)
8385

@@ -221,6 +223,8 @@ async function generateAttestation(
221223
): Promise<{
222224
bundle: Buffer
223225
bundleDigest: string
226+
bundleMediaType: string
227+
bundlePredicateType: string
224228
}> {
225229
const subjectName = `${options.nameWithOwner}@${semverTag}`
226230
const subjectDigest = removePrefix(manifestDigest, 'sha256:')
@@ -241,7 +245,26 @@ async function generateAttestation(
241245
hash.update(bundleArtifact)
242246
const bundleSHA = hash.digest('hex')
243247

244-
return { bundle: bundleArtifact, bundleDigest: `sha256:${bundleSHA}` }
248+
// We must base64 decode the dsse envelope to grab the predicate type
249+
const dsseEnvelopeArtifact = attestation.bundle.dsseEnvelope
250+
if (dsseEnvelopeArtifact === undefined) {
251+
throw new Error('Attestation bundle is missing dsseEnvelope artifact')
252+
}
253+
254+
const dsseEnvelope = JSON.parse(
255+
Buffer.from(dsseEnvelopeArtifact.payload, 'base64').toString('utf-8')
256+
)
257+
const predicateType = dsseEnvelope.predicateType
258+
if (predicateType === undefined) {
259+
throw new Error('Attestation bundle is missing predicateType')
260+
}
261+
262+
return {
263+
bundle: bundleArtifact,
264+
bundleDigest: `sha256:${bundleSHA}`,
265+
bundleMediaType: attestation.bundle.mediaType,
266+
bundlePredicateType: predicateType
267+
}
245268
}
246269

247270
function removePrefix(str: string, prefix: string): string {

0 commit comments

Comments
 (0)