mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
chore: enable SBOM attestations for docker images (#16894)
- Enable SBOM and provenance attestations in Docker builds - Installs `cosign` and `syft` in dogfood image - Adds [github attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) Signed-off-by: Thomas Kosiewski <tk@coder.com> --------- Signed-off-by: Thomas Kosiewski <tk@coder.com> Co-authored-by: Thomas Kosiewski <tk@coder.com>
This commit is contained in:
co-authored by
Thomas Kosiewski
parent
30179aeaac
commit
4987de654e
@@ -1024,7 +1024,11 @@ jobs:
|
||||
# Necessary to push docker images to ghcr.io.
|
||||
packages: write
|
||||
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
|
||||
# Also necessary for keyless cosign (https://docs.sigstore.dev/cosign/signing/overview/)
|
||||
# And for GitHub Actions attestation
|
||||
id-token: write
|
||||
# Required for GitHub Actions attestation
|
||||
attestations: write
|
||||
env:
|
||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||
outputs:
|
||||
@@ -1069,6 +1073,16 @@ jobs:
|
||||
- name: Install zstd
|
||||
run: sudo apt-get install -y zstd
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
with:
|
||||
cosign-release: "v2.4.3"
|
||||
|
||||
- name: Install syft
|
||||
uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
|
||||
with:
|
||||
syft-version: "v1.20.0"
|
||||
|
||||
- name: Setup Windows EV Signing Certificate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1170,6 +1184,138 @@ jobs:
|
||||
done
|
||||
fi
|
||||
|
||||
# GitHub attestation provides SLSA provenance for the Docker images, establishing a verifiable
|
||||
# record that these images were built in GitHub Actions with specific inputs and environment.
|
||||
# This complements our existing cosign attestations which focus on SBOMs.
|
||||
#
|
||||
# We attest each tag separately to ensure all tags have proper provenance records.
|
||||
# TODO: Consider refactoring these steps to use a matrix strategy or composite action to reduce duplication
|
||||
# while maintaining the required functionality for each tag.
|
||||
- name: GitHub Attestation for Docker image
|
||||
id: attest_main
|
||||
if: github.ref == 'refs/heads/main'
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: "ghcr.io/coder/coder-preview:main"
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/ci.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
- name: GitHub Attestation for Docker image (latest tag)
|
||||
id: attest_latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: "ghcr.io/coder/coder-preview:latest"
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/ci.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
- name: GitHub Attestation for version-specific Docker image
|
||||
id: attest_version
|
||||
if: github.ref == 'refs/heads/main'
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: "ghcr.io/coder/coder-preview:${{ steps.build-docker.outputs.tag }}"
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/ci.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
# Report attestation failures but don't fail the workflow
|
||||
- name: Check attestation status
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
if [[ "${{ steps.attest_main.outcome }}" == "failure" ]]; then
|
||||
echo "::warning::GitHub attestation for main tag failed"
|
||||
fi
|
||||
if [[ "${{ steps.attest_latest.outcome }}" == "failure" ]]; then
|
||||
echo "::warning::GitHub attestation for latest tag failed"
|
||||
fi
|
||||
if [[ "${{ steps.attest_version.outcome }}" == "failure" ]]; then
|
||||
echo "::warning::GitHub attestation for version-specific tag failed"
|
||||
fi
|
||||
|
||||
- name: Prune old images
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: vlaurin/action-ghcr-prune@0cf7d39f88546edd31965acba78cdcb0be14d641 # v0.6.0
|
||||
|
||||
@@ -122,7 +122,11 @@ jobs:
|
||||
# Necessary to push docker images to ghcr.io.
|
||||
packages: write
|
||||
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
|
||||
# Also necessary for keyless cosign (https://docs.sigstore.dev/cosign/signing/overview/)
|
||||
# And for GitHub Actions attestation
|
||||
id-token: write
|
||||
# Required for GitHub Actions attestation
|
||||
attestations: write
|
||||
env:
|
||||
# Necessary for Docker manifest
|
||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||
@@ -246,6 +250,16 @@ jobs:
|
||||
apple-codesign-0.22.0-x86_64-unknown-linux-musl/rcodesign
|
||||
rm /tmp/rcodesign.tar.gz
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
with:
|
||||
cosign-release: "v2.4.3"
|
||||
|
||||
- name: Install syft
|
||||
uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
|
||||
with:
|
||||
syft-version: "v1.20.0"
|
||||
|
||||
- name: Setup Apple Developer certificate and API key
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -361,6 +375,7 @@ jobs:
|
||||
file: scripts/Dockerfile.base
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
provenance: true
|
||||
sbom: true
|
||||
pull: true
|
||||
no-cache: true
|
||||
push: true
|
||||
@@ -397,7 +412,52 @@ jobs:
|
||||
echo "$manifests" | grep -q linux/arm64
|
||||
echo "$manifests" | grep -q linux/arm/v7
|
||||
|
||||
# GitHub attestation provides SLSA provenance for Docker images, establishing a verifiable
|
||||
# record that these images were built in GitHub Actions with specific inputs and environment.
|
||||
# This complements our existing cosign attestations (which focus on SBOMs) by adding
|
||||
# GitHub-specific build provenance to enhance our supply chain security.
|
||||
#
|
||||
# TODO: Consider refactoring these attestation steps to use a matrix strategy or composite action
|
||||
# to reduce duplication while maintaining the required functionality for each distinct image tag.
|
||||
- name: GitHub Attestation for Base Docker image
|
||||
id: attest_base
|
||||
if: ${{ !inputs.dry_run && steps.image-base-tag.outputs.tag != '' }}
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: ${{ steps.image-base-tag.outputs.tag }}
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/release.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Build Linux Docker images
|
||||
id: build_docker
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
|
||||
@@ -416,18 +476,125 @@ jobs:
|
||||
# being pushed so will automatically push them.
|
||||
make push/build/coder_"$version"_linux.tag
|
||||
|
||||
# Save multiarch image tag for attestation
|
||||
multiarch_image="$(./scripts/image_tag.sh)"
|
||||
echo "multiarch_image=${multiarch_image}" >> $GITHUB_OUTPUT
|
||||
|
||||
# For debugging, print all docker image tags
|
||||
docker images
|
||||
|
||||
# if the current version is equal to the highest (according to semver)
|
||||
# version in the repo, also create a multi-arch image as ":latest" and
|
||||
# push it
|
||||
created_latest_tag=false
|
||||
if [[ "$(git tag | grep '^v' | grep -vE '(rc|dev|-|\+|\/)' | sort -r --version-sort | head -n1)" == "v$(./scripts/version.sh)" ]]; then
|
||||
./scripts/build_docker_multiarch.sh \
|
||||
--push \
|
||||
--target "$(./scripts/image_tag.sh --version latest)" \
|
||||
$(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag)
|
||||
created_latest_tag=true
|
||||
echo "created_latest_tag=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "created_latest_tag=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
CODER_BASE_IMAGE_TAG: ${{ steps.image-base-tag.outputs.tag }}
|
||||
|
||||
- name: GitHub Attestation for Docker image
|
||||
id: attest_main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: ${{ steps.build_docker.outputs.multiarch_image }}
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/release.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
# Get the latest tag name for attestation
|
||||
- name: Get latest tag name
|
||||
id: latest_tag
|
||||
if: ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }}
|
||||
run: echo "tag=$(./scripts/image_tag.sh --version latest)" >> $GITHUB_OUTPUT
|
||||
|
||||
# If this is the highest version according to semver, also attest the "latest" tag
|
||||
- name: GitHub Attestation for "latest" Docker image
|
||||
id: attest_latest
|
||||
if: ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }}
|
||||
continue-on-error: true
|
||||
uses: actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
|
||||
with:
|
||||
subject-name: ${{ steps.latest_tag.outputs.tag }}
|
||||
predicate-type: "https://slsa.dev/provenance/v1"
|
||||
predicate: |
|
||||
{
|
||||
"buildType": "https://github.com/actions/runner-images/",
|
||||
"builder": {
|
||||
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
},
|
||||
"invocation": {
|
||||
"configSource": {
|
||||
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
|
||||
"digest": {
|
||||
"sha1": "${{ github.sha }}"
|
||||
},
|
||||
"entryPoint": ".github/workflows/release.yaml"
|
||||
},
|
||||
"environment": {
|
||||
"github_workflow": "${{ github.workflow }}",
|
||||
"github_run_id": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "${{ github.run_id }}",
|
||||
"completeness": {
|
||||
"environment": true,
|
||||
"materials": true
|
||||
}
|
||||
}
|
||||
}
|
||||
push-to-registry: true
|
||||
|
||||
# Report attestation failures but don't fail the workflow
|
||||
- name: Check attestation status
|
||||
if: ${{ !inputs.dry_run }}
|
||||
run: |
|
||||
if [[ "${{ steps.attest_base.outcome }}" == "failure" && "${{ steps.attest_base.conclusion }}" != "skipped" ]]; then
|
||||
echo "::warning::GitHub attestation for base image failed"
|
||||
fi
|
||||
if [[ "${{ steps.attest_main.outcome }}" == "failure" ]]; then
|
||||
echo "::warning::GitHub attestation for main image failed"
|
||||
fi
|
||||
if [[ "${{ steps.attest_latest.outcome }}" == "failure" && "${{ steps.attest_latest.conclusion }}" != "skipped" ]]; then
|
||||
echo "::warning::GitHub attestation for latest image failed"
|
||||
fi
|
||||
|
||||
- name: Generate offline docs
|
||||
run: |
|
||||
version="$(./scripts/version.sh)"
|
||||
|
||||
Reference in New Issue
Block a user