Resolve "Modify PublishProvenanceService to persist attestation"

What does this MR do and why?

This MR and the involved changes

This merge request persists Sigstore bundles into the database. This is the integration of "Modify PublishProvenanceService so that it calls cosign, performing attestation." and "Add file uploader for SLSA attestations". The next merge requests will deal with retrieving these bundles and providing a way for users to verify the integrity of their artifacts.

This will happen only where the FF is enabled, and attestation has been explicitly enabled. More info in testing section.

What are "Sigstore bundles"?

The grouppipeline security group is working towards providing users with SLSA Level 3 Provenance Attestations. Quoting from the SLSA documentation, it states that attestations are:

It’s the verifiable information about software artifacts describing where, when, and how something was produced. For higher SLSA levels and more resilient integrity guarantees, provenance requirements are stricter and need a deeper, more technical understanding of the predicate. Describe how an artifact or set of artifacts was produced so that:

  • Consumers of the provenance can verify that the artifact was built according to expectations.
  • Others can rebuild the artifact, if desired.

As a simplified TL;DR, in the context of GitLab, a provenance statement is a JSON document that correlates the SHA-256 sum of an artifact with the build information. A worker then performs a digital signature, called a provenance attestation, stored as a "Sigstore Bundle" blob. This is a highly sought-after feature, particularly for our GitLab Ultimate customers.

References

Testing locally:

To test this end-to-end, we need an OIDC provider that Fulcio can reach and is allowlisted. In production, this is simple because gitlab.com is an authorised OIDC provider. Locally, there are some prerequisites.

  • Install sigstore, as documented in Sigstore Local. This will install Fulcio, Rekor and TUF.
  • You need to configure GDK to have runners enabled as documented in doc/howto/runner.md · main · GitLab.org / GitLab Development Kit · GitLab
  • You need to configure your local cosign command to use the local TUF by running regenerating TUF, cd tuf/repository, executing python3 -m http.server, and then cosign initialize --mirror http://localhost:8000 --root ~/root.json
  • You need to configure a build that generates an artifact and uses our temporary environment variable GENERATE_PROVENANCE to trigger a build, and properly populates the SIGSTORE_ID_TOKEN variable. The project associated with the build must be public.
  • The build must have an age of less than 60 minutes. This is because SIGSTORE_ID_TOKEN expires after that time.

The following .gitlab.yml file complies with the requirements above.

build-job:
  stage: build
  variables:
    GENERATE_PROVENANCE: true
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  script:
    - echo "Hello, $GITLAB_USER_LOGIN!"
    - echo "Hello, $GITLAB_USER_LOGIN!" > test.txt
  artifacts:
    paths:
      - test.txt

Once that is in place ☘️ we can do a call for the service:

% COSIGN_FULCIO_URL="http://sigstore.local:5555" COSIGN_REKOR_URL="http://sigstore.local:3090" bundle exec rails c
> build = Ci::Build.last; pps = Ci::Slsa::PublishProvenanceService.new(build);
[...]
> pps.execute
[...]
=> #<ServiceResponse:0x000000032b259580
 @http_status=:ok,
 @message="OK",
 @payload=
  {:attestations=>
    [#<SupplyChain::Attestation:0x000000015d717d08
      id: 17,
      created_at: Mon, 29 Sep 2025 03:33:08.742091000 UTC +00:00,
      updated_at: Mon, 29 Sep 2025 03:33:08.742091000 UTC +00:00,
      project_id: 20,
      build_id: 400,
      status: "success",
      expire_at: Wed, 29 Sep 2027 03:33:08.737975000 UTC +00:00,
      predicate_kind: "provenance",
      predicate_type: "https://slsa.dev/provenance/v1",
      subject_digest: "3c5bba498d6f7a2cb4c195cf0873c8b68c9407f04dfa9acaad7fe4875e5e93f1",
      file: "attestation-20250929-13540-q1gkmp.bundle",
      file_store: 1>]},
 @reason=nil,
 @status=:success>
