Skip to content

Commit

Permalink
handle attests correctly with provenance and sbom inputs
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Mar 26, 2024
1 parent 090ca15 commit 5bb8c00
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 41 deletions.
88 changes: 74 additions & 14 deletions __tests__/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
'.'
]
Expand All @@ -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'),
'.'
]
Expand All @@ -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'),
'.'
]
Expand All @@ -538,7 +538,7 @@ nproc=3`],
[
'build',
'--iidfile', path.join(tmpDir, 'iidfile'),
"--provenance", 'false',
'--attest', 'type=provenance,false',
'--metadata-file', path.join(tmpDir, 'metadata-file'),
'.'
]
Expand All @@ -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'),
'.'
]
Expand Down Expand Up @@ -620,7 +620,7 @@ nproc=3`],
]
],
[
25,
26,
'0.10.0',
new Map<string, string>([
['context', '.'],
Expand All @@ -642,7 +642,7 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`]
]
],
[
26,
27,
'0.10.0',
new Map<string, string>([
['context', '.'],
Expand All @@ -663,7 +663,7 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`]
]
],
[
27,
28,
'0.11.0',
new Map<string, string>([
['context', '.'],
Expand All @@ -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<string, string>([
['context', '.'],
Expand All @@ -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<string, string>([
['context', '.'],
Expand All @@ -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<string, string>([
['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,true`,
'--metadata-file', path.join(tmpDir, 'metadata-file'),
'.'
]
],
[
32,
'0.13.1',
new Map<string, string>([
['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<string, string>([
['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<string, string>, expected: Array<string>) => {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@docker/actions-toolkit": "0.19.0",
"csv-parse": "^5.5.5",
"handlebars": "^4.7.7"
},
"devDependencies": {
Expand Down
97 changes: 70 additions & 27 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from '@actions/core';
import * as handlebars from 'handlebars';
import {parse} from 'csv-parse/sync';
import {Context} from '@docker/actions-toolkit/lib/context';
import {GitHub} from '@docker/actions-toolkit/lib/github';
import {Inputs as BuildxInputs} from '@docker/actions-toolkit/lib/buildx/inputs';
Expand Down Expand Up @@ -98,13 +99,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);
Expand Down Expand Up @@ -157,26 +151,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 {
Expand Down Expand Up @@ -238,3 +215,69 @@ async function getCommonArgs(inputs: Inputs, toolkit: Toolkit): Promise<Array<st
}
return args;
}

async function getAttestArgs(inputs: Inputs, toolkit: Toolkit): Promise<Array<string>> {
const args: Array<string> = [];

// check if provenance attestation is set in attests input
let hasAttestProvenance = false;
await Util.asyncForEach(inputs.attests, async (attest: string) => {
if (hasAttestationType('provenance', attest)) {
hasAttestProvenance = true;
}
});

let provenanceSet = false;
let sbomSet = false;
if (inputs.provenance) {
args.push('--attest', `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', `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 (!hasAttestationType('provenance', attest) && !hasAttestationType('sbom', attest)) {
args.push('--attest', attest);
} else if (!provenanceSet && hasAttestationType('provenance', attest)) {
args.push('--attest', BuildxInputs.resolveProvenanceAttrs(attest));
} else if (!sbomSet && hasAttestationType('sbom', attest)) {
args.push('--attest', attest);
}
});

return args;
}

function hasAttestationType(name: string, attrs: string): boolean {
const attributes = parse(attrs, {
delimiter: ',',
trim: true,
columns: false,
relaxColumnCount: true
});
for (const attr of attributes) {
for (const [key, value] of attr.map((chunk: string) => chunk.split('=').map(item => item.trim()))) {
if (key == 'type' && value == name) {
return true;
}
}
}
return false;
}

0 comments on commit 5bb8c00

Please sign in to comment.