Use GitHub attestations #81
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release VaultEnv | |
| on: | |
| push: | |
| tags: | |
| - "vaultenv-[0-9]+.[0-9]+.[0-9]+**" | |
| jobs: | |
| release: | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| attestations: write | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.PUBLIC_REGISTRY_USERNAME }} | |
| password: ${{ secrets.PUBLIC_REGISTRY_PASSWORD }} | |
| - name: Setup Cosign | |
| uses: sigstore/cosign-installer@v3 | |
| - name: Setup ORAS | |
| uses: oras-project/setup-oras@v1 | |
| - name: Version | |
| run: make print-v-vaultenv | |
| - name: Release image | |
| run: | | |
| make \ | |
| DOCKER_INTERNAL_REG=${{ secrets.PRIVATE_REGISTRY }} \ | |
| DOCKER_INTERNAL_URL=${{ secrets.PRIVATE_REGISTRY_URL }} \ | |
| DOCKER_INTERNAL_USER=${{ secrets.PRIVATE_REGISTRY_USERNAME }} \ | |
| DOCKER_INTERNAL_PASSW=${{ secrets.PRIVATE_REGISTRY_PASSWORD }} \ | |
| DOCKER_RELEASE_USER=${{ secrets.PUBLIC_REGISTRY_USERNAME }} \ | |
| DOCKER_RELEASE_PASSW=${{ secrets.PUBLIC_REGISTRY_PASSWORD }} \ | |
| DOCKER_RELEASE_REG=${{ secrets.PUBLIC_REGISTRY }} \ | |
| DOCKER_RELEASE_TAG=${GITHUB_REF##*/} \ | |
| release-vaultenv | |
| - name: Resolve released image digest | |
| id: released-image | |
| env: | |
| RELEASE_REGISTRY: ${{ secrets.PUBLIC_REGISTRY }} | |
| run: | | |
| set -euo pipefail | |
| IMAGE_REF="${RELEASE_REGISTRY}/azure-keyvault-env:${GITHUB_REF_NAME#vaultenv-}" | |
| IMAGE_DIGEST="$(docker buildx imagetools inspect --format '{{json .Manifest}}' "$IMAGE_REF" | jq -r '.digest // empty')" | |
| if [ -z "$IMAGE_DIGEST" ]; then | |
| echo "Failed to resolve released image digest for $IMAGE_REF" >&2 | |
| exit 1 | |
| fi | |
| printf 'image-digest=%s\n' "$IMAGE_DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Attest released image provenance | |
| uses: actions/attest-build-provenance@v2 | |
| with: | |
| subject-name: ${{ secrets.PUBLIC_REGISTRY }}/azure-keyvault-env | |
| subject-digest: ${{ steps.released-image.outputs.image-digest }} | |
| push-to-registry: true | |
| - name: Sign released image and attestations | |
| env: | |
| RELEASE_REGISTRY: ${{ secrets.PUBLIC_REGISTRY }} | |
| run: | | |
| set -euo pipefail | |
| IMAGE_REF="${RELEASE_REGISTRY}/azure-keyvault-env:${GITHUB_REF_NAME#vaultenv-}" | |
| cosign sign --yes "$IMAGE_REF" | |
| docker buildx imagetools inspect --raw "$IMAGE_REF" \ | |
| | jq -r '.manifests[] | select(.platform.architecture == "unknown" and .platform.os == "unknown") | .digest' \ | |
| | while read -r digest; do | |
| cosign sign --yes "$IMAGE_REF@$digest" | |
| done | |
| - name: Extract SPDX SBOMs | |
| env: | |
| RELEASE_REGISTRY: ${{ secrets.PUBLIC_REGISTRY }} | |
| run: | | |
| set -euo pipefail | |
| IMAGE_REF="${RELEASE_REGISTRY}/azure-keyvault-env:${GITHUB_REF_NAME#vaultenv-}" | |
| IMAGE_REPO="${RELEASE_REGISTRY}/azure-keyvault-env" | |
| IMAGE_ORAS_REPO="docker.io/${RELEASE_REGISTRY#docker.io/}/azure-keyvault-env" | |
| VERSION="${GITHUB_REF_NAME#vaultenv-}" | |
| OUT_DIR="dist/sbom" | |
| TMP_DIR="$(mktemp -d)" | |
| mkdir -p "$OUT_DIR" | |
| trap 'rm -rf "$TMP_DIR"' EXIT | |
| docker buildx imagetools inspect --raw "$IMAGE_REF" > "$TMP_DIR/index.json" | |
| jq . "$TMP_DIR/index.json" | |
| jq -r '.manifests[] | select(.platform.os != "unknown" and .platform.architecture != "unknown") | "\(.platform.os) \(.platform.architecture) \(.digest)"' "$TMP_DIR/index.json" \ | |
| | while read -r os arch subject_digest; do | |
| echo "Resolving SBOM for platform ${os}/${arch} with subject digest ${subject_digest}" | |
| attestation_digest="$(jq -r --arg subject "$subject_digest" '.manifests[] | select(.platform.architecture == "unknown" and .platform.os == "unknown" and .annotations["vnd.docker.reference.type"] == "attestation-manifest" and .annotations["vnd.docker.reference.digest"] == $subject) | .digest' "$TMP_DIR/index.json")" | |
| if [ -z "$attestation_digest" ]; then | |
| echo "No attestation manifest found for subject digest ${subject_digest}" >&2 | |
| exit 1 | |
| fi | |
| echo "Found attestation digest ${attestation_digest}" | |
| docker buildx imagetools inspect --raw "$IMAGE_REPO@$attestation_digest" > "$TMP_DIR/attestation.json" | |
| jq . "$TMP_DIR/attestation.json" | |
| spdx_layer_digest="$(jq -r '.layers[] | select(.annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document") | .digest' "$TMP_DIR/attestation.json")" | |
| if [ -z "$spdx_layer_digest" ]; then | |
| echo "No SPDX layer found in attestation manifest ${attestation_digest}" >&2 | |
| exit 1 | |
| fi | |
| echo "Found SPDX layer digest ${spdx_layer_digest}" | |
| oras blob fetch --output - "$IMAGE_ORAS_REPO@$spdx_layer_digest" \ | |
| | jq '.predicate' \ | |
| > "$OUT_DIR/azure-keyvault-env_${VERSION}_${os}_${arch}.spdx.json" | |
| done | |
| - name: Publish GitHub Release | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| TMP_DIR="$(mktemp -d)" | |
| trap 'rm -rf "$TMP_DIR"' EXIT | |
| PREVIOUS_STABLE_TAG="$(git tag --list 'vaultenv-*' --sort=-version:refname | grep -E '^vaultenv-[0-9]+\.[0-9]+\.[0-9]+$' | grep -vx "$TAG" | head -n 1 || true)" | |
| PRERELEASE=false | |
| if [[ "$TAG" == *-alpha* ]] || [[ "$TAG" == *-beta* ]]; then | |
| PRERELEASE=true | |
| fi | |
| if gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| gh release delete "$TAG" --repo "${GITHUB_REPOSITORY}" --yes | |
| fi | |
| NOTES_JSON="$TMP_DIR/release-notes.json" | |
| if [ -n "$PREVIOUS_STABLE_TAG" ]; then | |
| gh api \ | |
| --method POST \ | |
| "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ | |
| -f tag_name="$TAG" \ | |
| -f target_commitish="$GITHUB_SHA" \ | |
| -f previous_tag_name="$PREVIOUS_STABLE_TAG" \ | |
| > "$NOTES_JSON" | |
| else | |
| gh api \ | |
| --method POST \ | |
| "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ | |
| -f tag_name="$TAG" \ | |
| -f target_commitish="$GITHUB_SHA" \ | |
| > "$NOTES_JSON" | |
| fi | |
| TITLE="$(jq -r '.name // empty' "$NOTES_JSON")" | |
| if [ -z "$TITLE" ]; then | |
| TITLE="$TAG" | |
| fi | |
| jq -r '.body' "$NOTES_JSON" > "$TMP_DIR/release-notes.md" | |
| ASSETS=(dist/sbom/*.spdx.json) | |
| CREATE_ARGS=("$TAG" "${ASSETS[@]}" "--repo" "${GITHUB_REPOSITORY}" "--verify-tag" "--title" "$TITLE" "--notes-file" "$TMP_DIR/release-notes.md") | |
| if [ "$PRERELEASE" = true ]; then | |
| CREATE_ARGS+=("--prerelease") | |
| fi | |
| gh release create "${CREATE_ARGS[@]}" |