Skip to content

Commit 3552e35

Browse files
committed
verify attestation
1 parent c27ddbb commit 3552e35

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

.github/workflows/pull_request.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ jobs:
3737
echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE"
3838
echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION"
3939
} >> "$GITHUB_OUTPUT"
40+
verify_attestation:
41+
needs: get_config_values
42+
uses: ./.github/workflows/verify_attestation.yml
43+
with:
44+
runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}"
4045
quality_checks:
4146
uses: ./.github/workflows/quality-checks-devcontainer.yml
4247
needs: [get_config_values]
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: Verify image digest and attestation
2+
"on":
3+
workflow_call:
4+
inputs:
5+
runtime_docker_image:
6+
required: true
7+
type: string
8+
description: Image reference as name:tag (for example node_24_python_3_12:v1.2.3) or fully qualified image ref
9+
registry:
10+
required: false
11+
type: string
12+
default: ghcr.io
13+
namespace:
14+
required: false
15+
type: string
16+
default: nhsdigital/eps-devcontainers
17+
owner:
18+
required: false
19+
type: string
20+
default: NHSDigital
21+
signer_workflow:
22+
required: false
23+
type: string
24+
default: ""
25+
signer_repo:
26+
required: false
27+
type: string
28+
default: ""
29+
source_ref:
30+
required: false
31+
type: string
32+
default: ""
33+
predicate_type:
34+
required: false
35+
type: string
36+
default: https://slsa.dev/provenance/v1
37+
bundle_from_oci:
38+
required: false
39+
type: boolean
40+
default: false
41+
outputs:
42+
pinned_image:
43+
description: Fully-qualified digest-pinned image reference
44+
value: ${{ jobs.verify.outputs.pinned_image }}
45+
resolved_digest:
46+
description: Resolved digest for the supplied image reference
47+
value: ${{ jobs.verify.outputs.resolved_digest }}
48+
49+
jobs:
50+
verify:
51+
runs-on: ubuntu-22.04
52+
permissions:
53+
contents: read
54+
packages: read
55+
attestations: read
56+
outputs:
57+
pinned_image: ${{ steps.resolve.outputs.pinned_image }}
58+
resolved_digest: ${{ steps.resolve.outputs.resolved_digest }}
59+
steps:
60+
- name: Login to github container registry
61+
if: startsWith(inputs.registry, 'ghcr.io')
62+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
63+
with:
64+
registry: ghcr.io
65+
username: ${{ github.actor }}
66+
password: ${{ secrets.GITHUB_TOKEN }}
67+
68+
- name: Resolve digest and validate expected value
69+
id: resolve
70+
shell: bash
71+
env:
72+
RUNTIME_DOCKER_IMAGE: ${{ inputs.runtime_docker_image }}
73+
REGISTRY: ${{ inputs.registry }}
74+
NAMESPACE: ${{ inputs.namespace }}
75+
run: |
76+
set -euo pipefail
77+
78+
if [[ "$RUNTIME_DOCKER_IMAGE" == *"/"* ]]; then
79+
IMAGE_REF="$RUNTIME_DOCKER_IMAGE"
80+
else
81+
IMAGE_REF="${REGISTRY}/${NAMESPACE}/${RUNTIME_DOCKER_IMAGE}"
82+
fi
83+
84+
if [[ "$IMAGE_REF" == *@sha256:* ]]; then
85+
IMAGE_BASE="${IMAGE_REF%@*}"
86+
RESOLVED_DIGEST="${IMAGE_REF#*@}"
87+
else
88+
RESOLVED_DIGEST="$(docker buildx imagetools inspect "$IMAGE_REF" | awk '/^Digest:/ {print $2; exit}')"
89+
IMAGE_BASE="${IMAGE_REF%:*}"
90+
fi
91+
92+
if [[ -z "$RESOLVED_DIGEST" ]]; then
93+
echo "Could not resolve digest for image: $IMAGE_REF" >&2
94+
exit 1
95+
fi
96+
97+
PINNED_IMAGE="${IMAGE_BASE}@${RESOLVED_DIGEST}"
98+
echo "resolved_digest=${RESOLVED_DIGEST}" >> "$GITHUB_OUTPUT"
99+
echo "pinned_image=${PINNED_IMAGE}" >> "$GITHUB_OUTPUT"
100+
echo "Resolved and matched digest for ${IMAGE_REF}: ${RESOLVED_DIGEST}"
101+
102+
- name: Verify attestation
103+
shell: bash
104+
env:
105+
GH_TOKEN: ${{ github.token }}
106+
OWNER: ${{ inputs.owner }}
107+
SIGNER_WORKFLOW: ${{ inputs.signer_workflow }}
108+
SIGNER_REPO: ${{ inputs.signer_repo }}
109+
SOURCE_REF: ${{ inputs.source_ref }}
110+
PREDICATE_TYPE: ${{ inputs.predicate_type }}
111+
BUNDLE_FROM_OCI: ${{ inputs.bundle_from_oci }}
112+
PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }}
113+
run: |
114+
set -euo pipefail
115+
116+
args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE")
117+
118+
if [[ -n "$SIGNER_WORKFLOW" ]]; then
119+
args+=("--signer-workflow" "$SIGNER_WORKFLOW")
120+
fi
121+
122+
if [[ -n "$SIGNER_REPO" ]]; then
123+
args+=("--signer-repo" "$SIGNER_REPO")
124+
fi
125+
126+
if [[ -n "$SOURCE_REF" ]]; then
127+
args+=("--source-ref" "$SOURCE_REF")
128+
fi
129+
130+
if [[ "$BUNDLE_FROM_OCI" == "true" ]]; then
131+
args+=("--bundle-from-oci")
132+
fi
133+
134+
gh attestation verify "${args[@]}"
135+
echo "Verified attestation for ${PINNED_IMAGE}"

0 commit comments

Comments
 (0)