Skip to content

Commit 228a6e2

Browse files
authored
Fix: [AEA-0000] - build attestation (#22)
## Summary - Routine Change ### Details - add build attestation
1 parent 469314c commit 228a6e2

File tree

3 files changed

+258
-4
lines changed

3 files changed

+258
-4
lines changed

.github/scripts/delete_unused_images.sh

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
DRY_RUN=false
44
DELETE_PR=false
55
DELETE_CI=false
6+
DELETE_UNTAGGED=false
67

78
while [[ $# -gt 0 ]]; do
89
case "$1" in
@@ -18,13 +19,17 @@ while [[ $# -gt 0 ]]; do
1819
DELETE_CI=true
1920
shift
2021
;;
22+
--delete-untagged)
23+
DELETE_UNTAGGED=true
24+
shift
25+
;;
2126
--help|-h)
22-
echo "Usage: $0 [--dry-run] [--delete-pr] [--delete-ci]"
27+
echo "Usage: $0 [--dry-run] [--delete-pr] [--delete-ci] [--delete-untagged]"
2328
exit 0
2429
;;
2530
*)
2631
echo "Unknown option: $1" >&2
27-
echo "Usage: $0 [--dry-run] [--delete-pr] [--delete-ci]" >&2
32+
echo "Usage: $0 [--dry-run] [--delete-pr] [--delete-ci] [--delete-untagged]" >&2
2833
exit 1
2934
;;
3035
esac
@@ -166,7 +171,37 @@ delete_ci_images() {
166171
done <<<"${tags}"
167172
}
168173

174+
delete_untagged_images() {
175+
local container_name=$1
176+
local package_name
177+
local versions_json
169178

179+
if [[ -z "${container_name}" ]]; then
180+
echo "Container name is required" >&2
181+
return 1
182+
fi
183+
184+
package_name=$(get_container_package_name "${container_name}")
185+
versions_json=$(get_container_versions_json "${container_name}")
186+
187+
jq -r '.[] | select(((.metadata.container.tags // []) | length) == 0) | .id' \
188+
<<<"${versions_json}" \
189+
| while IFS= read -r version_id; do
190+
if [[ -n "${version_id}" ]]; then
191+
if [[ "${DRY_RUN}" == "true" ]]; then
192+
echo "[DRY RUN] Would delete untagged image version ID ${version_id} from container ${container_name}."
193+
else
194+
echo "Deleting untagged image version ID ${version_id} from container ${container_name}..."
195+
gh api \
196+
-H "Accept: application/vnd.github+json" \
197+
-X DELETE \
198+
"/orgs/nhsdigital/packages/container/${package_name}/versions/${version_id}"
199+
fi
200+
fi
201+
done
202+
}
203+
204+
base_node_folders=$(find src/base_node -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
170205
language_folders=$(find src/languages -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
171206
project_folders=$(find src/projects -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
172207

@@ -177,6 +212,21 @@ for container_name in $(jq -r '.[]' <<<"${project_folders}"); do
177212
if [[ "${DELETE_CI}" == "true" ]]; then
178213
delete_ci_images "${container_name}"
179214
fi
215+
if [[ "${DELETE_UNTAGGED}" == "true" ]]; then
216+
delete_untagged_images "${container_name}"
217+
fi
218+
done
219+
220+
for container_name in $(jq -r '.[]' <<<"${base_node_folders}"); do
221+
if [[ "${DELETE_PR}" == "true" ]]; then
222+
delete_pr_images "${container_name}"
223+
fi
224+
if [[ "${DELETE_CI}" == "true" ]]; then
225+
delete_ci_images "${container_name}"
226+
fi
227+
if [[ "${DELETE_UNTAGGED}" == "true" ]]; then
228+
delete_untagged_images "${container_name}"
229+
fi
180230
done
181231

182232
for container_name in $(jq -r '.[]' <<<"${language_folders}"); do
@@ -186,6 +236,9 @@ for container_name in $(jq -r '.[]' <<<"${language_folders}"); do
186236
if [[ "${DELETE_CI}" == "true" ]]; then
187237
delete_ci_images "${container_name}"
188238
fi
239+
if [[ "${DELETE_UNTAGGED}" == "true" ]]; then
240+
delete_untagged_images "${container_name}"
241+
fi
189242
done
190243

191244
if [[ "${DELETE_PR}" == "true" ]]; then
@@ -194,3 +247,6 @@ fi
194247
if [[ "${DELETE_CI}" == "true" ]]; then
195248
delete_ci_images "base"
196249
fi
250+
if [[ "${DELETE_UNTAGGED}" == "true" ]]; then
251+
delete_untagged_images "base"
252+
fi

.github/workflows/build_multi_arch_image.yml

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jobs:
8888
IMAGE_TAG: "${{ inputs.docker_tag }}-${{ matrix.arch }}"
8989
BASE_FOLDER: "${{ inputs.base_folder }}"
9090
NO_CACHE: '${{ inputs.NO_CACHE }}'
91+
BUILDX_NO_DEFAULT_ATTESTATIONS: "1"
9192
- name: Check docker vulnerabilities - json output
9293
run: |
9394
make scan-image-json
@@ -136,6 +137,55 @@ jobs:
136137
DOCKER_TAG: ${{ inputs.docker_tag }}
137138
CONTAINER_NAME: '${{ inputs.container_name }}'
138139
ARCHITECTURE: '${{ matrix.arch }}'
140+
BUILDX_NO_DEFAULT_ATTESTATIONS: "1"
141+
- name: Resolve image digest
142+
id: resolve_arch_digest
143+
run: |
144+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}" | awk '/^Digest:/ {print $2; exit}')
145+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
146+
echo "Resolved digest ${DIGEST} for ${DOCKER_TAG}-${ARCHITECTURE}"
147+
env:
148+
DOCKER_TAG: ${{ inputs.docker_tag }}
149+
CONTAINER_NAME: '${{ inputs.container_name }}'
150+
ARCHITECTURE: '${{ matrix.arch }}'
151+
- name: Attest image
152+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
153+
with:
154+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
155+
subject-digest: ${{ steps.resolve_arch_digest.outputs.digest }}
156+
push-to-registry: false
157+
- name: Summarise attested image
158+
run: |
159+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
160+
env:
161+
DOCKER_TAG: ${{ inputs.docker_tag }}
162+
CONTAINER_NAME: '${{ inputs.container_name }}'
163+
ARCHITECTURE: '${{ matrix.arch }}'
164+
DIGEST: ${{ steps.resolve_arch_digest.outputs.digest }}
165+
- name: Resolve github actions image digest
166+
id: resolve_githubactions_arch_digest
167+
run: |
168+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-${ARCHITECTURE}" | awk '/^Digest:/ {print $2; exit}')
169+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
170+
echo "Resolved digest ${DIGEST} for githubactions-${DOCKER_TAG}-${ARCHITECTURE}"
171+
env:
172+
DOCKER_TAG: ${{ inputs.docker_tag }}
173+
CONTAINER_NAME: '${{ inputs.container_name }}'
174+
ARCHITECTURE: '${{ matrix.arch }}'
175+
- name: Attest github actions image
176+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
177+
with:
178+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
179+
subject-digest: ${{ steps.resolve_githubactions_arch_digest.outputs.digest }}
180+
push-to-registry: false
181+
- name: Summarise attested github actions image
182+
run: |
183+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-${ARCHITECTURE}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
184+
env:
185+
DOCKER_TAG: ${{ inputs.docker_tag }}
186+
CONTAINER_NAME: '${{ inputs.container_name }}'
187+
ARCHITECTURE: '${{ matrix.arch }}'
188+
DIGEST: ${{ steps.resolve_githubactions_arch_digest.outputs.digest }}
139189
- name: Push latest image
140190
if: ${{ inputs.tag_latest }}
141191
run: |
@@ -152,6 +202,56 @@ jobs:
152202
DOCKER_TAG: ${{ inputs.docker_tag }}
153203
CONTAINER_NAME: '${{ inputs.container_name }}'
154204
ARCHITECTURE: '${{ matrix.arch }}'
205+
- name: Resolve github actions latest image digest
206+
if: ${{ inputs.tag_latest }}
207+
id: resolve_githubactions_latest_arch_digest
208+
run: |
209+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-${ARCHITECTURE}" | awk '/^Digest:/ {print $2; exit}')
210+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
211+
echo "Resolved digest ${DIGEST} for githubactions-latest-${ARCHITECTURE}"
212+
env:
213+
CONTAINER_NAME: '${{ inputs.container_name }}'
214+
ARCHITECTURE: '${{ matrix.arch }}'
215+
- name: Attest github actions latest image
216+
if: ${{ inputs.tag_latest }}
217+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
218+
with:
219+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
220+
subject-digest: ${{ steps.resolve_githubactions_latest_arch_digest.outputs.digest }}
221+
push-to-registry: false
222+
- name: Summarise attested github actions latest image
223+
if: ${{ inputs.tag_latest }}
224+
run: |
225+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-${ARCHITECTURE}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
226+
env:
227+
CONTAINER_NAME: '${{ inputs.container_name }}'
228+
ARCHITECTURE: '${{ matrix.arch }}'
229+
DIGEST: ${{ steps.resolve_githubactions_latest_arch_digest.outputs.digest }}
230+
- name: Resolve latest image digest
231+
if: ${{ inputs.tag_latest }}
232+
id: resolve_latest_arch_digest
233+
run: |
234+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}" | awk '/^Digest:/ {print $2; exit}')
235+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
236+
echo "Resolved digest ${DIGEST} for latest-${ARCHITECTURE}"
237+
env:
238+
CONTAINER_NAME: '${{ inputs.container_name }}'
239+
ARCHITECTURE: '${{ matrix.arch }}'
240+
- name: Attest latest image
241+
if: ${{ inputs.tag_latest }}
242+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
243+
with:
244+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
245+
subject-digest: ${{ steps.resolve_latest_arch_digest.outputs.digest }}
246+
push-to-registry: false
247+
- name: Summarise attested latest image
248+
if: ${{ inputs.tag_latest }}
249+
run: |
250+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
251+
env:
252+
CONTAINER_NAME: '${{ inputs.container_name }}'
253+
ARCHITECTURE: '${{ matrix.arch }}'
254+
DIGEST: ${{ steps.resolve_latest_arch_digest.outputs.digest }}
155255
publish_combined_image:
156256
name: Publish combined image for ${{ inputs.container_name }}
157257
runs-on: ubuntu-22.04
@@ -222,3 +322,101 @@ jobs:
222322
env:
223323
DOCKER_TAG: ${{ inputs.docker_tag }}
224324
CONTAINER_NAME: '${{ inputs.container_name }}'
325+
326+
- name: Resolve combined image digest
327+
id: resolve_combined_digest
328+
run: |
329+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}" | awk '/^Digest:/ {print $2; exit}')
330+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
331+
echo "Resolved digest ${DIGEST} for ${DOCKER_TAG}"
332+
env:
333+
DOCKER_TAG: ${{ inputs.docker_tag }}
334+
CONTAINER_NAME: '${{ inputs.container_name }}'
335+
336+
- name: Attest combined image
337+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
338+
with:
339+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
340+
subject-digest: ${{ steps.resolve_combined_digest.outputs.digest }}
341+
push-to-registry: false
342+
- name: Summarise attested combined image
343+
run: |
344+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
345+
env:
346+
DOCKER_TAG: ${{ inputs.docker_tag }}
347+
CONTAINER_NAME: '${{ inputs.container_name }}'
348+
DIGEST: ${{ steps.resolve_combined_digest.outputs.digest }}
349+
350+
- name: Resolve combined github actions image digest
351+
id: resolve_githubactions_combined_digest
352+
run: |
353+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}" | awk '/^Digest:/ {print $2; exit}')
354+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
355+
echo "Resolved digest ${DIGEST} for githubactions-${DOCKER_TAG}"
356+
env:
357+
DOCKER_TAG: ${{ inputs.docker_tag }}
358+
CONTAINER_NAME: '${{ inputs.container_name }}'
359+
360+
- name: Attest combined github actions image
361+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
362+
with:
363+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
364+
subject-digest: ${{ steps.resolve_githubactions_combined_digest.outputs.digest }}
365+
push-to-registry: false
366+
- name: Summarise attested combined github actions image
367+
run: |
368+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
369+
env:
370+
DOCKER_TAG: ${{ inputs.docker_tag }}
371+
CONTAINER_NAME: '${{ inputs.container_name }}'
372+
DIGEST: ${{ steps.resolve_githubactions_combined_digest.outputs.digest }}
373+
374+
- name: Resolve latest github actions image digest
375+
if: ${{ inputs.tag_latest }}
376+
id: resolve_githubactions_latest_digest
377+
run: |
378+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest" | awk '/^Digest:/ {print $2; exit}')
379+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
380+
echo "Resolved digest ${DIGEST} for githubactions-latest"
381+
env:
382+
CONTAINER_NAME: '${{ inputs.container_name }}'
383+
384+
- name: Attest latest github actions image
385+
if: ${{ inputs.tag_latest }}
386+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
387+
with:
388+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
389+
subject-digest: ${{ steps.resolve_githubactions_latest_digest.outputs.digest }}
390+
push-to-registry: false
391+
- name: Summarise attested latest github actions image
392+
if: ${{ inputs.tag_latest }}
393+
run: |
394+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
395+
env:
396+
CONTAINER_NAME: '${{ inputs.container_name }}'
397+
DIGEST: ${{ steps.resolve_githubactions_latest_digest.outputs.digest }}
398+
399+
- name: Resolve latest image digest
400+
if: ${{ inputs.tag_latest }}
401+
id: resolve_latest_digest
402+
run: |
403+
DIGEST=$(docker buildx imagetools inspect "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest" | awk '/^Digest:/ {print $2; exit}')
404+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
405+
echo "Resolved digest ${DIGEST} for latest"
406+
env:
407+
CONTAINER_NAME: '${{ inputs.container_name }}'
408+
409+
- name: Attest latest image
410+
if: ${{ inputs.tag_latest }}
411+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a
412+
with:
413+
subject-name: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}
414+
subject-digest: ${{ steps.resolve_latest_digest.outputs.digest }}
415+
push-to-registry: false
416+
- name: Summarise attested latest image
417+
if: ${{ inputs.tag_latest }}
418+
run: |
419+
echo "## ATTESTED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"
420+
env:
421+
CONTAINER_NAME: '${{ inputs.container_name }}'
422+
DIGEST: ${{ steps.resolve_latest_digest.outputs.digest }}

.github/workflows/delete_old_images.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: "Delete old images"
44
on:
55
workflow_dispatch:
66
schedule:
7-
- cron: "0 1,13 * * *"
7+
- cron: "0 1 * * 6"
88
push:
99
branches: [main]
1010

@@ -30,7 +30,7 @@ jobs:
3030
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
3131
.github/scripts/delete_unused_images.sh --delete-pr
3232
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
33-
.github/scripts/delete_unused_images.sh --delete-ci
33+
.github/scripts/delete_unused_images.sh --delete-ci --delete-untagged
3434
else
3535
.github/scripts/delete_unused_images.sh
3636
fi

0 commit comments

Comments
 (0)