Add cosign key import to ProvenancePublisher

What does this MR do and why?

This MR adds ProvenancePublisher#cosign_import_key_pair, a private method that imports a leaf private key into cosign's encrypted key format. It is written next to cosign_attest_blob, in the same style.

ADR 007 LOCAL_CA signing signs each attestation with a fresh leaf key from the project's root CA (RootCaDriver#generate_leaf_certificate). cosign signs with a key file in its own encrypted format, so the leaf private key has to be imported into that format first.

Behaviour:

  • Runs cosign import-key-pair --yes --key /dev/stdin --output-key-prefix <dir>/cosign. The leaf private key PEM goes over stdin, so the plaintext key never touches disk.
  • A random password (SecureRandom.hex(32)) is passed only in the COSIGN_PASSWORD environment variable, never on the command line.
  • cosign writes an encrypted cosign.key (and cosign.pub) into dir. The method returns { key_path:, password: }, where key_path is <dir>/cosign.key.
  • dir is validated before cosign runs by a new validate_key_dir!, in the same style as validate_blob_name!: it checks for path traversal, requires an absolute path (so it cannot be read as a cosign option), and requires an existing directory.
  • An invalid dir raises InvalidInput, and a cosign failure raises the new KeyImportFailure with the duration and cosign's stderr, the same way AttestationFailure works in cosign_attest_blob.
  • --yes lets cosign overwrite an existing key pair in dir instead of prompting.

This MR replaces the earlier approach in !258964 (closed) and !260612 (closed), which added separate SupplyChain::Cosign::* classes. After discussion, the import lives in ProvenancePublisher instead, to reuse the existing, AppSec-approved cosign_attest_blob pattern.

Not in this MR: the method is not called yet. https://gitlab.com/gitlab-org/gitlab/-/work_items/603913 will call it from cosign_attest_blob when signing with LOCAL_CA, and owns the temp dir (Dir.mktmpdir), leaf generation with SupplyChain::PostgresCaDriver, and cleanup. Option B verification (https://gitlab.com/gitlab-org/gitlab/-/work_items/630121) also needs a trusted root and an offline signing config, which come in later issues.

No database, API, UI or documentation changes.

Tests: spec/lib/supply_chain/provenance_publisher_spec.rb (8 new examples), using the existing 'with mocked cosign execution' shared context so CI does not need cosign. 100% line and branch coverage of provenance_publisher.rb.

References

Screenshots or screen recordings

Not applicable. This is a backend-only change with no UI.

How to set up and validate locally

In a Rails console, with cosign installed:

  1. Import a key and inspect the result:

    You would need to have cosign installed locally

    brew install cosign # if you want to test this locally
    build = Ci::Build.last
    publisher = SupplyChain::ProvenancePublisher.new(build)
    dir = Dir.mktmpdir
    key = OpenSSL::PKey::EC.generate('prime256v1')
    imported = publisher.send(:cosign_import_key_pair, key: key, dir: dir)
    Dir.children(dir)                              # ["cosign.key", "cosign.pub"]
    File.readlines(imported[:key_path]).first      # "-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY-----\n"
  2. From a shell, confirm the imported key matches the original: COSIGN_PASSWORD=<imported[:password]> cosign public-key --key <imported[:key_path]> prints the public key, which matches key.public_to_pem.

  3. Show validation: publisher.send(:cosign_import_key_pair, key: key, dir: 'relative/dir') raises SupplyChain::ProvenancePublisher::InvalidInput without running cosign.

  4. Run the specs: bin/rspec spec/lib/supply_chain/provenance_publisher_spec.rb

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