diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 87dd9ed..09b0ed7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -68,6 +68,9 @@ jobs: release: name: release needs: [build] + outputs: + container_digest: ${{ steps.container_info.outputs.container_digest }} + container_tags: ${{ steps.container_info.outputs.container_tags }} runs-on: ubuntu-20.04 @@ -87,6 +90,9 @@ jobs: with: cosign-release: 'v1.6.0' + - name: Install Syft + uses: anchore/sbom-action/download-syft@v0.7.0 + - name: Checkout uses: actions/checkout@v3 with: @@ -126,6 +132,149 @@ jobs: GIT_HASH: ${{ steps.release-vars.outputs.GIT_HASH }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + - name: Get container info + id: container_info + if: startsWith(github.ref, 'refs/tags/') + run: | + export CONTAINER_DIGEST=$(make container-digest GITHUB_REF=${{ github.ref_name }}) + echo "::set-output name=container_digest::$CONTAINER_DIGEST" + echo "::set-output name=container_tags::$(make container-tags CONTAINER_DIGEST="${CONTAINER_DIGEST}" | paste -s -d ',' -)" + - name: Cleanup signing keys if: ${{ always() }} run: rm -f cosign.key + + sbom: + name: sbom + needs: [release] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + env: + TAGS: "${{ needs.release.outputs.container_tags }}" + + steps: + - name: Install cosign + uses: sigstore/cosign-installer@v2.1.0 + with: + cosign-release: 'v1.6.0' + + - name: Install Syft + uses: anchore/sbom-action/download-syft@v0.7.0 + + - name: Login to ghcr.io + uses: docker/login-action@v1.14.1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Attach SBOM + env: + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + REPO: ghcr.io/philips-labs/fatt + run: | + echo '${{ secrets.COSIGN_PUBLIC_KEY }}' > cosign.pub + echo '${{ secrets.COSIGN_PRIVATE_KEY }}' > cosign.key + IFS=, + for t in ${TAGS}; do + cosign verify --key cosign.pub ${REPO}:${t} + syft ${REPO}:${t} -o spdx-json > sbom-spdx.json + cosign attach sbom --sbom sbom-spdx.json --type spdx ${REPO}:${t} + cosign attest --predicate sbom-spdx.json --type spdx --key cosign.key ${REPO}:${t} + cosign verify-attestation -o verified-sbom-spdx.json --key cosign.pub ${REPO}:${t} + done + + - name: Clean up signing keys + if: ${{ always() }} + run: | + rm -f cosign.key + + provenance: + name: provenance + needs: [release] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + + steps: + - name: Generate provenance for Release + uses: philips-labs/slsa-provenance-action@v0.7.2 + with: + command: generate + subcommand: github-release + arguments: --artifact-path release-assets --output-path provenance.att --tag-name ${{ github.ref_name }} + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Install cosign + uses: sigstore/cosign-installer@v2.1.0 + with: + cosign-release: 'v1.6.0' + + - name: Sign provenance + run: | + echo '${{ secrets.COSIGN_PRIVATE_KEY }}' > cosign.key + cosign sign-blob --key cosign.key --output-signature "${SIGNATURE}" provenance.att + cat "${SIGNATURE}" + curl_args=(-s -H "Authorization: token ${GITHUB_TOKEN}") + curl_args+=(-H "Accept: application/vnd.github.v3+json") + release_id="$(curl "${curl_args[@]}" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases?per_page=10" | jq "map(select(.name == \"${GITHUB_REF_NAME}\"))" | jq -r '.[0].id')" + echo "Upload ${SIGNATURE} to release with id ${release_id}…" + curl_args+=(-H "Content-Type: $(file -b --mime-type "${SIGNATURE}")") + curl "${curl_args[@]}" \ + --data-binary @"${SIGNATURE}" \ + "https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets?name=${SIGNATURE}" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + SIGNATURE: provenance.att.sig + + container-provenance: + name: container-provenance + needs: [release] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + env: + REPO: ghcr.io/philips-labs/fatt + + steps: + - name: Install cosign + uses: sigstore/cosign-installer@v2.1.0 + with: + cosign-release: 'v1.6.0' + + - name: Generate provenance for ${REPO} + uses: philips-labs/slsa-provenance-action@v0.7.2 + with: + command: generate + subcommand: container + arguments: --repository ${REPO} --output-path provenance.att --digest ${{ needs.release.outputs.container_digest }} --tags ${{ needs.release.outputs.container_tags }} + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Get slsa-provenance predicate + run: | + cat provenance.att | jq .predicate > provenance-predicate.att + + - name: Login to ghcr.io + uses: docker/login-action@v1.14.1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Attach provenance to image + run: | + echo '${{ secrets.COSIGN_PRIVATE_KEY }}' > cosign.key + cosign attest --predicate provenance-predicate.att --type slsaprovenance --key cosign.key ${REPO}@${{ needs.release.outputs.container_digest }} + env: + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + + - name: Verify attestation + run: | + echo '${{ secrets.COSIGN_PUBLIC_KEY }}' > cosign.pub + cosign verify-attestation --key cosign.pub ${REPO}@${{ needs.release.outputs.container_digest }} + + - name: Cleanup signing keys + if: ${{ always() }} + run: | + rm -f cosign.key diff --git a/.goreleaser.yml b/.goreleaser.yml index ed9cef4..85b6354 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -36,6 +36,14 @@ archives: - goos: windows format: zip +sboms: + - id: archive-sbom + cmd: syft + args: ["${artifact}", "--file", "${artifact}.sbom.json", "--output", "spdx-json"] + documents: + - "${artifact}.sbom.json" + artifacts: archive + checksum: name_template: 'checksums.txt' @@ -100,6 +108,18 @@ signs: - '--output-certificate=${certificate}' - '--output-signature=${signature}' - '${artifact}' + - id: sboms + cmd: cosign + stdin: '{{ .Env.COSIGN_PASSWORD }}' + output: true + artifacts: sbom + args: + - sign-blob + - --key + - cosign.key + - '--output-certificate=${certificate}' + - '--output-signature=${signature}' + - '${artifact}' docker_signs: - cmd: cosign diff --git a/Makefile b/Makefile index f437501..18ccb8d 100644 --- a/Makefile +++ b/Makefile @@ -96,3 +96,13 @@ release: $(GO_PATH)/bin/goreleaser ## creates a release using goreleaser .PHONY: release-vars release-vars: ## print the release variables for goreleaser @echo export LDFLAGS=\"$(LDFLAGS)\" + +.PHONY: container-digest +container-digest: ## retrieves the container digest from the given tag + @:$(call check_defined, GITHUB_REF) + @docker inspect $(GHCR_REPO):$(subst refs/tags/,,$(GITHUB_REF)) --format '{{ index .RepoDigests 0 }}' | cut -d '@' -f 2 + +.PHONY: container-tags +container-tags: ## retrieves the container tags applied to the image with a given digest + @:$(call check_defined, CONTAINER_DIGEST) + @docker inspect ghcr.io/philips-labs/fatt@$(CONTAINER_DIGEST) --format '{{ join .RepoTags "\n" }}' | sed 's/.*://' | awk '!_[$$0]++'