diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 150c5255fbd64df5300bff642b67f1751ac76a48..eab9e26a541950c1fe45985f64dd914688ad5328 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1202,6 +1202,59 @@ def token super end + 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: file.name, + digest: { + 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: 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! diff --git a/workhorse/internal/zipartifacts/metadata.go b/workhorse/internal/zipartifacts/metadata.go index 875c93e03f8dfc402a73e4cc64d2a74ed034c777..1e94fe0d2db4f61cf029eb7e8e4ae87ba7a5866c 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 }