action.yaml 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. name: 'Sign Container Image'
  2. description: >-
  3. Sign a container image and attest SLSA v1 build provenance using cosign
  4. keyless (Sigstore) with GitHub Actions OIDC. Callers must check out the
  5. source tree (e.g. `actions/checkout`) before invoking this action — the
  6. built commit is resolved with `git rev-parse HEAD` so the attestation
  7. records the revision that was actually built rather than `github.sha`,
  8. which on a tag push reflects the tag-pointed commit and may differ from
  9. the branch tip the release workflow checks out. The calling job must
  10. have `id-token: write`.
  11. inputs:
  12. image:
  13. description: 'Full image reference (registry/repo:tag) to sign by digest.'
  14. required: true
  15. workflow-path:
  16. description: 'Path to the workflow file (repo-relative) that triggered the build.'
  17. required: true
  18. run-started-at:
  19. description: >-
  20. ISO-8601 workflow run start time, typically `${{ github.run_started_at }}`
  21. from the caller. Recorded as `runDetails.metadata.startedOn` in the
  22. SLSA provenance predicate.
  23. required: true
  24. runs:
  25. using: "composite"
  26. steps:
  27. - name: Install cosign
  28. uses: sigstore/cosign-installer@v3
  29. - name: Install crane
  30. uses: imjasonh/setup-crane@v0.5
  31. - name: Resolve image digest
  32. id: digest
  33. shell: bash
  34. env:
  35. IMAGE: ${{ inputs.image }}
  36. run: |
  37. set -euo pipefail
  38. DIGEST="$(crane digest "${IMAGE}")"
  39. REPO="${IMAGE%:*}"
  40. REF="${REPO}@${DIGEST}"
  41. echo "Resolved ${IMAGE} -> ${REF}"
  42. echo "REF=${REF}" >> "$GITHUB_OUTPUT"
  43. echo "DIGEST=${DIGEST}" >> "$GITHUB_OUTPUT"
  44. - name: Sign image with cosign (keyless)
  45. shell: bash
  46. env:
  47. REF: ${{ steps.digest.outputs.REF }}
  48. run: |
  49. set -euo pipefail
  50. cosign sign --yes "${REF}"
  51. - name: Generate SLSA v1 provenance predicate
  52. shell: bash
  53. env:
  54. WORKFLOW_PATH: ${{ inputs.workflow-path }}
  55. STARTED_ON: ${{ inputs.run-started-at }}
  56. run: |
  57. set -euo pipefail
  58. RESOLVED_GIT_COMMIT="$(git rev-parse HEAD)"
  59. export RESOLVED_GIT_COMMIT
  60. python3 - <<'PY' > predicate.json
  61. import json
  62. import os
  63. repo = os.environ["GITHUB_REPOSITORY"]
  64. commit = os.environ["RESOLVED_GIT_COMMIT"]
  65. run_id = os.environ["GITHUB_RUN_ID"]
  66. run_attempt = os.environ["GITHUB_RUN_ATTEMPT"]
  67. predicate = {
  68. "buildDefinition": {
  69. "buildType": "https://github.com/opencost/opencost/build/workflow@v1",
  70. "externalParameters": {
  71. "workflow": {
  72. "ref": os.environ["GITHUB_REF"],
  73. "repository": f"https://github.com/{repo}",
  74. "path": os.environ["WORKFLOW_PATH"],
  75. }
  76. },
  77. "internalParameters": {},
  78. "resolvedDependencies": [
  79. {
  80. "uri": f"git+https://github.com/{repo}@{commit}",
  81. "digest": {"gitCommit": commit},
  82. }
  83. ],
  84. },
  85. "runDetails": {
  86. "builder": {
  87. "id": f"https://github.com/{repo}/actions/runs/{run_id}",
  88. },
  89. "metadata": {
  90. "invocationId": f"https://github.com/{repo}/actions/runs/{run_id}/attempts/{run_attempt}",
  91. "startedOn": os.environ["STARTED_ON"],
  92. },
  93. },
  94. }
  95. print(json.dumps(predicate, indent=2))
  96. PY
  97. - name: Attest SLSA provenance with cosign
  98. shell: bash
  99. env:
  100. REF: ${{ steps.digest.outputs.REF }}
  101. run: |
  102. set -euo pipefail
  103. cosign attest --yes \
  104. --predicate predicate.json \
  105. --type slsaprovenance1 \
  106. "${REF}"