From 96acf63e4cabaa18c27005e204ddd023996571f3 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:31:37 +0100 Subject: [PATCH] handle attests correctly with provenance and sbom inputs Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/context.test.ts | 88 ++++++++++++++++++++++++++++++++------- src/context.ts | 79 +++++++++++++++++++++++------------ 2 files changed, 126 insertions(+), 41 deletions(-) diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index bcc8bba..8d2a107 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -481,7 +481,7 @@ nproc=3`], [ 'build', '--iidfile', path.join(tmpDir, 'iidfile'), - "--provenance", `mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] @@ -500,7 +500,7 @@ nproc=3`], [ 'build', '--iidfile', path.join(tmpDir, 'iidfile'), - "--provenance", `builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] @@ -519,7 +519,7 @@ nproc=3`], [ 'build', '--iidfile', path.join(tmpDir, 'iidfile'), - "--provenance", `mode=max,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,mode=max,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] @@ -538,7 +538,7 @@ nproc=3`], [ 'build', '--iidfile', path.join(tmpDir, 'iidfile'), - "--provenance", 'false', + '--attest', 'type=provenance,disabled=true', '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] @@ -557,7 +557,7 @@ nproc=3`], [ 'build', '--iidfile', path.join(tmpDir, 'iidfile'), - "--provenance", 'builder-id=foo', + '--attest', 'type=provenance,builder-id=foo', '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] @@ -620,7 +620,7 @@ nproc=3`], ] ], [ - 25, + 26, '0.10.0', new Map([ ['context', '.'], @@ -642,7 +642,7 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] ] ], [ - 26, + 27, '0.10.0', new Map([ ['context', '.'], @@ -663,7 +663,7 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] ] ], [ - 27, + 28, '0.11.0', new Map([ ['context', '.'], @@ -677,13 +677,13 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] [ 'build', '--output', 'type=local,dest=./release-out', - "--provenance", `mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] ], [ - 28, + 29, '0.12.0', new Map([ ['context', '.'], @@ -701,13 +701,13 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] '--annotation', 'manifest:example3=yyy', '--annotation', 'manifest-descriptor[linux/amd64]:example4=zzz', '--output', 'type=local,dest=./release-out', - "--provenance", `mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] ], [ - 29, + 30, '0.12.0', new Map([ ['context', '.'], @@ -721,11 +721,71 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] 'build', '--iidfile', path.join(tmpDir, 'iidfile'), "--output", `type=image,"name=localhost:5000/name/app:latest,localhost:5000/name/app:foo",push-by-digest=true,name-canonical=true,push=true`, - "--provenance", `mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, '--metadata-file', path.join(tmpDir, 'metadata-file'), '.' ] - ] + ], + [ + 31, + '0.13.1', + new Map([ + ['context', '.'], + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'false'], + ['pull', 'false'], + ['provenance', 'mode=max'], + ['sbom', 'true'], + ]), + [ + 'build', + '--iidfile', path.join(tmpDir, 'iidfile'), + '--attest', `type=provenance,mode=max,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--attest', `type=sbom,disabled=false`, + '--metadata-file', path.join(tmpDir, 'metadata-file'), + '.' + ] + ], + [ + 32, + '0.13.1', + new Map([ + ['context', '.'], + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'false'], + ['pull', 'false'], + ['attests', 'type=provenance,mode=min'], + ['provenance', 'mode=max'], + ]), + [ + 'build', + '--iidfile', path.join(tmpDir, 'iidfile'), + '--attest', `type=provenance,mode=max,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--metadata-file', path.join(tmpDir, 'metadata-file'), + '.' + ] + ], + [ + 33, + '0.13.1', + new Map([ + ['context', '.'], + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'false'], + ['pull', 'false'], + ['attests', 'type=provenance,mode=min'], + ]), + [ + 'build', + '--iidfile', path.join(tmpDir, 'iidfile'), + '--attest', `type=provenance,mode=min,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789`, + '--metadata-file', path.join(tmpDir, 'metadata-file'), + '.' + ] + ], ])( '[%d] given %p with %p as inputs, returns %p', async (num: number, buildxVersion: string, inputs: Map, expected: Array) => { diff --git a/src/context.ts b/src/context.ts index 28ec871..480cb1d 100644 --- a/src/context.ts +++ b/src/context.ts @@ -98,13 +98,6 @@ async function getBuildArgs(inputs: Inputs, context: string, toolkit: Toolkit): if (inputs.allow.length > 0) { args.push('--allow', inputs.allow.join(',')); } - if (await toolkit.buildx.versionSatisfies('>=0.10.0')) { - await Util.asyncForEach(inputs.attests, async attest => { - args.push('--attest', attest); - }); - } else if (inputs.attests.length > 0) { - core.warning("Attestations are only supported by buildx >= 0.10.0; the input 'attests' is ignored."); - } if (await toolkit.buildx.versionSatisfies('>=0.12.0')) { await Util.asyncForEach(inputs.annotations, async annotation => { args.push('--annotation', annotation); @@ -157,26 +150,9 @@ async function getBuildArgs(inputs: Inputs, context: string, toolkit: Toolkit): args.push('--platform', inputs.platforms.join(',')); } if (await toolkit.buildx.versionSatisfies('>=0.10.0')) { - if (inputs.provenance) { - args.push('--provenance', inputs.provenance); - } else if ((await toolkit.buildkit.versionSatisfies(inputs.builder, '>=0.11.0')) && !BuildxInputs.hasDockerExporter(inputs.outputs, inputs.load)) { - // if provenance not specified and BuildKit version compatible for - // attestation, set default provenance. Also needs to make sure user - // doesn't want to explicitly load the image to docker. - if (GitHub.context.payload.repository?.private ?? false) { - // if this is a private repository, we set the default provenance - // attributes being set in buildx: https://github.com/docker/buildx/blob/fb27e3f919dcbf614d7126b10c2bc2d0b1927eb6/build/build.go#L603 - args.push('--provenance', BuildxInputs.resolveProvenanceAttrs(`mode=min,inline-only=true`)); - } else { - // for a public repository, we set max provenance mode. - args.push('--provenance', BuildxInputs.resolveProvenanceAttrs(`mode=max`)); - } - } - if (inputs.sbom) { - args.push('--sbom', inputs.sbom); - } - } else if (inputs.provenance || inputs.sbom) { - core.warning("Attestations are only supported by buildx >= 0.10.0; the inputs 'provenance' and 'sbom' are ignored."); + args.push(...(await getAttestArgs(inputs, toolkit))); + } else { + core.warning("Attestations are only supported by buildx >= 0.10.0; the inputs 'attests', 'provenance' and 'sbom' are ignored."); } await Util.asyncForEach(inputs.secrets, async secret => { try { @@ -238,3 +214,52 @@ async function getCommonArgs(inputs: Inputs, toolkit: Toolkit): Promise> { + const args: Array = []; + + // check if provenance attestation is set in attests input + let hasAttestProvenance = false; + await Util.asyncForEach(inputs.attests, async (attest: string) => { + if (BuildxInputs.hasAttestationType('provenance', attest)) { + hasAttestProvenance = true; + } + }); + + let provenanceSet = false; + let sbomSet = false; + if (inputs.provenance) { + args.push('--attest', BuildxInputs.resolveAttestationAttrs(`type=provenance,${inputs.provenance}`)); + provenanceSet = true; + } else if (!hasAttestProvenance && (await toolkit.buildkit.versionSatisfies(inputs.builder, '>=0.11.0')) && !BuildxInputs.hasDockerExporter(inputs.outputs, inputs.load)) { + // if provenance not specified in provenance or attests inputs and BuildKit + // version compatible for attestation, set default provenance. Also needs + // to make sure user doesn't want to explicitly load the image to docker. + if (GitHub.context.payload.repository?.private ?? false) { + // if this is a private repository, we set the default provenance + // attributes being set in buildx: https://github.com/docker/buildx/blob/fb27e3f919dcbf614d7126b10c2bc2d0b1927eb6/build/build.go#L603 + args.push('--attest', `type=provenance,${BuildxInputs.resolveProvenanceAttrs(`mode=min,inline-only=true`)}`); + } else { + // for a public repository, we set max provenance mode. + args.push('--attest', `type=provenance,${BuildxInputs.resolveProvenanceAttrs(`mode=max`)}`); + } + } + if (inputs.sbom) { + args.push('--attest', BuildxInputs.resolveAttestationAttrs(`type=sbom,${inputs.sbom}`)); + sbomSet = true; + } + + // set attests but check if provenance or sbom types already set as + // provenance and sbom inputs take precedence over attests input. + await Util.asyncForEach(inputs.attests, async (attest: string) => { + if (!BuildxInputs.hasAttestationType('provenance', attest) && !BuildxInputs.hasAttestationType('sbom', attest)) { + args.push('--attest', BuildxInputs.resolveAttestationAttrs(attest)); + } else if (!provenanceSet && BuildxInputs.hasAttestationType('provenance', attest)) { + args.push('--attest', BuildxInputs.resolveProvenanceAttrs(attest)); + } else if (!sbomSet && BuildxInputs.hasAttestationType('sbom', attest)) { + args.push('--attest', attest); + } + }); + + return args; +}