add new OCI manifests for attestations
This commit is contained in:
@@ -182,7 +182,7 @@ function configureFetchMock(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const testManifest: ociContainer.Manifest = {
|
const testManifest: ociContainer.OCIImageManifest = {
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||||
artifactType: 'application/vnd.oci.image.manifest.v1+json',
|
artifactType: 'application/vnd.oci.image.manifest.v1+json',
|
||||||
@@ -526,8 +526,10 @@ function validateRequestConfig(url: string, config: any): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneLayers(layers: ociContainer.Layer[]): ociContainer.Layer[] {
|
function cloneLayers(
|
||||||
const result: ociContainer.Layer[] = []
|
layers: ociContainer.Descriptor[]
|
||||||
|
): ociContainer.Descriptor[] {
|
||||||
|
const result: ociContainer.Descriptor[] = []
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
result.push({ ...layer }) // this is _NOT_ a deep clone
|
result.push({ ...layer }) // this is _NOT_ a deep clone
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-91
@@ -1,8 +1,168 @@
|
|||||||
import { createActionPackageManifest, sha256Digest } from '../src/oci-container'
|
import {
|
||||||
|
createActionPackageManifest,
|
||||||
|
sha256Digest,
|
||||||
|
sizeInBytes,
|
||||||
|
OCIImageManifest,
|
||||||
|
createSigstoreAttestationManifest,
|
||||||
|
OCIIndexManifest,
|
||||||
|
createReferrerTagManifest
|
||||||
|
} from '../src/oci-container'
|
||||||
import { FileMetadata } from '../src/fs-helper'
|
import { FileMetadata } from '../src/fs-helper'
|
||||||
|
|
||||||
|
const createdTimestamp = '2021-01-01T00:00:00.000Z'
|
||||||
|
|
||||||
describe('sha256Digest', () => {
|
describe('sha256Digest', () => {
|
||||||
it('calculates the SHA256 digest of the provided manifest', () => {
|
it('calculates the SHA256 digest of the provided manifest', () => {
|
||||||
|
const { manifest } = testActionPackageManifest()
|
||||||
|
const digest = sha256Digest(manifest)
|
||||||
|
const expectedDigest =
|
||||||
|
'sha256:dd8537ef913cf87e25064a074973ed2c62699f1dbd74d0dd78e85d394a5758b5'
|
||||||
|
|
||||||
|
expect(digest).toEqual(expectedDigest)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('size', () => {
|
||||||
|
it('returns the total size of the provided manifest', () => {
|
||||||
|
const { manifest } = testActionPackageManifest()
|
||||||
|
const size = sizeInBytes(manifest)
|
||||||
|
expect(size).toBe(1133)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createActionPackageManifest', () => {
|
||||||
|
it('creates a manifest containing the provided information', () => {
|
||||||
|
const { manifest, zipFile, tarFile } = testActionPackageManifest()
|
||||||
|
|
||||||
|
const expectedJSON = `{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"artifactType": "application/vnd.github.actions.package.v1+json",
|
||||||
|
"config": {
|
||||||
|
"mediaType":"application/vnd.oci.empty.v1+json",
|
||||||
|
"size":2,
|
||||||
|
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
||||||
|
},
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"mediaType":"application/vnd.oci.empty.v1+json",
|
||||||
|
"size":2,
|
||||||
|
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType":"application/vnd.github.actions.package.layer.v1.tar+gzip",
|
||||||
|
"size":${tarFile.size},
|
||||||
|
"digest":"${tarFile.sha256}",
|
||||||
|
"annotations":{
|
||||||
|
"org.opencontainers.image.title":"test-org-test-repo_1.2.3.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType":"application/vnd.github.actions.package.layer.v1.zip",
|
||||||
|
"size":${zipFile.size},
|
||||||
|
"digest":"${zipFile.sha256}",
|
||||||
|
"annotations":{
|
||||||
|
"org.opencontainers.image.title":"test-org-test-repo_1.2.3.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations":{
|
||||||
|
"org.opencontainers.image.created":"${createdTimestamp}",
|
||||||
|
"action.tar.gz.digest":"${tarFile.sha256}",
|
||||||
|
"action.zip.digest":"${zipFile.sha256}",
|
||||||
|
"com.github.package.type":"actions_oci_pkg",
|
||||||
|
"com.github.package.version":"1.2.3",
|
||||||
|
"com.github.source.repo.id":"123",
|
||||||
|
"com.github.source.repo.owner.id":"456",
|
||||||
|
"com.github.source.commit":"abc"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const manifestJSON = JSON.stringify(manifest)
|
||||||
|
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createSigstoreAttestationManifest', () => {
|
||||||
|
it('creates a manifest containing the provided information', () => {
|
||||||
|
const manifest = testAttestationManifest()
|
||||||
|
|
||||||
|
const expectedJSON = `{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
||||||
|
"config": {
|
||||||
|
"mediaType": "application/vnd.oci.empty.v1+json",
|
||||||
|
"size": 2,
|
||||||
|
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
||||||
|
"size": 10,
|
||||||
|
"digest": "bundleDigest"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subject": {
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"size": 100,
|
||||||
|
"digest": "subjectDigest"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"dev.sigstore.bundle.content": "dsse-envelope",
|
||||||
|
"dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1",
|
||||||
|
"com.github.package.type": "actions_oci_pkg_attestation",
|
||||||
|
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const manifestJSON = JSON.stringify(manifest)
|
||||||
|
|
||||||
|
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createReferrerIndexManifest', () => {
|
||||||
|
it('creates a manifest containing the provided information', () => {
|
||||||
|
const manifest = testReferrerIndexManifest()
|
||||||
|
|
||||||
|
const expectedJSON = `
|
||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||||
|
"manifests": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
||||||
|
"size": 100,
|
||||||
|
"digest": "attDigest",
|
||||||
|
"annotations": {
|
||||||
|
"com.github.package.type": "actions_oci_pkg_attestation",
|
||||||
|
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z",
|
||||||
|
"dev.sigstore.bundle.content": "dsse-envelope",
|
||||||
|
"dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"annotations": {
|
||||||
|
"com.github.package.type": "actions_oci_pkg_referrer_tag",
|
||||||
|
"org.opencontainers.image.created": "2021-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const manifestJSON = JSON.stringify(manifest)
|
||||||
|
|
||||||
|
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function testActionPackageManifest(): {
|
||||||
|
manifest: OCIImageManifest
|
||||||
|
tarFile: FileMetadata
|
||||||
|
zipFile: FileMetadata
|
||||||
|
} {
|
||||||
const date = new Date('2021-01-01T00:00:00Z')
|
const date = new Date('2021-01-01T00:00:00Z')
|
||||||
const repo = 'test-org/test-repo'
|
const repo = 'test-org/test-repo'
|
||||||
const version = '1.2.3'
|
const version = '1.2.3'
|
||||||
@@ -31,98 +191,28 @@ describe('sha256Digest', () => {
|
|||||||
date
|
date
|
||||||
)
|
)
|
||||||
|
|
||||||
const digest = sha256Digest(manifest)
|
return {
|
||||||
const expectedDigest =
|
manifest,
|
||||||
'sha256:dd8537ef913cf87e25064a074973ed2c62699f1dbd74d0dd78e85d394a5758b5'
|
tarFile,
|
||||||
|
zipFile
|
||||||
expect(digest).toEqual(expectedDigest)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createActionPackageManifest', () => {
|
|
||||||
it('creates a manifest containing the provided information', () => {
|
|
||||||
const date = new Date()
|
|
||||||
const repo = 'test-org/test-repo'
|
|
||||||
const sanitizedRepo = 'test-org-test-repo'
|
|
||||||
const version = '1.2.3'
|
|
||||||
const repoId = '123'
|
|
||||||
const ownerId = '456'
|
|
||||||
const sourceCommit = 'abc'
|
|
||||||
const tarFile: FileMetadata = {
|
|
||||||
path: '/test/test/test.tar.gz',
|
|
||||||
sha256: 'tarSha',
|
|
||||||
size: 123
|
|
||||||
}
|
}
|
||||||
const zipFile: FileMetadata = {
|
|
||||||
path: '/test/test/test.zip',
|
|
||||||
sha256: 'zipSha',
|
|
||||||
size: 456
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedJSON = `{
|
function testAttestationManifest(): OCIImageManifest {
|
||||||
"schemaVersion": 2,
|
return createSigstoreAttestationManifest(
|
||||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
10,
|
||||||
"artifactType": "application/vnd.github.actions.package.v1+json",
|
'bundleDigest',
|
||||||
"config": {
|
100,
|
||||||
"mediaType":"application/vnd.oci.empty.v1+json",
|
'subjectDigest',
|
||||||
"size":2,
|
new Date(createdTimestamp)
|
||||||
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
||||||
},
|
|
||||||
"layers":[
|
|
||||||
{
|
|
||||||
"mediaType":"application/vnd.oci.empty.v1+json",
|
|
||||||
"size":2,
|
|
||||||
"digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mediaType":"application/vnd.github.actions.package.layer.v1.tar+gzip",
|
|
||||||
"size":${tarFile.size},
|
|
||||||
"digest":"${tarFile.sha256}",
|
|
||||||
"annotations":{
|
|
||||||
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.tar.gz"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mediaType":"application/vnd.github.actions.package.layer.v1.zip",
|
|
||||||
"size":${zipFile.size},
|
|
||||||
"digest":"${zipFile.sha256}",
|
|
||||||
"annotations":{
|
|
||||||
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"annotations":{
|
|
||||||
"org.opencontainers.image.created":"${date.toISOString()}",
|
|
||||||
"action.tar.gz.digest":"${tarFile.sha256}",
|
|
||||||
"action.zip.digest":"${zipFile.sha256}",
|
|
||||||
"com.github.package.type":"actions_oci_pkg",
|
|
||||||
"com.github.package.version":"1.2.3",
|
|
||||||
"com.github.source.repo.id":"123",
|
|
||||||
"com.github.source.repo.owner.id":"456",
|
|
||||||
"com.github.source.commit":"abc"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
const manifest = createActionPackageManifest(
|
|
||||||
{
|
|
||||||
path: 'test.tar.gz',
|
|
||||||
size: tarFile.size,
|
|
||||||
sha256: tarFile.sha256
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'test.zip',
|
|
||||||
size: zipFile.size,
|
|
||||||
sha256: zipFile.sha256
|
|
||||||
},
|
|
||||||
repo,
|
|
||||||
repoId,
|
|
||||||
ownerId,
|
|
||||||
sourceCommit,
|
|
||||||
version,
|
|
||||||
date
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const manifestJSON = JSON.stringify(manifest)
|
function testReferrerIndexManifest(): OCIIndexManifest {
|
||||||
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
return createReferrerTagManifest(
|
||||||
})
|
'attDigest',
|
||||||
})
|
100,
|
||||||
|
new Date(createdTimestamp),
|
||||||
|
new Date(createdTimestamp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 97.17%"><title>Coverage: 97.17%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#4c1"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">97.17%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">97.17%</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="Coverage: 97.39%"><title>Coverage: 97.39%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#4c1"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">97.39%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">97.39%</text></g></svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
+81
-9
@@ -107754,25 +107754,40 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.createActionPackageManifest = createActionPackageManifest;
|
exports.createActionPackageManifest = createActionPackageManifest;
|
||||||
|
exports.createSigstoreAttestationManifest = createSigstoreAttestationManifest;
|
||||||
|
exports.createReferrerTagManifest = createReferrerTagManifest;
|
||||||
exports.sha256Digest = sha256Digest;
|
exports.sha256Digest = sha256Digest;
|
||||||
|
exports.sizeInBytes = sizeInBytes;
|
||||||
const crypto = __importStar(__nccwpck_require__(6113));
|
const crypto = __importStar(__nccwpck_require__(6113));
|
||||||
|
const imageIndexMediaType = 'application/vnd.oci.image.index.v1+json';
|
||||||
|
const imageManifestMediaType = 'application/vnd.oci.image.manifest.v1+json';
|
||||||
|
const actionsPackageMediaType = 'application/vnd.github.actions.package.v1+json';
|
||||||
|
const actionsPackageTarLayerMediaType = 'application/vnd.github.actions.package.layer.v1.tar+gzip';
|
||||||
|
const actionsPackageZipLayerMediaType = 'application/vnd.github.actions.package.layer.v1.zip';
|
||||||
|
const sigstoreBundleMediaType = 'application/vnd.dev.sigstore.bundle.v0.3+json';
|
||||||
|
const ociEmptyMediaType = 'application/vnd.oci.empty.v1+json';
|
||||||
|
const actionPackageAnnotationValue = 'actions_oci_pkg';
|
||||||
|
const actionPackageAttestationAnnotationValue = 'actions_oci_pkg_attestation';
|
||||||
|
const actionPackageReferrerTagAnnotationValue = 'actions_oci_pkg_referrer_tag';
|
||||||
|
const emptyConfigSize = 2;
|
||||||
|
const emptyConfigSha = 'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a';
|
||||||
// Given a name and archive metadata, creates a manifest in the format expected by GHCR for an Actions Package.
|
// Given a name and archive metadata, creates a manifest in the format expected by GHCR for an Actions Package.
|
||||||
function createActionPackageManifest(tarFile, zipFile, repository, repoId, ownerId, sourceCommit, version, created) {
|
function createActionPackageManifest(tarFile, zipFile, repository, repoId, ownerId, sourceCommit, version, created = new Date()) {
|
||||||
const configLayer = createConfigLayer();
|
const configLayer = createConfigLayer();
|
||||||
const sanitizedRepo = sanitizeRepository(repository);
|
const sanitizedRepo = sanitizeRepository(repository);
|
||||||
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version);
|
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version);
|
||||||
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version);
|
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version);
|
||||||
const manifest = {
|
const manifest = {
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
mediaType: imageManifestMediaType,
|
||||||
artifactType: 'application/vnd.github.actions.package.v1+json',
|
artifactType: actionsPackageMediaType,
|
||||||
config: configLayer,
|
config: configLayer,
|
||||||
layers: [configLayer, tarLayer, zipLayer],
|
layers: [configLayer, tarLayer, zipLayer],
|
||||||
annotations: {
|
annotations: {
|
||||||
'org.opencontainers.image.created': created.toISOString(),
|
'org.opencontainers.image.created': created.toISOString(),
|
||||||
'action.tar.gz.digest': tarFile.sha256,
|
'action.tar.gz.digest': tarFile.sha256,
|
||||||
'action.zip.digest': zipFile.sha256,
|
'action.zip.digest': zipFile.sha256,
|
||||||
'com.github.package.type': 'actions_oci_pkg',
|
'com.github.package.type': actionPackageAnnotationValue,
|
||||||
'com.github.package.version': version,
|
'com.github.package.version': version,
|
||||||
'com.github.source.repo.id': repoId,
|
'com.github.source.repo.id': repoId,
|
||||||
'com.github.source.repo.owner.id': ownerId,
|
'com.github.source.repo.owner.id': ownerId,
|
||||||
@@ -107781,6 +107796,59 @@ function createActionPackageManifest(tarFile, zipFile, repository, repoId, owner
|
|||||||
};
|
};
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
function createSigstoreAttestationManifest(bundleSize, bundleDigest, subjectSize, subjectDigest, created = new Date()) {
|
||||||
|
const configLayer = createConfigLayer();
|
||||||
|
const sigstoreAttestationLayer = {
|
||||||
|
mediaType: sigstoreBundleMediaType,
|
||||||
|
size: bundleSize,
|
||||||
|
digest: bundleDigest
|
||||||
|
};
|
||||||
|
const subject = {
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
size: subjectSize,
|
||||||
|
digest: subjectDigest
|
||||||
|
};
|
||||||
|
const manifest = {
|
||||||
|
schemaVersion: 2,
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
artifactType: sigstoreBundleMediaType,
|
||||||
|
config: configLayer,
|
||||||
|
layers: [sigstoreAttestationLayer],
|
||||||
|
subject,
|
||||||
|
annotations: {
|
||||||
|
'dev.sigstore.bundle.content': 'dsse-envelope',
|
||||||
|
'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1',
|
||||||
|
'com.github.package.type': actionPackageAttestationAnnotationValue,
|
||||||
|
'org.opencontainers.image.created': created.toISOString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
function createReferrerTagManifest(attestationDigest, attestationSize, attestationCreated, created = new Date()) {
|
||||||
|
const manifest = {
|
||||||
|
schemaVersion: 2,
|
||||||
|
mediaType: imageIndexMediaType,
|
||||||
|
manifests: [
|
||||||
|
{
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
artifactType: sigstoreBundleMediaType,
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
annotations: {
|
||||||
|
'com.github.package.type': actionPackageReferrerTagAnnotationValue,
|
||||||
|
'org.opencontainers.image.created': created.toISOString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
// Calculate the SHA256 digest of a given manifest.
|
// Calculate the SHA256 digest of a given manifest.
|
||||||
// This should match the digest which the GitHub container registry calculates for this manifest.
|
// This should match the digest which the GitHub container registry calculates for this manifest.
|
||||||
function sha256Digest(manifest) {
|
function sha256Digest(manifest) {
|
||||||
@@ -107791,17 +107859,21 @@ function sha256Digest(manifest) {
|
|||||||
const hexHash = hash.digest('hex');
|
const hexHash = hash.digest('hex');
|
||||||
return `sha256:${hexHash}`;
|
return `sha256:${hexHash}`;
|
||||||
}
|
}
|
||||||
|
function sizeInBytes(manifest) {
|
||||||
|
const data = JSON.stringify(manifest);
|
||||||
|
return Buffer.byteLength(data, 'utf8');
|
||||||
|
}
|
||||||
function createConfigLayer() {
|
function createConfigLayer() {
|
||||||
const configLayer = {
|
const configLayer = {
|
||||||
mediaType: 'application/vnd.oci.empty.v1+json',
|
mediaType: ociEmptyMediaType,
|
||||||
size: 2,
|
size: emptyConfigSize,
|
||||||
digest: 'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'
|
digest: emptyConfigSha
|
||||||
};
|
};
|
||||||
return configLayer;
|
return configLayer;
|
||||||
}
|
}
|
||||||
function createZipLayer(zipFile, repository, version) {
|
function createZipLayer(zipFile, repository, version) {
|
||||||
const zipLayer = {
|
const zipLayer = {
|
||||||
mediaType: 'application/vnd.github.actions.package.layer.v1.zip',
|
mediaType: actionsPackageZipLayerMediaType,
|
||||||
size: zipFile.size,
|
size: zipFile.size,
|
||||||
digest: zipFile.sha256,
|
digest: zipFile.sha256,
|
||||||
annotations: {
|
annotations: {
|
||||||
@@ -107812,7 +107884,7 @@ function createZipLayer(zipFile, repository, version) {
|
|||||||
}
|
}
|
||||||
function createTarLayer(tarFile, repository, version) {
|
function createTarLayer(tarFile, repository, version) {
|
||||||
const tarLayer = {
|
const tarLayer = {
|
||||||
mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip',
|
mediaType: actionsPackageTarLayerMediaType,
|
||||||
size: tarFile.size,
|
size: tarFile.size,
|
||||||
digest: tarFile.sha256,
|
digest: tarFile.sha256,
|
||||||
annotations: {
|
annotations: {
|
||||||
|
|||||||
+2
-2
@@ -11,7 +11,7 @@ export async function publishOCIArtifact(
|
|||||||
semver: string,
|
semver: string,
|
||||||
zipFile: FileMetadata,
|
zipFile: FileMetadata,
|
||||||
tarFile: FileMetadata,
|
tarFile: FileMetadata,
|
||||||
manifest: ociContainer.Manifest
|
manifest: ociContainer.OCIImageManifest
|
||||||
): Promise<{ packageURL: URL; publishedDigest: string }> {
|
): Promise<{ packageURL: URL; publishedDigest: string }> {
|
||||||
const b64Token = Buffer.from(token).toString('base64')
|
const b64Token = Buffer.from(token).toString('base64')
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export async function publishOCIArtifact(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadLayer(
|
async function uploadLayer(
|
||||||
layer: ociContainer.Layer,
|
layer: ociContainer.Descriptor,
|
||||||
file: FileMetadata,
|
file: FileMetadata,
|
||||||
registryURL: URL,
|
registryURL: URL,
|
||||||
checkBlobEndpoint: string,
|
checkBlobEndpoint: string,
|
||||||
|
|||||||
+130
-23
@@ -1,19 +1,46 @@
|
|||||||
import { FileMetadata } from './fs-helper'
|
import { FileMetadata } from './fs-helper'
|
||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
|
|
||||||
export interface Manifest {
|
const imageIndexMediaType = 'application/vnd.oci.image.index.v1+json'
|
||||||
|
const imageManifestMediaType = 'application/vnd.oci.image.manifest.v1+json'
|
||||||
|
const actionsPackageMediaType = 'application/vnd.github.actions.package.v1+json'
|
||||||
|
const actionsPackageTarLayerMediaType =
|
||||||
|
'application/vnd.github.actions.package.layer.v1.tar+gzip'
|
||||||
|
const actionsPackageZipLayerMediaType =
|
||||||
|
'application/vnd.github.actions.package.layer.v1.zip'
|
||||||
|
const sigstoreBundleMediaType = 'application/vnd.dev.sigstore.bundle.v0.3+json'
|
||||||
|
const ociEmptyMediaType = 'application/vnd.oci.empty.v1+json'
|
||||||
|
|
||||||
|
const actionPackageAnnotationValue = 'actions_oci_pkg'
|
||||||
|
const actionPackageAttestationAnnotationValue = 'actions_oci_pkg_attestation'
|
||||||
|
const actionPackageReferrerTagAnnotationValue = 'actions_oci_pkg_referrer_tag'
|
||||||
|
|
||||||
|
const emptyConfigSize = 2
|
||||||
|
const emptyConfigSha =
|
||||||
|
'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'
|
||||||
|
|
||||||
|
export interface OCIImageManifest {
|
||||||
schemaVersion: number
|
schemaVersion: number
|
||||||
mediaType: string
|
mediaType: string
|
||||||
artifactType: string
|
artifactType: string
|
||||||
config: Layer
|
config: Descriptor
|
||||||
layers: Layer[]
|
layers: Descriptor[]
|
||||||
|
subject?: Descriptor
|
||||||
annotations: { [key: string]: string }
|
annotations: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Layer {
|
export interface OCIIndexManifest {
|
||||||
|
schemaVersion: number
|
||||||
|
mediaType: string
|
||||||
|
manifests: Descriptor[]
|
||||||
|
annotations: { [key: string]: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Descriptor {
|
||||||
mediaType: string
|
mediaType: string
|
||||||
size: number
|
size: number
|
||||||
digest: string
|
digest: string
|
||||||
|
artifactType?: string
|
||||||
annotations?: { [key: string]: string }
|
annotations?: { [key: string]: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,24 +53,24 @@ export function createActionPackageManifest(
|
|||||||
ownerId: string,
|
ownerId: string,
|
||||||
sourceCommit: string,
|
sourceCommit: string,
|
||||||
version: string,
|
version: string,
|
||||||
created: Date
|
created: Date = new Date()
|
||||||
): Manifest {
|
): OCIImageManifest {
|
||||||
const configLayer = createConfigLayer()
|
const configLayer = createConfigLayer()
|
||||||
const sanitizedRepo = sanitizeRepository(repository)
|
const sanitizedRepo = sanitizeRepository(repository)
|
||||||
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version)
|
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version)
|
||||||
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version)
|
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version)
|
||||||
|
|
||||||
const manifest: Manifest = {
|
const manifest: OCIImageManifest = {
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
mediaType: imageManifestMediaType,
|
||||||
artifactType: 'application/vnd.github.actions.package.v1+json',
|
artifactType: actionsPackageMediaType,
|
||||||
config: configLayer,
|
config: configLayer,
|
||||||
layers: [configLayer, tarLayer, zipLayer],
|
layers: [configLayer, tarLayer, zipLayer],
|
||||||
annotations: {
|
annotations: {
|
||||||
'org.opencontainers.image.created': created.toISOString(),
|
'org.opencontainers.image.created': created.toISOString(),
|
||||||
'action.tar.gz.digest': tarFile.sha256,
|
'action.tar.gz.digest': tarFile.sha256,
|
||||||
'action.zip.digest': zipFile.sha256,
|
'action.zip.digest': zipFile.sha256,
|
||||||
'com.github.package.type': 'actions_oci_pkg',
|
'com.github.package.type': actionPackageAnnotationValue,
|
||||||
'com.github.package.version': version,
|
'com.github.package.version': version,
|
||||||
'com.github.source.repo.id': repoId,
|
'com.github.source.repo.id': repoId,
|
||||||
'com.github.source.repo.owner.id': ownerId,
|
'com.github.source.repo.owner.id': ownerId,
|
||||||
@@ -54,9 +81,83 @@ export function createActionPackageManifest(
|
|||||||
return manifest
|
return manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createSigstoreAttestationManifest(
|
||||||
|
bundleSize: number,
|
||||||
|
bundleDigest: string,
|
||||||
|
subjectSize: number,
|
||||||
|
subjectDigest: string,
|
||||||
|
created: Date = new Date()
|
||||||
|
): OCIImageManifest {
|
||||||
|
const configLayer = createConfigLayer()
|
||||||
|
|
||||||
|
const sigstoreAttestationLayer: Descriptor = {
|
||||||
|
mediaType: sigstoreBundleMediaType,
|
||||||
|
size: bundleSize,
|
||||||
|
digest: bundleDigest
|
||||||
|
}
|
||||||
|
|
||||||
|
const subject: Descriptor = {
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
size: subjectSize,
|
||||||
|
digest: subjectDigest
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest: OCIImageManifest = {
|
||||||
|
schemaVersion: 2,
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
artifactType: sigstoreBundleMediaType,
|
||||||
|
config: configLayer,
|
||||||
|
layers: [sigstoreAttestationLayer],
|
||||||
|
subject,
|
||||||
|
|
||||||
|
annotations: {
|
||||||
|
'dev.sigstore.bundle.content': 'dsse-envelope',
|
||||||
|
'dev.sigstore.bundle.predicateType': 'https://slsa.dev/provenance/v1',
|
||||||
|
'com.github.package.type': actionPackageAttestationAnnotationValue,
|
||||||
|
'org.opencontainers.image.created': created.toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReferrerTagManifest(
|
||||||
|
attestationDigest: string,
|
||||||
|
attestationSize: number,
|
||||||
|
attestationCreated: Date,
|
||||||
|
created: Date = new Date()
|
||||||
|
): OCIIndexManifest {
|
||||||
|
const manifest: OCIIndexManifest = {
|
||||||
|
schemaVersion: 2,
|
||||||
|
mediaType: imageIndexMediaType,
|
||||||
|
manifests: [
|
||||||
|
{
|
||||||
|
mediaType: imageManifestMediaType,
|
||||||
|
artifactType: sigstoreBundleMediaType,
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
annotations: {
|
||||||
|
'com.github.package.type': actionPackageReferrerTagAnnotationValue,
|
||||||
|
'org.opencontainers.image.created': created.toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the SHA256 digest of a given manifest.
|
// Calculate the SHA256 digest of a given manifest.
|
||||||
// This should match the digest which the GitHub container registry calculates for this manifest.
|
// This should match the digest which the GitHub container registry calculates for this manifest.
|
||||||
export function sha256Digest(manifest: Manifest): string {
|
export function sha256Digest(
|
||||||
|
manifest: OCIImageManifest | OCIIndexManifest
|
||||||
|
): string {
|
||||||
const data = JSON.stringify(manifest)
|
const data = JSON.stringify(manifest)
|
||||||
const buffer = Buffer.from(data, 'utf8')
|
const buffer = Buffer.from(data, 'utf8')
|
||||||
const hash = crypto.createHash('sha256')
|
const hash = crypto.createHash('sha256')
|
||||||
@@ -65,12 +166,18 @@ export function sha256Digest(manifest: Manifest): string {
|
|||||||
return `sha256:${hexHash}`
|
return `sha256:${hexHash}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function createConfigLayer(): Layer {
|
export function sizeInBytes(
|
||||||
const configLayer: Layer = {
|
manifest: OCIImageManifest | OCIIndexManifest
|
||||||
mediaType: 'application/vnd.oci.empty.v1+json',
|
): number {
|
||||||
size: 2,
|
const data = JSON.stringify(manifest)
|
||||||
digest:
|
return Buffer.byteLength(data, 'utf8')
|
||||||
'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'
|
}
|
||||||
|
|
||||||
|
function createConfigLayer(): Descriptor {
|
||||||
|
const configLayer: Descriptor = {
|
||||||
|
mediaType: ociEmptyMediaType,
|
||||||
|
size: emptyConfigSize,
|
||||||
|
digest: emptyConfigSha
|
||||||
}
|
}
|
||||||
|
|
||||||
return configLayer
|
return configLayer
|
||||||
@@ -80,9 +187,9 @@ function createZipLayer(
|
|||||||
zipFile: FileMetadata,
|
zipFile: FileMetadata,
|
||||||
repository: string,
|
repository: string,
|
||||||
version: string
|
version: string
|
||||||
): Layer {
|
): Descriptor {
|
||||||
const zipLayer: Layer = {
|
const zipLayer: Descriptor = {
|
||||||
mediaType: 'application/vnd.github.actions.package.layer.v1.zip',
|
mediaType: actionsPackageZipLayerMediaType,
|
||||||
size: zipFile.size,
|
size: zipFile.size,
|
||||||
digest: zipFile.sha256,
|
digest: zipFile.sha256,
|
||||||
annotations: {
|
annotations: {
|
||||||
@@ -97,9 +204,9 @@ function createTarLayer(
|
|||||||
tarFile: FileMetadata,
|
tarFile: FileMetadata,
|
||||||
repository: string,
|
repository: string,
|
||||||
version: string
|
version: string
|
||||||
): Layer {
|
): Descriptor {
|
||||||
const tarLayer: Layer = {
|
const tarLayer: Descriptor = {
|
||||||
mediaType: 'application/vnd.github.actions.package.layer.v1.tar+gzip',
|
mediaType: actionsPackageTarLayerMediaType,
|
||||||
size: tarFile.size,
|
size: tarFile.size,
|
||||||
digest: tarFile.sha256,
|
digest: tarFile.sha256,
|
||||||
annotations: {
|
annotations: {
|
||||||
|
|||||||
Reference in New Issue
Block a user