Skip to content

Commit 404a87e

Browse files
committed
feat(docker): sign built images with cosign
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent cd824b3 commit 404a87e

5 files changed

Lines changed: 164 additions & 11 deletions

File tree

.github/workflows/__test-workflow-docker-build-images.yml

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ permissions:
1616
# jscpd:ignore-start
1717
jobs:
1818
arrange:
19+
name: Arrange
1920
runs-on: ubuntu-latest
2021
steps:
2122
- run: |
@@ -24,7 +25,8 @@ jobs:
2425
exit 1
2526
fi
2627
27-
act-build-arch:
28+
act-build-images:
29+
name: Act - Build multi-arch and mono-arch images
2830
needs: arrange
2931
uses: ./.github/workflows/docker-build-images.yml
3032
secrets:
@@ -55,17 +57,18 @@ jobs:
5557
}
5658
]
5759
58-
assert-build-arch:
59-
needs: act-build-arch
60+
assert-build-arch-mono-arch:
61+
name: Assert - multi-arch and mono-arch builds
62+
needs: act-build-images
6063
runs-on: "ubuntu-latest"
6164
steps:
62-
- name: Check built images ouput
65+
- name: Assert - built images output
6366
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
6467
with:
6568
script: |
6669
const assert = require("assert");
6770
68-
const builtImagesOutput = `${{ needs.act-build-arch.outputs.built-images }}`;
71+
const builtImagesOutput = `${{ needs.act-build-images.outputs.built-images }}`;
6972
assert(builtImagesOutput.length, `"built-images" output is empty`);
7073
7174
// Check if is valid Json
@@ -132,13 +135,13 @@ jobs:
132135
username: ${{ github.repository_owner }}
133136
password: ${{ github.token }}
134137

135-
- name: Check multi-arch docker image and manifest
138+
- name: Assert - multi-arch docker image and manifest
136139
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
137140
with:
138141
script: |
139142
const assert = require("assert");
140143
141-
const image = `${{ fromJson(needs.act-build-arch.outputs.built-images).test-multi-arch.images[0] }}`;
144+
const image = `${{ fromJson(needs.act-build-images.outputs.built-images).test-multi-arch.images[0] }}`;
142145
143146
await exec.exec('docker', ['pull', image]);
144147
@@ -194,13 +197,23 @@ jobs:
194197
assert.equal(annotations[key], value, `Expected annotation not found: ${key}`);
195198
});
196199
200+
- name: Assert signed multi-arch docker image
201+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
202+
with:
203+
script: |
204+
const assert = require("assert");
205+
206+
for(const image of ${{ fromJson(needs.act-build-images.outputs.built-images).test-multi-arch.images }}) {
207+
await exec.exec('cosign', ['verify', image]);
208+
}
209+
197210
- name: Check mono-arch docker image
198211
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
199212
with:
200213
script: |
201214
const assert = require("assert");
202215
203-
const image = `${{ fromJson(needs.act-build-arch.outputs.built-images).test-mono-arch.images[0] }}`;
216+
const image = `${{ fromJson(needs.act-build-images.outputs.built-images).test-mono-arch.images[0] }}`;
204217
205218
await exec.exec('docker', ['pull', image]);
206219
@@ -239,7 +252,18 @@ jobs:
239252
);
240253
});
241254
255+
- name: Assert signed mono-arch docker image
256+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
257+
with:
258+
script: |
259+
const assert = require("assert");
260+
261+
for(const image of ${{ fromJson(needs.act-build-images.outputs.built-images).test-mono-arch.images }}) {
262+
await exec.exec('cosign', ['verify', image]);
263+
}
264+
242265
act-build-args-secrets-and-registry-caching:
266+
name: Act - Build with args, secrets and registry caching
243267
needs: arrange
244268
uses: ./.github/workflows/docker-build-images.yml
245269
secrets:
@@ -275,6 +299,7 @@ jobs:
275299
SECRET_ENV_GITHUB_APP_TOKEN_2
276300
277301
assert-build-args-secrets-and-registry-caching:
302+
name: Assert - Build with args, secrets and registry caching
278303
needs: act-build-args-secrets-and-registry-caching
279304
runs-on: "ubuntu-latest"
280305
steps:

.github/workflows/docker-build-images.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Needs the following permissions:
1919
- `issues`: `read`
2020
- `packages`: `write`
2121
- `pull-requests`: `read`
22-
- `id-token`: `write` <!-- FIXME: This is a workaround for having workflow actions. See https://github.com/orgs/community/discussions/38659 -->
22+
- `id-token`: `write`
2323

