mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 18:31:37 +00:00
d809874fa1
Per operator decision the framework-mapping docs are gone. They
were aspirational (no audit, no certification, no validated
mapping); keeping them around was misleading.
Files deleted (1,883 lines):
- docs/compliance/index.md
- docs/compliance/soc2.md
- docs/compliance/pci-dss.md
- docs/compliance/nist-sp-800-57.md
Hyperlinks removed:
- README.md: 'Auditor / compliance' row in the doc table; the
'(compliance mapping included)' parenthetical in the
positioning paragraph
- docs/README.md: the '## Compliance' section table; the
'Auditor / compliance team' reading-order-by-role row
Prose name-drops swept across 24 files:
- README.md: 'FedRAMP boundary CAs / financial-services policy
CAs' → '4-level boundary CAs / 3-level policy CAs';
'Compliance-grade for PCI-DSS Level 1, FedRAMP Moderate / High,
SOC 2 Type II, HIPAA' → cut entirely
- getting-started/{quickstart,concepts,examples,why-certctl,
advanced-demo}.md: 'compliance' → 'audit' / 'policy';
'PCI-DSS / SOC 2 / NIST SP 800-57' framework lists cut;
''pci': 'true'' tag example → ''environment': 'production''
- migration/cert-manager-coexistence.md: 'compliance rules' →
'policy rules'
- operator/approval-workflow.md: 'Compliance customers (PCI-DSS
Level 1, FedRAMP Moderate / High, SOC 2 Type II, HIPAA)' →
'Operators'; entire 'Compliance control mapping' table
(PCI-DSS §6.4.5 / NIST SP 800-53 SA-15 / SOC 2 Type II CC6.1
/ HIPAA §164.308(a)(4)) deleted; 'compliance contract' →
'two-person-integrity contract'; 'compliance auditors' →
'reviewers'
- operator/legacy-clients-tls-1.2.md: 'PCI-DSS v4.0 Req 4 §2.2.5'
audit-reference → CWE-326 (kept); 'PCI-DSS Req 4 §2.2.5
attestation' section retitled to 'TLS posture summary' and
rewritten without framework framing; 'PCI-DSS, NIST, and
major browsers will eventually deprecate TLS 1.2' →
'Major browsers and OS vendors will eventually deprecate
TLS 1.2'
- operator/database-tls.md: PCI-DSS Req 4 §2.2.5 audit-ref →
CWE-319 only; 'PCI-DSS scope' → 'sensitive data'; PCI-DSS
Req 4 v4.0 prose footing → cut
- operator/runbooks/disaster-recovery.md: 'SOC 2 / PCI
procurement-team deliverable' → 'on-call deliverable';
'compliance auditors' → 'reviewers'
- reference/connectors/{acme,aws-acm,azure-kv,globalsign,
local-ca,openssl,ssh,index}.md: 'compliance reporting
(PCI-DSS §3.6, HIPAA §164.312)' → 'audit reporting';
'Compliance environments (PCI-DSS Level 1, FedRAMP High,
HIPAA)' → 'Regulated environments'; 'compliance audits' →
'audit'; 'FedRAMP boundary CA' pattern names →
'4-level boundary CA' (technically descriptive)
- reference/protocols/est.md: 'compliance-hook seam' →
'device-state hook seam'; 'compliance gating' → 'device-state
gating'; 'est_compliance_failed' → 'est_device_state_failed'
- reference/protocols/scep-intune.md: 'Optional compliance
check' → 'Optional device-state check'; failure-counter
'compliance_failed' → 'device_state_failed'; 'Conditional
Access compliance gating' → 'Conditional Access
device-state gating'
- reference/intermediate-ca-hierarchy.md: 'FedRAMP boundary-CA
deployments where the regulator requires...' →
'Boundary-CA deployments where you want separation of policy
and issuing authorities'; pattern A retitled '4-level FedRAMP
boundary CA' → '4-level boundary CA'
- reference/architecture.md: broken Related-docs link to
compliance.md removed; the rest of that block had stale
pre-Phase-2 paths (quickstart.md, demo-advanced.md,
connectors.md, openapi.md, testing-guide.md, test-env.md) —
retargeted to current locations
- reference/deployment-model.md: 'SOC 2 evidence-report
generator' → 'Audit-evidence report generator'
- reference/vendor-matrix.md: 'SOC 2 / PCI auditors paste this
into evidence packs' → 'reviewers paste this into
vendor-evaluation packs'
- contributor/qa-test-suite.md: 'compliance exist' coverage
description cut; 'Compliance (PCI / SOC2 / HIPAA-relevant)'
risk-class label → 'Audit-relevant'
What was kept:
- CWE references (legitimate technical pointers)
- Microsoft API/feature names that happen to use 'compliance'
literally ('Microsoft Graph compliance API',
'device-compliance validators' — these are MS product names,
not framework name-drops)
- 'NIST PQC' on the landing page (Post-Quantum Cryptography is
the actual NIST standard family, not a compliance framework)
Verified: zero hyperlinks into docs/compliance/ remain. All 24
ci-guards/*.sh pass locally. qa-doc-seed-count.sh clean.
Net diff: 26 files / -1,883 deletions in compliance/ + -32 net
across the prose sweep.
Companion edits in cowork/ (CLAUDE.md doc-tree summary +
WORKSPACE-CHANGELOG.md retirement note) land separately.
157 lines
6.9 KiB
Markdown
157 lines
6.9 KiB
Markdown
# OpenSSL / Custom CA Issuer Connector — Operator Deep-Dive
|
|
|
|
> Last reviewed: 2026-05-05
|
|
>
|
|
> Operator-grade documentation for the script-based OpenSSL /
|
|
> Custom CA issuer connector. For the connector-development context
|
|
> (interface contract, registry, ports/adapters), see the
|
|
> [connector index](index.md).
|
|
|
|
## Overview
|
|
|
|
Script-based issuer connector for organizations with existing CA
|
|
tooling. Delegates certificate signing, revocation, and CRL
|
|
generation to user-provided shell scripts. The connector `exec`s
|
|
the script for every certificate lifecycle operation; the script
|
|
runs as the certctl-server user with that user's full filesystem
|
|
and network access.
|
|
|
|
This is the highest-flexibility, highest-trust connector in
|
|
certctl. It exists to integrate with arbitrary CLI-driven CAs that
|
|
don't have a Go SDK — at the cost of a wider attack surface than
|
|
any other issuer.
|
|
|
|
Implementation lives at `internal/connector/issuer/openssl/`.
|
|
|
|
## When to use this connector
|
|
|
|
Use the OpenSSL / Custom CA connector when:
|
|
|
|
- Your CA is a CLI tool (BoringSSL, custom OpenSSL wrapper,
|
|
hardware-CA controller, internal CA with no published SDK) and
|
|
no Go-native adapter exists.
|
|
- You're prepared to operate the script with the same care as any
|
|
privileged binary on the host (review every line, lock the path
|
|
ownership and mode, audit invocations).
|
|
|
|
Look elsewhere when:
|
|
|
|
- A Go-native adapter exists for your CA (Vault, DigiCert,
|
|
Sectigo, ACME, AWS ACM PCA, Google CAS, EJBCA, Entrust,
|
|
GlobalSign, step-ca). Use the native adapter — narrower attack
|
|
surface, no shell-out exposure.
|
|
- You're in a regulated environment where shell-out attack
|
|
surfaces are formally disallowed by your security policy.
|
|
- You're running multi-tenant certctl-server where tenant-A's
|
|
script can affect tenant-B's certificates.
|
|
|
|
## Configuration
|
|
|
|
| Variable | Required | Description |
|
|
|---|---|---|
|
|
| `CERTCTL_OPENSSL_SIGN_SCRIPT` | Yes | Script that receives CSR on stdin and outputs signed PEM cert on stdout |
|
|
| `CERTCTL_OPENSSL_REVOKE_SCRIPT` | No | Script to revoke a certificate (receives serial number as argument) |
|
|
| `CERTCTL_OPENSSL_CRL_SCRIPT` | No | Script that outputs DER-encoded CRL on stdout |
|
|
| `CERTCTL_OPENSSL_TIMEOUT_SECONDS` | No | Script execution timeout (default 30s) |
|
|
|
|
The sign script receives the CSR PEM on stdin and outputs the
|
|
signed certificate PEM on stdout. The connector parses the
|
|
certificate to extract serial number, validity dates, and chain
|
|
information.
|
|
|
|
Before shell execution, serial numbers are validated as hex-only
|
|
(`^[0-9a-fA-F]+$`) and revocation reason codes are validated
|
|
against the RFC 5280 specification to prevent argv injection. Both
|
|
checks live in `internal/validation/command.go`.
|
|
|
|
## Threat model
|
|
|
|
certctl's OpenSSL adapter is a deliberate trade between
|
|
flexibility and attack surface. Top-10 fix #6 of the 2026-05-03
|
|
issuer-coverage audit captured the threat model in detail; the
|
|
short version is below.
|
|
|
|
### What the adapter accepts
|
|
|
|
- A trusted operator pointing at a trusted script that lives in a
|
|
trusted filesystem location (`/usr/local/bin/`,
|
|
`/opt/<vendor>/bin/`, etc.) with appropriate ownership
|
|
(root-owned, mode 0755) and a clear audit trail
|
|
(filesystem-monitored, version-controlled).
|
|
- Env-var inheritance from the certctl-server process. Operators
|
|
must NOT export sensitive credentials (Vault tokens, API keys
|
|
for OTHER systems) into certctl-server's environment — or, if
|
|
they must, must accept that those credentials are visible to the
|
|
issuance script. The connector does not whitelist or strip env
|
|
vars before fork.
|
|
- The hex-only serial-number filter and the RFC 5280 reason-code
|
|
allow-list as defenses against argv injection. They are NOT
|
|
defenses against a malicious script.
|
|
|
|
### What the adapter does NOT accept
|
|
|
|
- A script path under operator-writable filesystem (`/tmp`,
|
|
`/var/tmp`, `~`) where a non-root user can swap the binary
|
|
mid-flight. **Symlink attack:** a non-root user with write
|
|
access to the directory replaces the script with a symlink to
|
|
`/etc/shadow` or `/root/.ssh/authorized_keys`; certctl-server
|
|
reads (or in the worst case writes via a malicious script)
|
|
those files.
|
|
- Untrusted script content. The script can do anything the
|
|
certctl-server user can — modify state outside `/etc/certctl/`,
|
|
exfiltrate data, write SSH keys to enable persistence.
|
|
Operators MUST review every script line before deploying.
|
|
- A multi-tenant host where multiple operators deploy scripts
|
|
under the same certctl-server. Process-level isolation isn't
|
|
enforced; one operator's script can read another's working
|
|
files (the temp CSR/cert files the connector writes to
|
|
`os.TempDir()` are mode 0600 but are visible by name to anyone
|
|
who can list the directory).
|
|
|
|
## Mitigations operators can layer on
|
|
|
|
- **Run certctl-server under a dedicated unprivileged user**
|
|
(e.g. `certctl:certctl`). The systemd unit ships with
|
|
`User=certctl` by default — keep it that way.
|
|
- **Pin the script path to a root-owned mode-0755 binary**
|
|
(`/usr/local/bin/issue-cert.sh`, root:root, 0755). Add a
|
|
filesystem audit rule (`auditctl -w /usr/local/bin/issue-cert.sh
|
|
-p wa -k certctl-script`) so any write attempt to the script is
|
|
logged.
|
|
- **Set a per-call timeout via `CERTCTL_OPENSSL_TIMEOUT_SECONDS`**
|
|
(default 30s). The connector wires this through
|
|
`exec.CommandContext` so a hung script is killed at the
|
|
wall-clock budget. Production operators should set it to the
|
|
upper bound of legitimate issuance time — anything longer is a
|
|
runaway.
|
|
- **Sanitise the certctl-server environment.** systemd's
|
|
`Environment=` directive lets operators allow-list which env
|
|
vars certctl-server (and therefore the script) sees.
|
|
Default-deny is the safe posture; the connector itself does NOT
|
|
scrub envs before fork.
|
|
- **Use a chroot or container.** systemd's `RootDirectory=` or
|
|
running certctl-server in a container limits the filesystem the
|
|
script can touch.
|
|
- **Audit the script's behaviour.** A wrapper script that logs
|
|
every invocation's argv + env-snapshot + exit code to a
|
|
separate audit log gives operators a forensic trail.
|
|
- **Per-call concurrency bound.** The renewal scheduler's
|
|
`CERTCTL_RENEWAL_CONCURRENCY` (Bundle L closure) bounds
|
|
scheduled traffic; ad-hoc `POST /api/v1/certificates` traffic
|
|
isn't bounded. For high-volume environments, layer a
|
|
reverse-proxy rate limit (NGINX, HAProxy) in front of the API.
|
|
|
|
## V3-Pro forward path
|
|
|
|
The hardened OpenSSL adapter (chroot/container by default,
|
|
env-var allow-list at the adapter layer, signed-script-binary
|
|
verification, audit-log-on-every-invocation, per-call concurrency
|
|
bound shared with the API surface) is V3-Pro work. Tracking:
|
|
`cowork/WORKSPACE-ROADMAP.md` (search "OpenSSL hardened mode").
|
|
|
|
## Related docs
|
|
|
|
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
|
|
- [Local CA issuer](local-ca.md) — Go-native alternative when the CA can be run as a sub-CA under certctl
|
|
- [Vault PKI](vault.md), [EJBCA](ejbca.md), [DigiCert](digicert.md) — Go-native alternatives for common CA stacks
|