@@ -122,7 +122,11 @@ jobs:
122
122
# Necessary to push docker images to ghcr.io.
123
123
packages : write
124
124
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
125
+ # Also necessary for keyless cosign (https://docs.sigstore.dev/cosign/signing/overview/)
126
+ # And for GitHub Actions attestation
125
127
id-token : write
128
+ # Required for GitHub Actions attestation
129
+ attestations : write
126
130
env :
127
131
# Necessary for Docker manifest
128
132
DOCKER_CLI_EXPERIMENTAL : " enabled"
@@ -246,6 +250,16 @@ jobs:
246
250
apple-codesign-0.22.0-x86_64-unknown-linux-musl/rcodesign
247
251
rm /tmp/rcodesign.tar.gz
248
252
253
+ - name : Install cosign
254
+ uses : sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
255
+ with :
256
+ cosign-release : " v2.4.3"
257
+
258
+ - name : Install syft
259
+ uses : anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
260
+ with :
261
+ syft-version : " v1.20.0"
262
+
249
263
- name : Setup Apple Developer certificate and API key
250
264
run : |
251
265
set -euo pipefail
@@ -361,6 +375,7 @@ jobs:
361
375
file : scripts/Dockerfile.base
362
376
platforms : linux/amd64,linux/arm64,linux/arm/v7
363
377
provenance : true
378
+ sbom : true
364
379
pull : true
365
380
no-cache : true
366
381
push : true
@@ -397,7 +412,52 @@ jobs:
397
412
echo "$manifests" | grep -q linux/arm64
398
413
echo "$manifests" | grep -q linux/arm/v7
399
414
415
+ # GitHub attestation provides SLSA provenance for Docker images, establishing a verifiable
416
+ # record that these images were built in GitHub Actions with specific inputs and environment.
417
+ # This complements our existing cosign attestations (which focus on SBOMs) by adding
418
+ # GitHub-specific build provenance to enhance our supply chain security.
419
+ #
420
+ # TODO: Consider refactoring these attestation steps to use a matrix strategy or composite action
421
+ # to reduce duplication while maintaining the required functionality for each distinct image tag.
422
+ - name : GitHub Attestation for Base Docker image
423
+ id : attest_base
424
+ if : ${{ !inputs.dry_run && steps.image-base-tag.outputs.tag != '' }}
425
+ continue-on-error : true
426
+ uses : actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
427
+ with :
428
+ subject-name : ${{ steps.image-base-tag.outputs.tag }}
429
+ predicate-type : " https://slsa.dev/provenance/v1"
430
+ predicate : |
431
+ {
432
+ "buildType": "https://github.com/actions/runner-images/",
433
+ "builder": {
434
+ "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
435
+ },
436
+ "invocation": {
437
+ "configSource": {
438
+ "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
439
+ "digest": {
440
+ "sha1": "${{ github.sha }}"
441
+ },
442
+ "entryPoint": ".github/workflows/release.yaml"
443
+ },
444
+ "environment": {
445
+ "github_workflow": "${{ github.workflow }}",
446
+ "github_run_id": "${{ github.run_id }}"
447
+ }
448
+ },
449
+ "metadata": {
450
+ "buildInvocationID": "${{ github.run_id }}",
451
+ "completeness": {
452
+ "environment": true,
453
+ "materials": true
454
+ }
455
+ }
456
+ }
457
+ push-to-registry : true
458
+
400
459
- name : Build Linux Docker images
460
+ id : build_docker
401
461
run : |
402
462
set -euxo pipefail
403
463
@@ -416,18 +476,125 @@ jobs:
416
476
# being pushed so will automatically push them.
417
477
make push/build/coder_"$version"_linux.tag
418
478
479
+ # Save multiarch image tag for attestation
480
+ multiarch_image="$(./scripts/image_tag.sh)"
481
+ echo "multiarch_image=${multiarch_image}" >> $GITHUB_OUTPUT
482
+
483
+ # For debugging, print all docker image tags
484
+ docker images
485
+
419
486
# if the current version is equal to the highest (according to semver)
420
487
# version in the repo, also create a multi-arch image as ":latest" and
421
488
# push it
489
+ created_latest_tag=false
422
490
if [[ "$(git tag | grep '^v' | grep -vE '(rc|dev|-|\+|\/)' | sort -r --version-sort | head -n1)" == "v$(./scripts/version.sh)" ]]; then
423
491
./scripts/build_docker_multiarch.sh \
424
492
--push \
425
493
--target "$(./scripts/image_tag.sh --version latest)" \
426
494
$(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag)
495
+ created_latest_tag=true
496
+ echo "created_latest_tag=true" >> $GITHUB_OUTPUT
497
+ else
498
+ echo "created_latest_tag=false" >> $GITHUB_OUTPUT
427
499
fi
428
500
env :
429
501
CODER_BASE_IMAGE_TAG : ${{ steps.image-base-tag.outputs.tag }}
430
502
503
+ - name : GitHub Attestation for Docker image
504
+ id : attest_main
505
+ if : ${{ !inputs.dry_run }}
506
+ continue-on-error : true
507
+ uses : actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
508
+ with :
509
+ subject-name : ${{ steps.build_docker.outputs.multiarch_image }}
510
+ predicate-type : " https://slsa.dev/provenance/v1"
511
+ predicate : |
512
+ {
513
+ "buildType": "https://github.com/actions/runner-images/",
514
+ "builder": {
515
+ "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
516
+ },
517
+ "invocation": {
518
+ "configSource": {
519
+ "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
520
+ "digest": {
521
+ "sha1": "${{ github.sha }}"
522
+ },
523
+ "entryPoint": ".github/workflows/release.yaml"
524
+ },
525
+ "environment": {
526
+ "github_workflow": "${{ github.workflow }}",
527
+ "github_run_id": "${{ github.run_id }}"
528
+ }
529
+ },
530
+ "metadata": {
531
+ "buildInvocationID": "${{ github.run_id }}",
532
+ "completeness": {
533
+ "environment": true,
534
+ "materials": true
535
+ }
536
+ }
537
+ }
538
+ push-to-registry : true
539
+
540
+ # Get the latest tag name for attestation
541
+ - name : Get latest tag name
542
+ id : latest_tag
543
+ if : ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }}
544
+ run : echo "tag=$(./scripts/image_tag.sh --version latest)" >> $GITHUB_OUTPUT
545
+
546
+ # If this is the highest version according to semver, also attest the "latest" tag
547
+ - name : GitHub Attestation for "latest" Docker image
548
+ id : attest_latest
549
+ if : ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }}
550
+ continue-on-error : true
551
+ uses : actions/attest@a63cfcc7d1aab266ee064c58250cfc2c7d07bc31 # v2.2.1
552
+ with :
553
+ subject-name : ${{ steps.latest_tag.outputs.tag }}
554
+ predicate-type : " https://slsa.dev/provenance/v1"
555
+ predicate : |
556
+ {
557
+ "buildType": "https://github.com/actions/runner-images/",
558
+ "builder": {
559
+ "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
560
+ },
561
+ "invocation": {
562
+ "configSource": {
563
+ "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
564
+ "digest": {
565
+ "sha1": "${{ github.sha }}"
566
+ },
567
+ "entryPoint": ".github/workflows/release.yaml"
568
+ },
569
+ "environment": {
570
+ "github_workflow": "${{ github.workflow }}",
571
+ "github_run_id": "${{ github.run_id }}"
572
+ }
573
+ },
574
+ "metadata": {
575
+ "buildInvocationID": "${{ github.run_id }}",
576
+ "completeness": {
577
+ "environment": true,
578
+ "materials": true
579
+ }
580
+ }
581
+ }
582
+ push-to-registry : true
583
+
584
+ # Report attestation failures but don't fail the workflow
585
+ - name : Check attestation status
586
+ if : ${{ !inputs.dry_run }}
587
+ run : |
588
+ if [[ "${{ steps.attest_base.outcome }}" == "failure" && "${{ steps.attest_base.conclusion }}" != "skipped" ]]; then
589
+ echo "::warning::GitHub attestation for base image failed"
590
+ fi
591
+ if [[ "${{ steps.attest_main.outcome }}" == "failure" ]]; then
592
+ echo "::warning::GitHub attestation for main image failed"
593
+ fi
594
+ if [[ "${{ steps.attest_latest.outcome }}" == "failure" && "${{ steps.attest_latest.conclusion }}" != "skipped" ]]; then
595
+ echo "::warning::GitHub attestation for latest image failed"
596
+ fi
597
+
431
598
- name : Generate offline docs
432
599
run : |
433
600
version="$(./scripts/version.sh)"
0 commit comments