Files
certctl/docs/reference/connectors/openssl.md
T
shankar0123 a310aab7c7 docs: Phase 4 follow-on batch 2 — 8 remaining issuer per-pages
Extracts the rest of the issuer per-connector deep-dive pages:

- local-ca.md (170 lines) — Local CA self-signed / sub-CA / tree mode,
  CRL+OCSP endpoints, EKU support, MaxTTL enforcement, L-014 file-on-
  disk threat model carve-out
- acme.md (235 lines) — RFC 8555 v2 client (HTTP-01 / DNS-01 /
  DNS-PERSIST-01), ARI per RFC 9773, EAB + ZeroSSL auto-EAB,
  Let's Encrypt profile selection, revoke-by-serial Top-10 fix #7
- step-ca.md (99 lines) — Smallstep JWK-provisioner synchronous
  issuance with MaxTTL enforcement
- openssl.md (157 lines) — script-based shell-out with full
  threat model (what's accepted, what's not, mitigations, V3-Pro
  forward path)
- sectigo.md (98 lines) — Sectigo SCM REST with bounded async polling
- google-cas.md (89 lines) — GCP managed private CA with OAuth2
  service-account auth + IAM-role guidance
- entrust.md (96 lines) — Entrust CA Gateway mTLS-authenticated with
  approval-pending support and mTLS keypair caching
- globalsign.md (122 lines) — Atlas HVCA dual auth (mTLS + API
  key/secret), region-aware base URLs, mTLS keypair caching

Index forward-list expanded to enumerate all 13 issuer connectors
(including the 5 pages from batch 1) in alphabetical order.

This is part 2 of 4 for the Phase 4 follow-on (per-connector page
extraction) tracked in cowork/docs-overhaul-phase-2-restructure-2026-05-04/log.md.

Net add: 8 files, 1,066 lines. No content removed from index.md.
2026-05-05 03:59:35 +00:00

7.0 KiB

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.

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 execs 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 compliance environment (PCI-DSS Level 1, FedRAMP High, HIPAA-regulated PHI handling) where shell-out attack surfaces are formally disallowed.
  • 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").