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
- Part 1 - RootCaDriver
- Part 2 - PostgresCaDriver (You are here)
References
- Part of https://gitlab.com/gitlab-org/gitlab/-/work_items/603912
- Depends on !255812 (merged)
- https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/slsa_level_3/decisions/007_attestations_private_dedicated_self_hosted/
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-SHA256Generated 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-SHA256How to set up and validate locally
bin/rspec \
spec/lib/supply_chain/postgres_ca_driver_spec.rb \
spec/models/supply_chain/signing_certificate_spec.rbDatabase 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 msHow 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
- The transaction opens.
find_active_certificatequeries the project's active certificate (uniq_idx_signing_certificates_on_project_id_when_active).- 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.
- No active certificate:
- 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, orRecordInvalidwithactive: :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_certificateis not set andRootCaDriver#ca_certificateraisesInvalidCaCertificate.
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.