From 7bd397290bc850b91bfba6bfd64622fb506ebdac Mon Sep 17 00:00:00 2001 From: Darby Frey Date: Mon, 5 May 2025 07:16:41 -0500 Subject: [PATCH 1/2] SLSA provenance POC --- app/models/ci/build.rb | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 150c5255fbd64d..da1c89edfe0cc1 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1202,6 +1202,56 @@ def token super end + def to_slsa + return false unless runner_manager_build + + { + _type: "https://in-toto.io/Statement/v0.1", + predicateType: "https://slsa.dev/provenance/v1", + subject: [ + { + name: "ARTIFACT_NAME", # i.e. "demo.txt" + digest: { + sha256: "ARTIFACT_DIGEST" # i.e. d98cde99908b1a041cb99de4eccd2d964de038dc25ab16056d25c3e926cc25c6 + } + } + ], + predicate: { + buildDefinition: { + buildType: "https://gitlab.com/gitlab-org/gitlab-runner/-/blob/#{runner_manager_build.runner_manager.revision}/PROVENANCE.md", + externalParameters: build.variables.map(&:key), + internalParameters: { + architecture: runner_manager_build.runner_manager.architecture, + executor: runner_manager_build.runner_manager.executor_type, + job: id, + name: runner.display_name + }, + resolvedDependencies: [ + { + uri: Gitlab::Routing.url_helpers.project_url(project), + digest: { + sha256: sha + } + } + ] + }, + runDetails: { + builder: { + id: Gitlab::Routing.url_helpers.group_runner_url(runner.owner, runner), + version: { + "gitlab-runner": runner_manager_build.runner_manager.revision + } + }, + metadata: { + invocationID: id, + startedOn: started_at.try(:iso8601), + finishedOn: finished_at.try(:iso8601) + } + } + } + } + end + protected def run_status_commit_hooks! -- GitLab From 415a33f9fd0d98633e0633ae9b6337478d1f9db5 Mon Sep 17 00:00:00 2001 From: Darby Frey Date: Wed, 14 May 2025 23:12:00 -0400 Subject: [PATCH 2/2] Adding sha256 to artifact metadata --- app/models/ci/build.rb | 9 +++-- workhorse/internal/zipartifacts/metadata.go | 41 +++++++++++++++++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index da1c89edfe0cc1..eab9e26a541950 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1205,21 +1205,24 @@ def token def to_slsa return false unless runner_manager_build + entry = artifacts_metadata_entry('') + file = entry.files.first + { _type: "https://in-toto.io/Statement/v0.1", predicateType: "https://slsa.dev/provenance/v1", subject: [ { - name: "ARTIFACT_NAME", # i.e. "demo.txt" + name: file.name, digest: { - sha256: "ARTIFACT_DIGEST" # i.e. d98cde99908b1a041cb99de4eccd2d964de038dc25ab16056d25c3e926cc25c6 + sha256: file.entries[file.name][:sha256] } } ], predicate: { buildDefinition: { buildType: "https://gitlab.com/gitlab-org/gitlab-runner/-/blob/#{runner_manager_build.runner_manager.revision}/PROVENANCE.md", - externalParameters: build.variables.map(&:key), + externalParameters: variables.map(&:key), internalParameters: { architecture: runner_manager_build.runner_manager.architecture, executor: runner_manager_build.runner_manager.executor_type, diff --git a/workhorse/internal/zipartifacts/metadata.go b/workhorse/internal/zipartifacts/metadata.go index 875c93e03f8dfc..1e94fe0d2db4f6 100644 --- a/workhorse/internal/zipartifacts/metadata.go +++ b/workhorse/internal/zipartifacts/metadata.go @@ -4,7 +4,9 @@ package zipartifacts import ( "archive/zip" "compress/gzip" + "crypto/sha256" "encoding/binary" + "encoding/hex" "encoding/json" "fmt" "io" @@ -20,6 +22,7 @@ type metadata struct { Size uint64 `json:"size"` Zipped uint64 `json:"zipped"` Comment string `json:"comment,omitempty"` + SHA256 string `json:"sha256,omitempty"` } // MetadataHeaderPrefix is the prefix indicating the length of the string below, encoded properly. @@ -28,9 +31,33 @@ const MetadataHeaderPrefix = "\x00\x00\x00&" // MetadataHeader is a string that represents the metadata header for GitLab Build Artifacts. const MetadataHeader = "GitLab Build Artifacts Metadata 0.0.2\n" -func newMetadata(file *zip.File) metadata { +func calculateSHA256(file *zip.File) (string, error) { if file == nil { - return metadata{} + return "", nil + } + + rc, err := file.Open() + if err != nil { + return "", err + } + defer rc.Close() + + h := sha256.New() + if _, err := io.Copy(h, rc); err != nil { + return "", err + } + + return hex.EncodeToString(h.Sum(nil)), nil +} + +func newMetadata(file *zip.File) (metadata, error) { + if file == nil { + return metadata{}, nil + } + + sha256Hash, err := calculateSHA256(file) + if err != nil { + return metadata{}, err } return metadata{ @@ -40,7 +67,8 @@ func newMetadata(file *zip.File) metadata { Size: file.UncompressedSize64, Zipped: file.CompressedSize64, Comment: file.Comment, - } + SHA256: sha256Hash, + }, nil } func (m metadata) writeEncoded(output io.Writer) error { @@ -57,7 +85,12 @@ func writeZipEntryMetadata(output io.Writer, path string, entry *zip.File) error return err } - if err := newMetadata(entry).writeEncoded(output); err != nil { + meta, err := newMetadata(entry) + if err != nil { + return err + } + + if err := meta.writeEncoded(output); err != nil { return err } -- GitLab