2424
<!-- end description -->
2525
<!-- start contents -->
@@ -39,7 +39,6 @@ permissions:
3939
issues: read
4040
packages: write
4141
pull-requests: read
42-
# FIXME: This is a workaround for having workflow actions. See https://github.com/orgs/community/discussions/38659
4342
id-token: write
4443

4544
jobs:

.github/workflows/docker-build-images.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ permissions:
137137
issues: read
138138
packages: write
139139
pull-requests: read
140-
# FIXME: This is a workaround for having workflow actions. See https://github.com/orgs/community/discussions/38659
141140
id-token: write
142141

143142
jobs:
@@ -500,3 +499,9 @@ jobs:
500499
oci-registry-username: ${{ inputs.oci-registry-username }}
501500
oci-registry-password: ${{ secrets.oci-registry-password }}
502501
built-images: ${{ steps.built-images.outputs.built-images }}
502+
503+
- id: sign-images
504+
uses: ./self-workflow/actions/docker/sign-images
505+
with:
506+
built-images: ${{ steps.built-images.outputs.built-images }}
507+
github-token: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!-- start title -->
2+
<!-- end title -->
3+
<!--
4+
// jscpd:ignore-start
5+
-->
6+
<!-- markdownlint-disable MD013 -->
7+
<!-- start badges -->
8+
<!-- end badges -->
9+
<!-- markdownlint-enable MD013 -->
10+
<!--
11+
// jscpd:ignore-end
12+
-->
13+
<!-- start description -->
14+
<!-- end description -->
15+
<!-- start contents -->
16+
<!-- end contents -->
17+
18+
If default GitHub token is used, the following permissions are required:
19+
20+
```yml
21+
permissions:
22+
id-token: write
23+
```
24+
25+
<!-- start usage -->
26+
<!-- end usage -->
27+
<!-- start inputs -->
28+
<!-- end inputs -->
29+
<!-- markdownlint-disable MD013 -->
30+
<!-- start outputs -->
31+
<!-- end outputs -->
32+
<!-- markdownlint-enable MD013 -->
33+
<!-- start [.github/ghadocs/examples/] -->
34+
<!-- end [.github/ghadocs/examples/] -->
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
name: "Sign image"
3+
description: |
4+
Action to sign an OCI image.
5+
It is based on [cosign](https://github.com/sigstore/cosign).
6+
It signs the image wioth the GitHub Actions OIDC token.
7+
8+
author: hoverkraft
9+
branding:
10+
icon: award
11+
color: blue
12+
13+
inputs:
14+
built-images:
15+
description: |
16+
Built images data.
17+
Example:
18+
19+
```json
20+
{
21+
"application": {
22+
"name": "application",
23+
"registry": "ghcr.io",
24+
"repository": "my-org/my-repo/application",
25+
"tags": ["pr-63-5222075","pr-63"],
26+
"images": [
27+
"ghcr.io/my-org/my-repo/application:pr-63-5222075",
28+
"ghcr.io/my-org/my-repo/application:pr-63"
29+
],
30+
"digests": [
31+
"ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
32+
"ghcr.io/my-org/my-repo/application@sha256:0f5aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f402"
33+
],
34+
"annotations": {
35+
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
36+
"org.opencontainers.image.description": "Application image"
37+
}
38+
}
39+
}
40+
```
41+
required: true
42+
github-token:
43+
description: |
44+
GitHub Token to sign the image.
45+
Permissions:
46+
- id-token: write
47+
default: ${{ github.token }}
48+
49+
runs:
50+
using: "composite"
51+
steps:
52+
- uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
53+
54+
- name: Sign the images with GitHub OIDC Token
55+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
56+
with:
57+
github-token: ${{ inputs.github-token }}
58+
script: |
59+
const builtImagesInput = `${{ inputs.built-images }}`;
60+
let builtImages = null;
61+
try {
62+
builtImages = JSON.parse(builtImagesInput);
63+
} catch (error) {
64+
throw new Error(`"built-images" input is not a valid JSON: ${error}`);
65+
}
66+
67+
// Create manifest for each image
68+
const commands = Object.keys(builtImages).map(imageName => {
69+
const builtImage = builtImages[imageName];
70+
71+
const imageTags = builtImage.images.join(" ");
72+
73+
const digests = builtImage.digests.join(" ");
74+
75+
signImageCommand = `cosign sign --recursive --yes ${imageTags}`;
76+
77+
return new Promise(async (resolve, reject) => {
78+
try {
79+
await exec.exec(signImageCommand);
80+
core.debug(`Sign image tags "${builtImage.name}" ("${signImageCommand}") executed`);
81+
resolve();
82+
} catch(error){
83+
reject(error);
84+
}
85+
});
86+
});
87+
88+
await Promise.all(commands);
89+
90+
core.debug("All images signed successfully");

0 commit comments

Comments
 (0)