action.yml 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. name: 'Provenance / SBOM / Sign'
  2. description: 'Creates SBOM & provenance files and signs the image'
  3. inputs:
  4. image-name:
  5. description: "name of the image"
  6. required: true
  7. default: ''
  8. image-tag:
  9. description: "image tag"
  10. required: true
  11. default: ""
  12. runs:
  13. using: "composite"
  14. steps:
  15. - name: Install cosign
  16. # https://github.com/sigstore/cosign-installer/releases/tag/v4.0.0
  17. uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
  18. with:
  19. cosign-release: 'v3.0.2'
  20. - name: Install Syft
  21. # https://github.com/anchore/sbom-action/releases/tag/v0.22.2
  22. uses: anchore/sbom-action/download-syft@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
  23. with:
  24. syft-version: v1.41.2
  25. - name: Check Cosign install
  26. shell: bash
  27. run: cosign version
  28. - name: Login to ghcr.io
  29. uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
  30. with:
  31. registry: ghcr.io
  32. username: ${{ github.actor }}
  33. password: ${{ github.token }}
  34. - name: Setup Go
  35. uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
  36. with:
  37. go-version-file: go.mod
  38. - name: Install crane
  39. shell: bash
  40. run: |
  41. make crane
  42. echo "${GITHUB_WORKSPACE}/bin" >> "${GITHUB_PATH}"
  43. - name: Get docker image tag
  44. id: container_info
  45. shell: bash
  46. env:
  47. IMAGE_NAME: ${{ inputs.image-name }}
  48. IMAGE_TAG: ${{ inputs.image-tag }}
  49. run: |
  50. echo "::group::Crane digest lookup"
  51. echo "Looking up digest for ${IMAGE_NAME}:${IMAGE_TAG}"
  52. DIGEST=$(crane digest ${IMAGE_NAME}:${IMAGE_TAG})
  53. echo "Found digest: ${DIGEST}"
  54. echo "digest=${DIGEST}" >> $GITHUB_OUTPUT
  55. echo "::endgroup::"
  56. - name: Sign image
  57. shell: bash
  58. env:
  59. IMAGE_NAME: ${{ inputs.image-name }}
  60. CONTAINER_DIGEST: ${{ steps.container_info.outputs.digest }}
  61. GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }}
  62. run: |
  63. echo "::group::Cosign sign"
  64. echo "Signing ${IMAGE_NAME}@${CONTAINER_DIGEST}"
  65. cosign sign --yes --new-bundle-format=false --use-signing-config=false -a GITHUB_ACTOR=${GITHUB_TRIGGERING_ACTOR} "${IMAGE_NAME}@${CONTAINER_DIGEST}"
  66. echo "::endgroup::"
  67. - name: Attach SBOM to image
  68. shell: bash
  69. id: sbom
  70. env:
  71. IMAGE_NAME: ${{ inputs.image-name }}
  72. IMAGE_TAG: ${{ inputs.image-tag }}
  73. CONTAINER_DIGEST: ${{ steps.container_info.outputs.digest }}
  74. run: |
  75. echo "::group::Image SBOM generation"
  76. # Image SBOM (OS + application libs contained in the image)
  77. echo "Generating image SBOM for ${IMAGE_NAME}@${CONTAINER_DIGEST}"
  78. syft "${IMAGE_NAME}@${CONTAINER_DIGEST}" -o spdx-json=sbom.${IMAGE_TAG}.spdx.json
  79. ORIGINAL_IMAGE_SBOM_SIZE="$(wc -c < sbom.${IMAGE_TAG}.spdx.json)"
  80. echo "Original image SBOM size: ${ORIGINAL_IMAGE_SBOM_SIZE} bytes"
  81. MAX_SBOM_SIZE_BYTES=10000000
  82. echo "Deduplicating image SPDX package nodes and relationships"
  83. bash ./hack/dedupe-spdx-gomod.sh \
  84. --input sbom.${IMAGE_TAG}.spdx.json \
  85. --output sbom.${IMAGE_TAG}.dedup.spdx.json
  86. DEDUP_IMAGE_SBOM_SIZE="$(wc -c < sbom.${IMAGE_TAG}.dedup.spdx.json)"
  87. echo "Deduplicated image SBOM size: ${DEDUP_IMAGE_SBOM_SIZE} bytes"
  88. if [[ "${DEDUP_IMAGE_SBOM_SIZE}" -gt "${MAX_SBOM_SIZE_BYTES}" ]]; then
  89. echo "Deduped image SBOM still above ${MAX_SBOM_SIZE_BYTES} bytes, dropping file ownership data"
  90. bash ./hack/dedupe-spdx-gomod.sh \
  91. --input sbom.${IMAGE_TAG}.spdx.json \
  92. --output sbom.${IMAGE_TAG}.dedup.spdx.json \
  93. --drop-file-ownership
  94. DEDUP_IMAGE_SBOM_SIZE="$(wc -c < sbom.${IMAGE_TAG}.dedup.spdx.json)"
  95. echo "Ownership-pruned deduplicated image SBOM size: ${DEDUP_IMAGE_SBOM_SIZE} bytes"
  96. fi
  97. if [[ "${DEDUP_IMAGE_SBOM_SIZE}" -gt "${MAX_SBOM_SIZE_BYTES}" ]]; then
  98. echo "Image SBOM predicate is still too large (${DEDUP_IMAGE_SBOM_SIZE} bytes)."
  99. echo "Refusing attestation to avoid Rekor submission retries/failure."
  100. exit 1
  101. fi
  102. echo "::endgroup::"
  103. echo "::group::Attest image SBOM"
  104. cosign attest --yes --new-bundle-format=false --use-signing-config=false --predicate sbom.${IMAGE_TAG}.dedup.spdx.json --type spdx "${IMAGE_NAME}@${CONTAINER_DIGEST}"
  105. echo "::endgroup::"
  106. echo "::group::Verify image SBOM attestation"
  107. echo "Using certificate-identity-regexp: https://github.com/$GITHUB_REPOSITORY/.*"
  108. cosign verify-attestation --type spdx ${IMAGE_NAME}@${CONTAINER_DIGEST} \
  109. --certificate-identity-regexp "https://github.com/$GITHUB_REPOSITORY/.*" \
  110. --certificate-oidc-issuer https://token.actions.githubusercontent.com | jq '.payload |= @base64d | .payload | fromjson'
  111. echo "::endgroup::"
  112. echo "::group::Go modules SBOM generation"
  113. # Go modules SBOM (dependencies from the source tree)
  114. # Requires repository to be checked out before this composite action runs.
  115. syft dir:. -o spdx-json=sbom.gomod.${IMAGE_TAG}.spdx.json
  116. ORIGINAL_GOMOD_SBOM_SIZE="$(wc -c < sbom.gomod.${IMAGE_TAG}.spdx.json)"
  117. echo "Original Go modules SBOM size: ${ORIGINAL_GOMOD_SBOM_SIZE} bytes"
  118. echo "Deduplicating Go modules SPDX package nodes and relationships"
  119. bash ./hack/dedupe-spdx-gomod.sh \
  120. --input sbom.gomod.${IMAGE_TAG}.spdx.json \
  121. --output sbom.gomod.${IMAGE_TAG}.dedup.spdx.json
  122. DEDUP_GOMOD_SBOM_SIZE="$(wc -c < sbom.gomod.${IMAGE_TAG}.dedup.spdx.json)"
  123. echo "Deduplicated Go modules SBOM size: ${DEDUP_GOMOD_SBOM_SIZE} bytes"
  124. # Rekor requests can fail when predicates are too large. If the deduped
  125. # SBOM is still big, drop file ownership-heavy data and re-check size.
  126. if [[ "${DEDUP_GOMOD_SBOM_SIZE}" -gt "${MAX_SBOM_SIZE_BYTES}" ]]; then
  127. echo "Deduped SBOM still above ${MAX_SBOM_SIZE_BYTES} bytes, dropping file ownership data"
  128. bash ./hack/dedupe-spdx-gomod.sh \
  129. --input sbom.gomod.${IMAGE_TAG}.spdx.json \
  130. --output sbom.gomod.${IMAGE_TAG}.dedup.spdx.json \
  131. --drop-file-ownership
  132. DEDUP_GOMOD_SBOM_SIZE="$(wc -c < sbom.gomod.${IMAGE_TAG}.dedup.spdx.json)"
  133. echo "Ownership-pruned deduplicated Go modules SBOM size: ${DEDUP_GOMOD_SBOM_SIZE} bytes"
  134. fi
  135. if [[ "${DEDUP_GOMOD_SBOM_SIZE}" -gt "${MAX_SBOM_SIZE_BYTES}" ]]; then
  136. echo "Go modules SBOM predicate is still too large (${DEDUP_GOMOD_SBOM_SIZE} bytes)."
  137. echo "Refusing attestation to avoid Rekor submission retries/failure."
  138. exit 1
  139. fi
  140. echo "::endgroup::"
  141. echo "::group::Attest Go modules SBOM"
  142. cosign attest --yes --new-bundle-format=false --use-signing-config=false --predicate sbom.gomod.${IMAGE_TAG}.dedup.spdx.json --type spdx "${IMAGE_NAME}@${CONTAINER_DIGEST}"
  143. echo "::endgroup::"
  144. echo "::group::Verify Go modules SBOM attestation"
  145. cosign verify-attestation --type spdx ${IMAGE_NAME}@${CONTAINER_DIGEST} \
  146. --certificate-identity-regexp "https://github.com/$GITHUB_REPOSITORY/.*" \
  147. --certificate-oidc-issuer https://token.actions.githubusercontent.com | jq ' .payload |= @base64d | .payload | fromjson | .subject'
  148. echo "::endgroup::"
  149. - name: Generate provenance
  150. shell: bash
  151. env:
  152. IMAGE_NAME: ${{ inputs.image-name }}
  153. IMAGE_TAG: ${{ inputs.image-tag }}
  154. CONTAINER_DIGEST: ${{ steps.container_info.outputs.digest }}
  155. run: |
  156. echo "::group::Generate provenance"
  157. ./hack/generate-provenance.sh \
  158. --repository "${IMAGE_NAME}" \
  159. --digest "${CONTAINER_DIGEST}" \
  160. --tags "${IMAGE_TAG}" \
  161. --output-path "provenance.${IMAGE_TAG}.intoto.jsonl"
  162. echo "::endgroup::"
  163. - name: Attach provenance
  164. shell: bash
  165. id: provenance
  166. env:
  167. IMAGE_NAME: ${{ inputs.image-name }}
  168. IMAGE_TAG: ${{ inputs.image-tag }}
  169. CONTAINER_DIGEST: ${{ steps.container_info.outputs.digest }}
  170. run: |
  171. echo "::group::Prepare provenance predicate"
  172. jq '.predicate' provenance.${IMAGE_TAG}.intoto.jsonl > provenance-predicate.att
  173. echo "::endgroup::"
  174. echo "::group::Attest provenance"
  175. cosign attest --yes --new-bundle-format=false --use-signing-config=false --predicate provenance-predicate.att --type slsaprovenance "${IMAGE_NAME}@${CONTAINER_DIGEST}"
  176. echo "::endgroup::"
  177. echo "::group::Verify provenance attestation"
  178. cosign verify-attestation --type slsaprovenance ${IMAGE_NAME}@${CONTAINER_DIGEST} \
  179. --certificate-identity-regexp "https://github.com/$GITHUB_REPOSITORY/.*" \
  180. --certificate-oidc-issuer https://token.actions.githubusercontent.com
  181. echo "::endgroup::"