-
Notifications
You must be signed in to change notification settings - Fork 1
GitOps
GitOps Certificate Management brings declarative, version-controlled certificate lifecycle management to the lab. Certificates are defined as desired state in a YAML file, and a reconciler script compares the desired state against the live PKI infrastructure to issue, revoke, or renew certificates as needed.
┌──────────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ desired-state.yaml │──────► │ Reconciler │──────► │ Dogtag PKI │
│ (Git-tracked) │ │ (gitops- │ │ (9 CAs across │
│ │ │ reconcile.py) │ │ 3 hierarchies)│
└──────────────────────┘ └──────────────────┘ └─────────────────┘
│ │
Git commit Dry-run / Apply
& PR review mode selection
Benefits:
- Certificates defined as code, tracked in Git
- Pull request reviews for certificate changes
- Audit trail for all certificate operations
- Automated drift detection between desired and actual state
- Bulk certificate management across all PKI hierarchies
Location: configs/gitops/desired-state.yaml
certificates:
# Web server certificates
- cn: web.cert-lab.local
pki_type: rsa
ca_level: intermediate
profile: caServerCert
state: present
san:
- web.cert-lab.local
- www.cert-lab.local
validity_days: 365
- cn: api.cert-lab.local
pki_type: ecc
ca_level: intermediate
profile: caECServerCert
state: present
san:
- api.cert-lab.local
validity_days: 365
# IoT device certificates
- cn: sensor-001.iot.cert-lab.local
pki_type: rsa
ca_level: iot
profile: caServerCert
state: present
validity_days: 90
- cn: sensor-002.iot.cert-lab.local
pki_type: ecc
ca_level: iot
profile: caECServerCert
state: present
validity_days: 90
# Post-quantum protected service
- cn: vault.cert-lab.local
pki_type: pqc
ca_level: intermediate
profile: caMLDSAServerCert
state: present
san:
- vault.cert-lab.local
validity_days: 365
# Decommissioned device — should be revoked
- cn: old-sensor.iot.cert-lab.local
pki_type: rsa
ca_level: iot
state: absent
revocation_reason: cessation_of_operation
# Compromised device — should be revoked
- cn: compromised.cert-lab.local
pki_type: rsa
ca_level: intermediate
state: absent
revocation_reason: key_compromise| Field | Required | Description | Values |
|---|---|---|---|
cn |
Yes | Common Name (fully qualified domain name) | Any valid hostname |
pki_type |
Yes | PKI hierarchy |
rsa, ecc, pqc
|
ca_level |
Yes | Target CA within the hierarchy |
root, intermediate, iot
|
profile |
No | Certificate profile (auto-selected if omitted) | See profile mapping |
state |
Yes | Desired state |
present or absent
|
san |
No | Subject Alternative Names | List of DNS names |
validity_days |
No | Certificate validity period in days | Integer (default: 365) |
revocation_reason |
No | Reason for revocation (required when state: absent) |
See reasons below |
Revocation reasons: unspecified, key_compromise, ca_compromise, affiliation_changed, superseded, cessation_of_operation, certificate_hold, privilege_withdrawn
When profile is not specified, the reconciler selects the correct profile based on pki_type:
| PKI Type | Profile | Algorithm |
|---|---|---|
rsa |
caServerCert |
SHA512withRSA (RSA-4096) |
ecc |
caECServerCert |
SHA384withEC (P-384) |
pqc |
caMLDSAServerCert |
ML-DSA-87 (FIPS 204) |
The reconciler maps pki_type + ca_level to the correct Dogtag CA container and port:
| PKI Type | CA Level | Container | Port |
|---|---|---|---|
rsa |
root |
rsa-root-ca |
8443 |
rsa |
intermediate |
rsa-intermediate-ca |
8444 |
rsa |
iot |
rsa-iot-ca |
8445 |
ecc |
root |
ecc-root-ca |
8463 |
ecc |
intermediate |
ecc-intermediate-ca |
8464 |
ecc |
iot |
ecc-iot-ca |
8465 |
pqc |
root |
pq-root-ca |
8453 |
pqc |
intermediate |
pq-intermediate-ca |
8454 |
pqc |
iot |
pq-iot-ca |
8455 |
Location: scripts/gitops-reconcile.py
# Dry-run mode (default) — shows planned actions without making changes
python scripts/gitops-reconcile.py --state configs/gitops/desired-state.yaml
# Apply mode — executes the reconciliation plan
python scripts/gitops-reconcile.py --state configs/gitops/desired-state.yaml --apply| Flag | Description | Default |
|---|---|---|
--state |
Path to the desired state YAML file | Required |
--dry-run |
Show plan without executing (default behavior) | true |
--apply |
Execute the reconciliation plan | false |
The reconciler performs these steps:
- Parse the desired state YAML file
- Query each target CA for existing certificates (by CN)
- Compare desired state against actual state
- Plan actions based on the difference
-
Execute (if
--applyis set) or display the plan
| Desired State | Actual State | Action |
|---|---|---|
present |
Not found | Issue new certificate |
present |
Valid certificate exists | No action (already compliant) |
present |
Expired certificate | Renew (issue new, revoke old) |
present |
Revoked certificate | Re-issue new certificate |
absent |
Valid certificate exists | Revoke with specified reason |
absent |
Already revoked | No action (already compliant) |
absent |
Not found | No action (already compliant) |
================================================================================
GitOps Certificate Reconciliation Plan
State file: configs/gitops/desired-state.yaml
Mode: DRY-RUN
================================================================================
ACTION CN PKI CA DETAILS
─────────────────────────────────────────────────────────────────────────────
ISSUE web.cert-lab.local rsa intermediate 365 days, SAN: web, www
ISSUE api.cert-lab.local ecc intermediate 365 days, SAN: api
ISSUE sensor-001.iot.cert-lab.local rsa iot 90 days
NO-OP sensor-002.iot.cert-lab.local ecc iot Already present (serial: 0x3F)
ISSUE vault.cert-lab.local pqc intermediate 365 days, SAN: vault
REVOKE old-sensor.iot.cert-lab.local rsa iot Reason: cessation_of_operation
NO-OP compromised.cert-lab.local rsa intermediate Already revoked
================================================================================
Summary: 4 ISSUE | 1 REVOKE | 2 NO-OP
Run with --apply to execute this plan
================================================================================
================================================================================
GitOps Certificate Reconciliation
State file: configs/gitops/desired-state.yaml
Mode: APPLY
================================================================================
[1/5] Issuing web.cert-lab.local (rsa/intermediate)... OK (serial: 0x4A)
[2/5] Issuing api.cert-lab.local (ecc/intermediate)... OK (serial: 0x12)
[3/5] Issuing sensor-001.iot.cert-lab.local (rsa/iot)... OK (serial: 0x7B)
[4/5] Issuing vault.cert-lab.local (pqc/intermediate)... OK (serial: 0x09)
[5/5] Revoking old-sensor.iot.cert-lab.local (rsa/iot, serial: 0x3E)... OK
================================================================================
Summary: 4 issued | 1 revoked | 0 errors
================================================================================
A typical GitOps workflow for certificate management:
# 1. Create a branch for the change
git checkout -b certs/add-new-api-server
# 2. Edit the desired state
vi configs/gitops/desired-state.yaml
# Add: cn: new-api.cert-lab.local, state: present, pki_type: ecc ...
# 3. Dry-run to verify the plan
python scripts/gitops-reconcile.py --state configs/gitops/desired-state.yaml
# 4. Commit and push
git add configs/gitops/desired-state.yaml
git commit -m "Add new-api.cert-lab.local certificate"
git push -u origin certs/add-new-api-server
# 5. Create merge request, get review
# 6. After merge, apply the changes
python scripts/gitops-reconcile.py --state configs/gitops/desired-state.yaml --applyTo revoke a certificate via GitOps, change its state to absent and specify a revocation reason:
# Before (active device)
- cn: old-sensor.iot.cert-lab.local
pki_type: rsa
ca_level: iot
state: present
validity_days: 90
# After (decommissioned)
- cn: old-sensor.iot.cert-lab.local
pki_type: rsa
ca_level: iot
state: absent
revocation_reason: cessation_of_operationThe reconciler will find the active certificate by CN, look up its serial number, and revoke it with the specified reason.