> a = SupplyChain::Attestation.last
> a.file.read
=> "{\"mediaType\":\"application/vnd.dev.sigstore.bundle.v0.3+json\",\"verificationMaterial\":{\"certificate\":{\"rawBytes\":\"MIIFyjCCBVGgAwIBAgIUUwF2f95As+4QTt4lsvnr5xQOWQswCgYI
KoZIzj0EAwMwaTEMMAoGA1UEBhMDVVNBMREwDwYDVQQIEwhBbnlQbGFjZTEQMA4GA1UEBxMHQW55dG93bjEUMBIGA1UECRMLMTIzIE1haW4gU3QxDzANBgNVBBETBkFCQ0RFRjENMAsGA1UEChMEYWNtZTAeFw0yNTA5MjkwMzMzMDhaFw0
yNTA5MjkwMzQzMDhaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS51osueUBOvXOlSUW/Qn9qxlidvMskx6rOAw1OhmANEhkvY5XW9WD0+9S9ewRf5sU67lq2atflZZ1PgZUhnhyZo4IEPjCCBDowDgYDVR0PAQH/BAQDAgeAMBMGA1
UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBTsY64rSw7IdOL+1iQhdh9nXHW19zAfBgNVHSMEGDAWgBTkoCXkrI1TAK8OKo8/Tollcu4UPjBZBgNVHREBAf8ETzBNhktodHRwczovL2dkay50ZXN0OjMwMDAvcm9vdC90ZXN0LXNsc2Etd
29ya2VyLy8uZ2l0bGFiLWNpLnltbEByZWZzL2hlYWRzL21haW4wIgYKKwYBBAGDvzABAQQUaHR0cDovL2dkay50ZXN0OjMwMDAwJAYKKwYBBAGDvzABCAQWDBRodHRwOi8vZ2RrLnRlc3Q6MzAwMDBbBgorBgEEAYO/MAEJBE0MS2h0dHBz
Oi8vZ2RrLnRlc3Q6MzAwMC9yb290L3Rlc3Qtc2xzYS13b3JrZXIvLy5naXRsYWItY2kueW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAEKBCoMKDU2YzdhY2RmMWY4OTFlZDZmYWVlZGM1ODkxZGNlMTdkOGNlMDgxYjAwGwYKKwY
BBAGDvzABCwQNDAtzZWxmLWhvc3RlZDA4BgorBgEEAYO/MAEMBCoMKGh0dHBzOi8vZ2l0bGFiLmNvbS9yb290L3Rlc3Qtc2xzYS13b3JrZXIwOAYKKwYBBAGDvzABDQQqDCg1NmM3YWNkZjFmODkxZWQ2ZmFlZWRjNTg5MWRjZTE3ZDhjZT
A4MWIwMB8GCisGAQQBg78wAQ4EEQwPcmVmcy9oZWFkcy9tYWluMBIGC[...]

Database Full query + Plan

Full query for destroy call, as required by Danger:

> existing_attestation.destroy
  SupplyChain::Attestation Destroy (0.5ms)  DELETE FROM "slsa_attestations" WHERE "slsa_attestations"."id" = 7 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:sroque-worcel--20250519-QHYJX,console_username:samroque-worcel,line:(pry):3:in `__pry__'*/

Explain plan:

EXPLAIN ANALYZE DELETE FROM "slsa_attestations" WHERE "slsa_attestations"."id" = 7;

                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on slsa_attestations  (cost=0.15..2.17 rows=0 width=0) (actual time=0.034..0.034 rows=0 loops=1)
   ->  Index Scan using slsa_attestations_pkey on slsa_attestations  (cost=0.15..2.17 rows=1 width=6) (actual time=0.033..0.034 rows=0 loops=1)
         Index Cond: (id = 7)
 Planning Time: 0.756 ms
 Execution Time: 0.160 ms
(5 rows)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #567885 (closed)

Edited by Sam Roque-Worcel

Merge request reports

Loading