Generate provenance attestation before performing upload to ghcr
This allows us to check in the backend that a valid attestation exists for a package version before we allow the upload to succeed. In doing this, we can perform an integrity check that the attestation is valid and all action packages have valid attestations.
This commit is contained in:
+2
-2
@@ -12,7 +12,7 @@ export async function publishOCIArtifact(
|
||||
zipFile: FileMetadata,
|
||||
tarFile: FileMetadata,
|
||||
manifest: ociContainer.Manifest
|
||||
): Promise<{ packageURL: URL; manifestDigest: string }> {
|
||||
): Promise<{ packageURL: URL; publishedDigest: string }> {
|
||||
const b64Token = Buffer.from(token).toString('base64')
|
||||
|
||||
const checkBlobEndpoint = new URL(
|
||||
@@ -76,7 +76,7 @@ export async function publishOCIArtifact(
|
||||
|
||||
return {
|
||||
packageURL: new URL(`${repository}:${semver}`, registry),
|
||||
manifestDigest: digest
|
||||
publishedDigest: digest
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-13
@@ -50,19 +50,7 @@ export async function run(): Promise<void> {
|
||||
new Date()
|
||||
)
|
||||
|
||||
const { packageURL, manifestDigest } = await ghcr.publishOCIArtifact(
|
||||
options.token,
|
||||
options.containerRegistryUrl,
|
||||
options.nameWithOwner,
|
||||
semverTag.raw,
|
||||
archives.zipFile,
|
||||
archives.tarFile,
|
||||
manifest
|
||||
)
|
||||
|
||||
core.setOutput('package-url', packageURL.toString())
|
||||
core.setOutput('package-manifest', JSON.stringify(manifest))
|
||||
core.setOutput('package-manifest-sha', manifestDigest)
|
||||
const manifestDigest = ociContainer.sha256Digest(manifest)
|
||||
|
||||
// Attestations are not currently supported in GHES.
|
||||
if (!options.isEnterprise) {
|
||||
@@ -75,6 +63,26 @@ export async function run(): Promise<void> {
|
||||
core.setOutput('attestation-id', attestation.attestationID)
|
||||
}
|
||||
}
|
||||
|
||||
const { packageURL, publishedDigest } = await ghcr.publishOCIArtifact(
|
||||
options.token,
|
||||
options.containerRegistryUrl,
|
||||
options.nameWithOwner,
|
||||
semverTag.raw,
|
||||
archives.zipFile,
|
||||
archives.tarFile,
|
||||
manifest
|
||||
)
|
||||
|
||||
if (manifestDigest !== publishedDigest) {
|
||||
throw new Error(
|
||||
`Unexpected digest returned for manifest. Expected ${manifestDigest}, got ${publishedDigest}`
|
||||
)
|
||||
}
|
||||
|
||||
core.setOutput('package-url', packageURL.toString())
|
||||
core.setOutput('package-manifest', JSON.stringify(manifest))
|
||||
core.setOutput('package-manifest-sha', publishedDigest)
|
||||
} catch (error) {
|
||||
// Fail the workflow run if an error occurs
|
||||
if (error instanceof Error) core.setFailed(error.message)
|
||||
@@ -112,6 +120,8 @@ async function generateAttestation(
|
||||
const subjectName = `${options.nameWithOwner}@${semverTag}`
|
||||
const subjectDigest = removePrefix(manifestDigest, 'sha256:')
|
||||
|
||||
core.info(`Generating attestation ${subjectName} for digest ${subjectDigest}`)
|
||||
|
||||
return await attest.attestProvenance({
|
||||
subjectName,
|
||||
subjectDigest: { sha256: subjectDigest },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FileMetadata } from './fs-helper'
|
||||
import * as crypto from 'crypto'
|
||||
|
||||
export interface Manifest {
|
||||
schemaVersion: number
|
||||
@@ -53,6 +54,17 @@ export function createActionPackageManifest(
|
||||
return manifest
|
||||
}
|
||||
|
||||
// Calculate the SHA256 digest of a given manifest.
|
||||
// This should match the digest which the GitHub container registry calculates for this manifest.
|
||||
export function sha256Digest(manifest: Manifest): string {
|
||||
const data = JSON.stringify(manifest)
|
||||
const buffer = Buffer.from(data, 'utf8')
|
||||
const hash = crypto.createHash('sha256')
|
||||
hash.update(buffer)
|
||||
const hexHash = hash.digest('hex')
|
||||
return `sha256:${hexHash}`
|
||||
}
|
||||
|
||||
function createConfigLayer(): Layer {
|
||||
const configLayer: Layer = {
|
||||
mediaType: 'application/vnd.oci.empty.v1+json',
|
||||
|
||||
Reference in New Issue
Block a user