Add PostgreSQL CA driver

What does this MR do and why?

Adds the PostgreSQL implementation of the root CA driver for local SLSA attestation signing.

It stores per-project root CA keys in SupplyChain::SigningCertificate, retrieves the active certificate, and signs leaf certificates without exposing the root key to the base driver.

This is part of a 2 part MR

References

Screenshots or screen recordings

Not applicable.

Generated root CA certificate
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            34:83:ee:63:d4:fe:0e:69:bd:4b:15:23:60:86:6c:29:d1:a7:0a:fc
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=ca, DC=gitlab
        Validity
            Not Before: Sep 14 11:48:16 2026 GMT
            Not After : Sep 14 11:53:16 2036 GMT
        Subject: CN=ca, DC=gitlab
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:38:33:74:60:37:5c:0c:8c:9b:ae:54:f8:bf:d2:
                    f2:a3:c1:b8:1f:c8:bb:55:28:08:65:76:b0:ae:b0:
                    69:8e:da:83:a0:bd:99:67:d8:1c:d3:13:44:f3:2b:
                    1e:be:5a:80:92:a8:f2:55:66:e7:86:01:85:fd:e8:
                    03:87:8d:fa:80
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Subject Key Identifier:
                0C:DF:5F:25:F2:CF:A3:32:22:A9:A4:01:9B:5B:55:53:5D:17:EE:33
    Signature Algorithm: ecdsa-with-SHA256
Generated leaf certificate
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0a:de:c2:90:f4:95:49:81:20:49:79:c2:0c:9d:0e:c2:92:6c:e1:c6
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=ca, DC=gitlab
        Validity
            Not Before: Sep 14 11:48:16 2026 GMT
            Not After : Sep 14 12:53:16 2026 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:7b:7f:2b:88:8f:1f:f1:46:b8:16:d4:c6:1b:5e:
                    3d:a0:26:74:cc:74:9b:e3:2d:7f:35:ac:fb:28:e0:
                    91:e4:0c:e6:54:f9:f7:d6:68:0e:a3:74:a9:f0:91:
                    7c:fd:15:63:05:88:64:21:14:14:5a:dc:c0:48:ef:
                    c4:73:59:eb:ef
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Subject Key Identifier:
                8C:4B:79:A1:1B:B0:16:DD:D8:3B:84:49:84:26:3B:A3:26:49:38:34
            X509v3 Authority Key Identifier:
                0C:DF:5F:25:F2:CF:A3:32:22:A9:A4:01:9B:5B:55:53:5D:17:EE:33
            X509v3 Subject Alternative Name: critical
                URI:http://gitlab.example.com/group/project//.gitlab-ci.yml@refs/heads/main
            1.3.6.1.4.1.57264.1.8:
                ..http://gdk.local:3000
    Signature Algorithm: ecdsa-with-SHA256

How to set up and validate locally

bin/rspec \
  spec/lib/supply_chain/postgres_ca_driver_spec.rb \
  spec/models/supply_chain/signing_certificate_spec.rb

Database query

SELECT "supply_chain_signing_certificates".*
FROM "supply_chain_signing_certificates"
WHERE "supply_chain_signing_certificates"."project_id" = 1
  AND "supply_chain_signing_certificates"."active" = TRUE
LIMIT 1;
Limit  (actual time=0.035..0.036 rows=1 loops=1)
  -> Index Scan using uniq_idx_signing_certificates_on_project_id_when_active
       Index Cond: (project_id = 1)
Planning Time: 0.167 ms
Execution Time: 0.041 ms
How the signing certificate transaction works

PostgresCaDriver#signing_certificate returns the SigningCertificate record for the project's active root CA. RootCaDriver#ca_certificate looks up the CA through fetch_ca. If no active CA exists, it creates one through provision_ca!, which calls store_certificate. Both hooks record the backing row in @signing_certificate, but only if the certificate passes the base class validity check. A rejected CA is never exposed.

Why a transaction

store_certificate reads, then writes. It looks for an active certificate and inserts one only if none exists. The transaction makes the "read, then decide" step atomic, so the driver can return a usable existing certificate or raise for an unusable one. Relying on the unique index alone would only give a RecordNotUnique error. The expensive work (key generation, building and signing the CA certificate) happens in provision_ca! before the transaction opens. The transaction only holds one indexed SELECT and at most one INSERT.

Normal path

  1. The transaction opens.
  2. find_active_certificate queries the project's active certificate (uniq_idx_signing_certificates_on_project_id_when_active).
  3. One of three outcomes:
    • No active certificate: create! inserts the new record and returns it.
    • Active certificate is usable: it is returned, and the newly generated key and certificate are discarded.
    • Active certificate is unusable (expired, or expires before a new leaf certificate would): raises InvalidCaCertificate.
  4. The transaction commits.

Concurrent path

Two processes can both see no active certificate and both call create!. The second one fails with either:

  • RecordNotUnique, when the partial unique index rejects the insert, or
  • RecordInvalid with active: :taken, when the model uniqueness validation catches it first.

The rescue checks certificate_conflict?, so unrelated validation errors are re-raised. It then runs find_active_certificate again and returns the first process's record.

  • If no record is found (the first process rolled back), the original error is re-raised.
  • If the first process's certificate is unusable, @signing_certificate is not set and RootCaDriver#ca_certificate raises InvalidCaCertificate.

What it never does

store_certificate never updates or deletes an existing record. It only inserts or returns. An unusable active certificate is rejected with an error, not overwritten. Rotation is out of scope for this MR.

Certificate creation is a single-row insert. No bulk operations or schema changes are included.

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