diff --git a/__tests__/ghcr-client.test.ts b/__tests__/ghcr-client.test.ts
index fcdb6b8..7696008 100644
--- a/__tests__/ghcr-client.test.ts
+++ b/__tests__/ghcr-client.test.ts
@@ -606,6 +606,8 @@ function testIndexManifest(): {
const manifest = ociContainer.createReferrerTagManifest(
'attestation-digest',
1234,
+ 'bundle-media-type',
+ 'bundle-predicate-type',
new Date(),
new Date()
)
diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index 1e1a593..fde06bc 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -15,6 +15,8 @@ import * as ghcr from '../src/ghcr-client'
import * as ociContainer from '../src/oci-container'
const ghcrUrl = new URL('https://ghcr.io')
+const predicateType = 'https://slsa.dev/provenance/v1'
+const bundleMediaType = 'application/vnd.dev.sigstore.bundle.v0.3+json'
// Mock the GitHub Actions core library
let setFailedMock: jest.SpyInstance
@@ -302,11 +304,14 @@ describe('run', () => {
attestationID: 'test-attestation-id',
certificate: 'test',
bundle: {
- mediaType: 'application/vnd.cncf.notary.v2+jwt',
+ mediaType: bundleMediaType,
verificationMaterial: {
publicKey: {
hint: 'test-hint'
}
+ },
+ dsseEnvelope: {
+ payload: btoa(`{"predicateType": "${predicateType}"}`)
}
}
}
@@ -360,11 +365,14 @@ describe('run', () => {
attestationID: 'test-attestation-id',
certificate: 'test',
bundle: {
- mediaType: 'application/vnd.cncf.notary.v2+jwt',
+ mediaType: bundleMediaType,
verificationMaterial: {
publicKey: {
hint: 'test-hint'
}
+ },
+ dsseEnvelope: {
+ payload: btoa(`{"predicateType": "${predicateType}"}`)
}
}
}
@@ -426,11 +434,14 @@ describe('run', () => {
attestationID: 'test-attestation-id',
certificate: 'test',
bundle: {
- mediaType: 'application/vnd.cncf.notary.v2+jwt',
+ mediaType: bundleMediaType,
verificationMaterial: {
publicKey: {
hint: 'test-hint'
}
+ },
+ dsseEnvelope: {
+ payload: btoa(`{"predicateType": "${predicateType}"}`)
}
}
}
@@ -568,11 +579,14 @@ describe('run', () => {
attestationID: 'test-attestation-id',
certificate: 'test',
bundle: {
- mediaType: 'application/vnd.cncf.notary.v2+jwt',
+ mediaType: bundleMediaType,
verificationMaterial: {
publicKey: {
hint: 'test-hint'
}
+ },
+ dsseEnvelope: {
+ payload: btoa(`{"predicateType": "${predicateType}"}`)
}
}
}
@@ -583,6 +597,21 @@ describe('run', () => {
expect(repository).toBe(options.nameWithOwner)
expect(tag).toBe('sha256-my-test-digest')
expect(manifest.mediaType).toBe(ociContainer.imageIndexMediaType)
+ expect(manifest.annotations['com.github.package.type']).toBe(
+ ociContainer.actionPackageReferrerTagAnnotationValue
+ )
+ expect(manifest.manifests.length).toBe(1)
+ expect(manifest.manifests[0].mediaType).toBe(
+ ociContainer.imageManifestMediaType
+ )
+ expect(manifest.manifests[0].artifactType).toBe(bundleMediaType)
+ expect(
+ manifest.manifests[0].annotations['dev.sigstore.bundle.predicateType']
+ ).toBe(predicateType)
+ expect(
+ manifest.manifests[0].annotations['com.github.package.type']
+ ).toBe(ociContainer.actionPackageAttestationAnnotationValue)
+
return 'sha256:referrer-index-digest'
}
)
@@ -593,16 +622,23 @@ describe('run', () => {
let expectedAnnotationValue = ''
let expectedTagValue: string | undefined = undefined
let returnValue = ''
+ let expectedPredicateTypeValue: string | undefined = undefined
+
+ let expectedSubjectMediaType: string | undefined = undefined
if (tag === undefined) {
expectedAnnotationValue =
ociContainer.actionPackageAttestationAnnotationValue
const sigStoreLayer = manifest.layers.find(
(layer: ociContainer.Descriptor) =>
- layer.mediaType === ociContainer.sigstoreBundleMediaType
+ layer.mediaType === bundleMediaType
)
+ expectedPredicateTypeValue = predicateType
expectedBlobKeys = [sigStoreLayer.digest, ociContainer.emptyConfigSha]
+
+ expectedSubjectMediaType = ociContainer.imageManifestMediaType
+
returnValue = 'sha256:attestation-digest'
} else {
expectedAnnotationValue = ociContainer.actionPackageAnnotationValue
@@ -616,7 +652,12 @@ describe('run', () => {
expect(manifest.annotations['com.github.package.type']).toBe(
expectedAnnotationValue
)
+ expect(manifest.annotations['dev.sigstore.bundle.predicateType']).toBe(
+ expectedPredicateTypeValue
+ )
expect(tag).toBe(expectedTagValue)
+ expect(manifest.subject?.mediaType).toBe(expectedSubjectMediaType)
+
expect(manifest.layers.length).toBe(expectedBlobKeys.length - 1) // Minus config layer
expect(blobs.size).toBe(expectedBlobKeys.length)
for (const expectedBlobKey of expectedBlobKeys) {
diff --git a/__tests__/oci-container.test.ts b/__tests__/oci-container.test.ts
index b54eb26..80855b9 100644
--- a/__tests__/oci-container.test.ts
+++ b/__tests__/oci-container.test.ts
@@ -219,6 +219,8 @@ function testAttestationManifest(setCreated = true): OCIImageManifest {
return createSigstoreAttestationManifest(
10,
'bundleDigest',
+ 'application/vnd.dev.sigstore.bundle.v0.3+json',
+ 'https://slsa.dev/provenance/v1',
100,
'subjectDigest',
setCreated ? date : undefined
@@ -230,6 +232,8 @@ function testReferrerIndexManifest(setCreated = true): OCIIndexManifest {
return createReferrerTagManifest(
'attDigest',
100,
+ 'application/vnd.dev.sigstore.bundle.v0.3+json',
+ 'https://slsa.dev/provenance/v1',
date,
setCreated ? date : undefined
)
diff --git a/badges/coverage.svg b/badges/coverage.svg
index 2f3c0cd..d252813 100644
--- a/badges/coverage.svg
+++ b/badges/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index cc7713d..c3228d8 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -106812,7 +106812,6 @@ class Client {
manifestEndpoint(repository, version) {
return new URL(`v2/${repository}/manifests/${version}`, this._registry).toString();
}
- // TODO: Add retries with backoff
async fetchWithDebug(url, config = {}) {
core.debug(`Request from ${url} with config: ${JSON.stringify(config)}`);
try {
@@ -106962,10 +106961,10 @@ async function run() {
const ghcrClient = new ghcr.Client(options.token, options.containerRegistryUrl);
// Attestations are not supported in GHES.
if (!options.isEnterprise) {
- const { bundle, bundleDigest } = await generateAttestation(manifestDigest, semverTag.raw, options);
+ const { bundle, bundleDigest, bundleMediaType, bundlePredicateType } = await generateAttestation(manifestDigest, semverTag.raw, options);
const attestationCreated = new Date();
- const attestationManifest = ociContainer.createSigstoreAttestationManifest(bundle.length, bundleDigest, ociContainer.sizeInBytes(manifest), manifestDigest, attestationCreated);
- const referrerIndexManifest = ociContainer.createReferrerTagManifest(ociContainer.sha256Digest(attestationManifest), ociContainer.sizeInBytes(attestationManifest), attestationCreated);
+ const attestationManifest = ociContainer.createSigstoreAttestationManifest(bundle.length, bundleDigest, bundleMediaType, bundlePredicateType, ociContainer.sizeInBytes(manifest), manifestDigest, attestationCreated);
+ const referrerIndexManifest = ociContainer.createReferrerTagManifest(ociContainer.sha256Digest(attestationManifest), ociContainer.sizeInBytes(attestationManifest), bundleMediaType, bundlePredicateType, attestationCreated);
const { attestationSHA, referrerIndexSHA } = await publishAttestation(ghcrClient, options.nameWithOwner, bundle, bundleDigest, manifest, attestationManifest, referrerIndexManifest);
if (attestationSHA !== undefined) {
core.info(`Uploaded attestation ${attestationSHA}`);
@@ -107039,7 +107038,22 @@ async function generateAttestation(manifestDigest, semverTag, options) {
const hash = crypto.createHash('sha256');
hash.update(bundleArtifact);
const bundleSHA = hash.digest('hex');
- return { bundle: bundleArtifact, bundleDigest: `sha256:${bundleSHA}` };
+ // We must base64 decode the dsse envelope to grab the predicate type
+ const dsseEnvelopeArtifact = attestation.bundle.dsseEnvelope;
+ if (dsseEnvelopeArtifact === undefined) {
+ throw new Error('Attestation bundle is missing dsseEnvelope artifact');
+ }
+ const dsseEnvelope = JSON.parse(Buffer.from(dsseEnvelopeArtifact.payload, 'base64').toString('utf-8'));
+ const predicateType = dsseEnvelope.predicateType;
+ if (predicateType === undefined) {
+ throw new Error('Attestation bundle is missing predicateType');
+ }
+ return {
+ bundle: bundleArtifact,
+ bundleDigest: `sha256:${bundleSHA}`,
+ bundleMediaType: attestation.bundle.mediaType,
+ bundlePredicateType: predicateType
+ };
}
function removePrefix(str, prefix) {
if (str.startsWith(prefix)) {
@@ -107080,7 +107094,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.emptyConfigSha = exports.emptyConfigSize = exports.ociEmptyMediaType = exports.actionPackageReferrerTagAnnotationValue = exports.actionPackageAttestationAnnotationValue = exports.actionPackageAnnotationValue = exports.sigstoreBundleMediaType = exports.actionsPackageZipLayerMediaType = exports.actionsPackageTarLayerMediaType = exports.actionsPackageMediaType = exports.imageManifestMediaType = exports.imageIndexMediaType = void 0;
+exports.emptyConfigSha = exports.emptyConfigSize = exports.ociEmptyMediaType = exports.actionPackageReferrerTagAnnotationValue = exports.actionPackageAttestationAnnotationValue = exports.actionPackageAnnotationValue = exports.actionsPackageZipLayerMediaType = exports.actionsPackageTarLayerMediaType = exports.actionsPackageMediaType = exports.imageManifestMediaType = exports.imageIndexMediaType = void 0;
exports.createActionPackageManifest = createActionPackageManifest;
exports.createSigstoreAttestationManifest = createSigstoreAttestationManifest;
exports.createReferrerTagManifest = createReferrerTagManifest;
@@ -107093,7 +107107,6 @@ exports.imageManifestMediaType = 'application/vnd.oci.image.manifest.v1+json';
exports.actionsPackageMediaType = 'application/vnd.github.actions.package.v1+json';
exports.actionsPackageTarLayerMediaType = 'application/vnd.github.actions.package.layer.v1.tar+gzip';
exports.actionsPackageZipLayerMediaType = 'application/vnd.github.actions.package.layer.v1.zip';
-exports.sigstoreBundleMediaType = 'application/vnd.dev.sigstore.bundle.v0.3+json';
exports.actionPackageAnnotationValue = 'actions_oci_pkg';
exports.actionPackageAttestationAnnotationValue = 'actions_oci_pkg_attestation';
exports.actionPackageReferrerTagAnnotationValue = 'actions_oci_pkg_referrer_index';
@@ -107125,10 +107138,10 @@ function createActionPackageManifest(tarFile, zipFile, repository, repoId, owner
};
return manifest;
}
-function createSigstoreAttestationManifest(bundleSize, bundleDigest, subjectSize, subjectDigest, created = new Date()) {
+function createSigstoreAttestationManifest(bundleSize, bundleDigest, bundleMediaType, bundlePredicateType, subjectSize, subjectDigest, created = new Date()) {
const configLayer = createEmptyConfigLayer();
const sigstoreAttestationLayer = {
- mediaType: exports.sigstoreBundleMediaType,
+ mediaType: bundleMediaType,
size: bundleSize,
digest: bundleDigest
};
@@ -107140,34 +107153,34 @@ function createSigstoreAttestationManifest(bundleSize, bundleDigest, subjectSize
const manifest = {
schemaVersion: 2,
mediaType: exports.imageManifestMediaType,
- artifactType: exports.sigstoreBundleMediaType,
+ artifactType: bundleMediaType,
config: configLayer,
layers: [sigstoreAttestationLayer],
subject,
annotations: {
'dev.sigstore.bundle.content': 'dsse-envelope',
- 'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1',
+ 'dev.sigstore.bundle.predicateType': bundlePredicateType,
'com.github.package.type': exports.actionPackageAttestationAnnotationValue,
'org.opencontainers.image.created': created.toISOString()
}
};
return manifest;
}
-function createReferrerTagManifest(attestationDigest, attestationSize, attestationCreated, created = new Date()) {
+function createReferrerTagManifest(attestationDigest, attestationSize, bundleMediaType, bundlePredicateType, attestationCreated, created = new Date()) {
const manifest = {
schemaVersion: 2,
mediaType: exports.imageIndexMediaType,
manifests: [
{
mediaType: exports.imageManifestMediaType,
- artifactType: exports.sigstoreBundleMediaType,
+ artifactType: bundleMediaType,
size: attestationSize,
digest: attestationDigest,
annotations: {
'com.github.package.type': exports.actionPackageAttestationAnnotationValue,
'org.opencontainers.image.created': attestationCreated.toISOString(),
'dev.sigstore.bundle.content': 'dsse-envelope',
- 'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1'
+ 'dev.sigstore.bundle.predicateType': bundlePredicateType
}
}
],
diff --git a/src/ghcr-client.ts b/src/ghcr-client.ts
index 94fda15..d1d6b1a 100644
--- a/src/ghcr-client.ts
+++ b/src/ghcr-client.ts
@@ -238,7 +238,6 @@ export class Client {
).toString()
}
- // TODO: Add retries with backoff
private async fetchWithDebug(
url: string,
config: RequestInit = {}
diff --git a/src/main.ts b/src/main.ts
index b93cd54..f31eb87 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -60,24 +60,26 @@ export async function run(): Promise {
// Attestations are not supported in GHES.
if (!options.isEnterprise) {
- const { bundle, bundleDigest } = await generateAttestation(
- manifestDigest,
- semverTag.raw,
- options
- )
+ const { bundle, bundleDigest, bundleMediaType, bundlePredicateType } =
+ await generateAttestation(manifestDigest, semverTag.raw, options)
const attestationCreated = new Date()
const attestationManifest =
ociContainer.createSigstoreAttestationManifest(
bundle.length,
bundleDigest,
+ bundleMediaType,
+ bundlePredicateType,
ociContainer.sizeInBytes(manifest),
manifestDigest,
attestationCreated
)
+
const referrerIndexManifest = ociContainer.createReferrerTagManifest(
ociContainer.sha256Digest(attestationManifest),
ociContainer.sizeInBytes(attestationManifest),
+ bundleMediaType,
+ bundlePredicateType,
attestationCreated
)
@@ -221,6 +223,8 @@ async function generateAttestation(
): Promise<{
bundle: Buffer
bundleDigest: string
+ bundleMediaType: string
+ bundlePredicateType: string
}> {
const subjectName = `${options.nameWithOwner}@${semverTag}`
const subjectDigest = removePrefix(manifestDigest, 'sha256:')
@@ -241,7 +245,26 @@ async function generateAttestation(
hash.update(bundleArtifact)
const bundleSHA = hash.digest('hex')
- return { bundle: bundleArtifact, bundleDigest: `sha256:${bundleSHA}` }
+ // We must base64 decode the dsse envelope to grab the predicate type
+ const dsseEnvelopeArtifact = attestation.bundle.dsseEnvelope
+ if (dsseEnvelopeArtifact === undefined) {
+ throw new Error('Attestation bundle is missing dsseEnvelope artifact')
+ }
+
+ const dsseEnvelope = JSON.parse(
+ Buffer.from(dsseEnvelopeArtifact.payload, 'base64').toString('utf-8')
+ )
+ const predicateType = dsseEnvelope.predicateType
+ if (predicateType === undefined) {
+ throw new Error('Attestation bundle is missing predicateType')
+ }
+
+ return {
+ bundle: bundleArtifact,
+ bundleDigest: `sha256:${bundleSHA}`,
+ bundleMediaType: attestation.bundle.mediaType,
+ bundlePredicateType: predicateType
+ }
}
function removePrefix(str: string, prefix: string): string {
diff --git a/src/oci-container.ts b/src/oci-container.ts
index 1abbbb4..343409d 100644
--- a/src/oci-container.ts
+++ b/src/oci-container.ts
@@ -10,8 +10,6 @@ export const actionsPackageTarLayerMediaType =
'application/vnd.github.actions.package.layer.v1.tar+gzip'
export const actionsPackageZipLayerMediaType =
'application/vnd.github.actions.package.layer.v1.zip'
-export const sigstoreBundleMediaType =
- 'application/vnd.dev.sigstore.bundle.v0.3+json'
export const actionPackageAnnotationValue = 'actions_oci_pkg'
export const actionPackageAttestationAnnotationValue =
@@ -89,6 +87,8 @@ export function createActionPackageManifest(
export function createSigstoreAttestationManifest(
bundleSize: number,
bundleDigest: string,
+ bundleMediaType: string,
+ bundlePredicateType: string,
subjectSize: number,
subjectDigest: string,
created: Date = new Date()
@@ -96,7 +96,7 @@ export function createSigstoreAttestationManifest(
const configLayer = createEmptyConfigLayer()
const sigstoreAttestationLayer: Descriptor = {
- mediaType: sigstoreBundleMediaType,
+ mediaType: bundleMediaType,
size: bundleSize,
digest: bundleDigest
}
@@ -110,14 +110,14 @@ export function createSigstoreAttestationManifest(
const manifest: OCIImageManifest = {
schemaVersion: 2,
mediaType: imageManifestMediaType,
- artifactType: sigstoreBundleMediaType,
+ artifactType: bundleMediaType,
config: configLayer,
layers: [sigstoreAttestationLayer],
subject,
annotations: {
'dev.sigstore.bundle.content': 'dsse-envelope',
- 'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1',
+ 'dev.sigstore.bundle.predicateType': bundlePredicateType,
'com.github.package.type': actionPackageAttestationAnnotationValue,
'org.opencontainers.image.created': created.toISOString()
}
@@ -129,6 +129,8 @@ export function createSigstoreAttestationManifest(
export function createReferrerTagManifest(
attestationDigest: string,
attestationSize: number,
+ bundleMediaType: string,
+ bundlePredicateType: string,
attestationCreated: Date,
created: Date = new Date()
): OCIIndexManifest {
@@ -138,14 +140,14 @@ export function createReferrerTagManifest(
manifests: [
{
mediaType: imageManifestMediaType,
- artifactType: sigstoreBundleMediaType,
+ artifactType: bundleMediaType,
size: attestationSize,
digest: attestationDigest,
annotations: {
'com.github.package.type': actionPackageAttestationAnnotationValue,
'org.opencontainers.image.created': attestationCreated.toISOString(),
'dev.sigstore.bundle.content': 'dsse-envelope',
- 'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1'
+ 'dev.sigstore.bundle.predicateType': bundlePredicateType
}
}
],