Sign LOCAL_CA SLSA attestations with the project CA

What does this MR do and why?

This MR connects the LOCAL_CA signing material to ProvenancePublisher#cosign_attest_blob. When ATTEST_METHOD resolves to LOCAL_CA, attestations are now signed with the project CA instead of going through Sigstore. The SIGSTORE path is unchanged.

The earlier MRs in this stack added the CA, the leaf certificates, the key import, the trusted root and the offline signing config. This is the step that uses them together.

  • Shared validation (hash, blob name) stays at the top. SIGSTORE then validates the ID token and runs cosign with the same flags and values as before. Only the order changes: --yes and --bundle, which both methods use, now come right after --hash. cosign does not depend on flag order.
  • LOCAL_CA passes --key, --certificate, --trusted-root and --signing-config in place of --identity-token, --oidc-issuer and --use-signing-config=false. The key password is only set in the cosign process environment (COSIGN_PASSWORD) and never appears on the command line. The ID token is not passed, as cosign does not use it when signing with a key.
  • LOCAL_CA does not send any Sigstore service URLs. --fulcio-url/--rekor-url are not added, and COSIGN_FULCIO_URL/COSIGN_REKOR_URL are unset for the cosign process. cosign reads COSIGN_<FLAG> environment variables as flag values and refuses to sign when service URLs are combined with --signing-config, so without this, LOCAL_CA would fail on any host that exports those variables (GDK with a local Sigstore stack does).
  • Successful LOCAL_CA attestations record the signing_certificate_id of the CA that issued the leaf. Error attestations keep a nil certificate, because most failures happen before a certificate exists.
  • The shared command body moves into run_cosign_attest_blob, with two small builders, sigstore_arguments and local_ca_arguments, so each path is easy to follow. The flags both methods use (--yes, --bundle) live in the shared base command.

With the offline signing config, the bundle contains the leaf certificate but no transparency log entry or SCT. Verification therefore needs the project trust root and --insecure-ignore-tlog --insecure-ignore-sct, as agreed in https://gitlab.com/gitlab-org/gitlab/-/work_items/630121. Support for this in glab is tracked in https://gitlab.com/gitlab-org/gitlab/-/work_items/603915.

Who this affects. This is the first MR in the stack that changes runtime behaviour. Everything remains behind the slsa_provenance_statement feature flag (default off, https://gitlab.com/gitlab-org/gitlab/-/work_items/547866), and Ci::Slsa::PublishProvenanceService still only accepts public projects until the next MR. After this MR, a build is signed with the project CA when the flag is enabled for its project and either:

  • the instance is self-managed or Dedicated, where LOCAL_CA is the default, or
  • the job sets ATTEST_METHOD: LOCAL_CA.

Until now, these builds were still signed keylessly through Sigstore, while !263571 (merged) already recorded them as local_ca. Any existing local_ca rows therefore contain Sigstore bundles, and this MR resolves that mismatch going forward.

There are no database, API, UI or documentation changes. The documentation will follow with the MR that enables private projects.

Tests: spec/lib/supply_chain/provenance_publisher_spec.rb checks the exact LOCAL_CA command and environment, the persisted row and certificate, and that the ID token is not validated. It also checks that invalid input is rejected before any signing material is created, and covers both attest-blob failures and signing material failures. The artifact and container publisher specs cover LOCAL_CA end to end through publish. The three publisher files have full line and branch coverage.

Manual testing: I ran a pipeline on GDK, using real cosign v3.1.3 and the local Sigstore stack, with a SIGSTORE job and a LOCAL_CA job side by side. Both attestations verified, the LOCAL_CA rows referenced the project CA, and Rekor only recorded an entry for the SIGSTORE artifact. I also ran 38 further checks covering the bundle contents, verification failures, offline signing, each failure path and cleanup of the temporary files.

References

Screenshots or screen recordings

Not applicable, as this is a backend-only change.

How to set up and validate locally

This requires cosign on the Sidekiq host and a runner.

  1. Enable the feature flag for a public project:

    project = Project.find_by_full_path('root/slsa-test')
    Feature.enable(:slsa_provenance_statement, project)
  2. Add the following job to the project's .gitlab-ci.yml and run a pipeline:

    build-local-ca:
      stage: build
      image: alpine:latest
      variables:
        ATTEST_BUILD_ARTIFACTS: "true"
        ATTEST_METHOD: "LOCAL_CA"
      script:
        - echo "hello from $CI_JOB_ID" > artifact.txt
      artifacts:
        paths:
          - artifact.txt
  3. After the job finishes, check the attestation in a Rails console and export the CA and bundle:

    attestation = SupplyChain::Attestation.where(project: project).last
    attestation.slice(:status, :signing_method, :signing_certificate_id)
    # => {"status"=>"success", "signing_method"=>"local_ca", "signing_certificate_id"=><id of the active CA>}
    File.write('/tmp/ca.crt.pem', SupplyChain::PostgresCaDriver.new(project).ca_certificate.to_pem)
    File.binwrite('/tmp/bundle.json', attestation.file.read)
  4. Download artifact.txt from the job, build a trusted root from the CA, and verify the attestation:

    GITLAB_URL=http://gdk.test:3000
    cosign trusted-root create --fulcio "url=$GITLAB_URL,certificate-chain=/tmp/ca.crt.pem" \
      --no-default-fulcio --no-default-rekor --no-default-ctfe --no-default-tsa --out /tmp/trusted-root.json
    cosign verify-blob-attestation --bundle /tmp/bundle.json --trusted-root /tmp/trusted-root.json \
      --certificate-identity "$GITLAB_URL/root/slsa-test//.gitlab-ci.yml@refs/heads/main" \
      --certificate-oidc-issuer "$GITLAB_URL" --type slsaprovenance1 \
      --insecure-ignore-tlog --insecure-ignore-sct artifact.txt
    # Verified OK

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.

Edited by Saahil Sabu Hameed

Merge request reports

Loading
Loading