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.
This commit is contained in:
shankar0123
2026-05-05 03:59:35 +00:00
parent 430180360d
commit a310aab7c7
9 changed files with 1074 additions and 0 deletions
+235
View File
@@ -0,0 +1,235 @@
# ACME Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the outbound ACME v2 issuer
> connector (certctl as an ACME *client*). For the inbound ACME
> server (certctl as an ACME *server*), see
> [acme-server.md](../protocols/acme-server.md). For the
> connector-development context (interface contract, registry,
> ports/adapters), see the [connector index](index.md).
## Overview
The ACME connector implements the full ACME v2 protocol (RFC 8555)
using Go's `golang.org/x/crypto/acme` package. It supports three
challenge methods and ARI (RFC 9773) for renewal-window negotiation.
Compatible CAs include Let's Encrypt, ZeroSSL, Sectigo, Buypass,
Google Trust Services, SSL.com, and any other RFC 8555 ACME
implementation. step-ca's ACME directory is also compatible if you
prefer ACME over the native step-ca connector.
Implementation lives at `internal/connector/issuer/acme/`.
## When to use this connector
Use the ACME connector when:
- You need public-trust certificates (Let's Encrypt, ZeroSSL,
Sectigo via ACME, Google Trust Services, SSL.com).
- You want certctl to drive renewal lifecycle on top of the ACME
CA's free or paid issuance.
- You want one tool that covers both internal PKI (Local, Vault,
step-ca) and public-trust ACME issuance.
Look elsewhere when:
- You need OV / EV certificates and your CA doesn't expose them
via ACME — use the DigiCert or Sectigo SCM REST connectors.
- You're standing up internal-only PKI and don't want to operate
ACME challenge infrastructure — use Local CA or Vault PKI for a
simpler synchronous path.
## Challenge methods
### HTTP-01 (default)
A built-in temporary HTTP server starts on demand during
certificate issuance. The domain being validated must resolve to
the machine running the connector, and the configured HTTP port
must be reachable from the internet.
```json
{
"directory_url": "https://acme-staging-v02.api.letsencrypt.org/directory",
"email": "admin@example.com",
"http_port": 80
}
```
### DNS-01 (for wildcards)
Creates DNS TXT records via user-provided scripts. Required for
wildcard certificates (`*.example.com`) and hosts that can't serve
HTTP on port 80. The connector invokes external scripts to create
and clean up `_acme-challenge` TXT records, making it compatible
with any DNS provider (Cloudflare, Route53, Azure DNS, etc.).
```json
{
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email": "admin@example.com",
"challenge_type": "dns-01",
"dns_present_script": "/etc/certctl/dns/create-record.sh",
"dns_cleanup_script": "/etc/certctl/dns/delete-record.sh",
"dns_propagation_wait": 30
}
```
DNS hook scripts receive these environment variables:
- `CERTCTL_DNS_DOMAIN` — domain being validated
- `CERTCTL_DNS_FQDN` — full record name (`_acme-challenge.<domain>`
for dns-01, `_validation-persist.<domain>` for dns-persist-01)
- `CERTCTL_DNS_VALUE` — TXT record value
- `CERTCTL_DNS_TOKEN` — ACME challenge token
The present script must create the TXT record and exit 0; the
cleanup script removes it (dns-01 only).
### DNS-PERSIST-01 (standing record)
Creates a one-time persistent TXT record at
`_validation-persist.<domain>` containing the CA's issuer domain
and your ACME account URI. Once set, this record authorizes
unlimited future certificate issuances without per-renewal DNS
updates. Based on
[draft-ietf-acme-dns-persist](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
and CA/Browser Forum ballot SC-088v3.
If the CA doesn't offer dns-persist-01 yet, the connector falls
back to dns-01 automatically.
```json
{
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email": "admin@example.com",
"challenge_type": "dns-persist-01",
"dns_present_script": "/etc/certctl/dns/create-record.sh",
"dns_persist_issuer_domain": "letsencrypt.org",
"dns_propagation_wait": 30
}
```
The present script creates a TXT record at
`_validation-persist.<domain>` with the value
`letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/<your-id>`.
This record is permanent — no cleanup script is needed.
## ACME Renewal Information (ARI, RFC 9773)
Instead of using fixed renewal thresholds (e.g. renew 30 days
before expiry), certctl can ask the CA when it should renew.
Enable with `CERTCTL_ACME_ARI_ENABLED=true`.
The ARI protocol lets the CA specify a `suggestedWindow` (start
and end times) for when you should renew — useful for distributing
load during maintenance windows or coordinating mass-revocation
scenarios. Cert ID is computed as `base64url(SHA-256(DER cert))`.
If the CA doesn't support ARI (404 response), certctl
automatically falls back to threshold-based renewal with no
operator intervention required.
## External Account Binding (EAB)
ZeroSSL, Google Trust Services, and SSL.com require EAB for ACME
account registration. For most CAs, get your EAB credentials from
the CA's dashboard and provide them via `eab_kid` and `eab_hmac`.
The HMAC key must be base64url-encoded (no padding). CAs that
don't require EAB (Let's Encrypt, Buypass) ignore these fields.
```json
{
"directory_url": "https://acme.zerossl.com/v2/DV90",
"email": "admin@example.com",
"eab_kid": "your-zerossl-eab-kid",
"eab_hmac": "your-zerossl-eab-hmac-base64url"
}
```
### ZeroSSL auto-EAB
When the directory URL points to ZeroSSL and no EAB credentials
are provided, certctl automatically fetches them from ZeroSSL's
public API (`api.zerossl.com/acme/eab-credentials-email`) using
your configured email address. No dashboard visit required — just
set the directory URL and email. Same approach used by Caddy and
acme.sh.
```json
{
"directory_url": "https://acme.zerossl.com/v2/DV90",
"email": "admin@example.com"
}
```
## Certificate profiles (Let's Encrypt, GA January 2026)
Let's Encrypt supports ACME certificate profile selection. Set
`CERTCTL_ACME_PROFILE=shortlived` to request 6-day certificates —
ideal for ephemeral workloads where short validity substitutes for
revocation. The `tlsserver` profile produces standard TLS
certificates. When the profile field is empty (default), the CA
uses its default profile.
## Environment variables
- `CERTCTL_ACME_DIRECTORY_URL` — ACME directory URL
- `CERTCTL_ACME_EMAIL` — Contact email for account registration
- `CERTCTL_ACME_EAB_KID` — External Account Binding Key ID
- `CERTCTL_ACME_EAB_HMAC` — External Account Binding HMAC key
(base64url-encoded)
- `CERTCTL_ACME_CHALLENGE_TYPE``http-01` (default), `dns-01`,
or `dns-persist-01`
- `CERTCTL_ACME_DNS_PRESENT_SCRIPT` — Path to DNS record creation
script
- `CERTCTL_ACME_DNS_CLEANUP_SCRIPT` — Path to DNS record cleanup
script (dns-01 only)
- `CERTCTL_ACME_DNS_PERSIST_ISSUER_DOMAIN` — CA issuer domain for
persistent record (dns-persist-01 only)
- `CERTCTL_ACME_PROFILE` — Certificate profile for the newOrder
request
## Revocation by serial number (Top-10 fix #7)
RFC 8555 §7.6 requires the certificate DER bytes (not just the
serial) on the revoke wire — but a CLM platform's job is to
abstract over that limitation. Operators routinely have only the
serial in hand: the original PEM was lost, the private key was
rotated, the operator clicked "revoke" in the GUI based on a row
in the certs list.
certctl's ACME
`RevokeCertificate(ctx, RevocationRequest{Serial: ...})` looks the
serial up in the local cert store
(`certificate_versions.pem_chain`), decodes the leaf-cert PEM into
DER, and calls the ACME revoke endpoint with
`(accountKey, der, reasonCode)` — RFC 8555 §7.6 case 1,
"revocation request signed with account key". This works because
the same account key issued the cert, so authority is intrinsic.
The cert version must exist in the local store: this means the
cert was issued through certctl, not imported. If
`GetVersionBySerial` returns `sql.ErrNoRows`, the connector
returns an actionable error pointing at the local-store
requirement. Revoke-by-serial is therefore only available for
ACME certs that certctl issued.
Reason codes follow RFC 5280 §5.3.1: nil reason maps to
`unspecified` (0), and the connector accepts the canonical
camelCase form (`keyCompromise`, `cACompromise`,
`affiliationChanged`, `superseded`, `cessationOfOperation`,
`certificateHold`, `removeFromCRL`, `privilegeWithdrawn`,
`aACompromise`) plus underscore_lower and ALL_CAPS_UNDERSCORE
variants. An unknown reason returns an error rather than silently
demoting to `unspecified` — operators rely on the reason for
compliance reporting (PCI-DSS §3.6, HIPAA §164.312).
## Related docs
- [ACME server](../protocols/acme-server.md) — certctl *as* an ACME server (the inverse direction)
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [migration/acme-from-cert-manager.md](../../migration/acme-from-cert-manager.md) — point cert-manager at certctl's ACME server
- [migration/acme-from-traefik.md](../../migration/acme-from-traefik.md) — point Traefik at certctl's ACME server
+96
View File
@@ -0,0 +1,96 @@
# Entrust Certificate Services Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Entrust CA Gateway issuer
> connector. For the connector-development context (interface
> contract, registry, ports/adapters), see the
> [connector index](index.md).
## Overview
The Entrust connector calls the Entrust CA Gateway REST API with
mutual TLS client-certificate authentication. It supports
synchronous issuance (200 OK with PEM) and approval-pending flows
(201 Accepted with async polling).
Implementation lives at `internal/connector/issuer/entrust/` (the
mTLS keypair cache is shared at
`internal/connector/issuer/mtlscache/`).
## When to use this connector
Use the Entrust connector when:
- You're an Entrust Certificate Services customer using the CA
Gateway as the integration surface.
- You need approval-pending workflows where humans approve
enrollments before issuance.
- You want mTLS-authenticated issuance against a commercial CA
with no API keys to rotate.
Look elsewhere when:
- You only need DV / OV public-trust and your CA is reachable via
ACME — use the [ACME connector](acme.md) for a simpler path.
- You're not already an Entrust customer — DigiCert, Sectigo, and
GlobalSign are comparable commercial alternatives, with
different auth shapes.
## Configuration
| Setting | Required | Default | Description |
|---|---|---|---|
| `CERTCTL_ENTRUST_API_URL` | Yes | — | Entrust CA Gateway base URL |
| `CERTCTL_ENTRUST_CLIENT_CERT_PATH` | Yes | — | Path to mTLS client certificate PEM |
| `CERTCTL_ENTRUST_CLIENT_KEY_PATH` | Yes | — | Path to mTLS client private key PEM |
| `CERTCTL_ENTRUST_CA_ID` | Yes | — | Certificate Authority ID (from `GET /certificate-authorities`) |
| `CERTCTL_ENTRUST_PROFILE_ID` | No | — | Optional enrollment profile ID |
| `CERTCTL_ENTRUST_POLL_MAX_WAIT_SECONDS` | No | `600` (10m) | Bounded-polling deadline for `GetOrderStatus` |
For approval-pending workflows where humans approve enrollments,
bump `CERTCTL_ENTRUST_POLL_MAX_WAIT_SECONDS` to `86400` (24h) so a
single tick can wait through the approval window.
## Authentication
Mutual TLS — the client certificate and key are loaded via
`tls.LoadX509KeyPair()` and attached to the HTTP transport. No API
key or token required.
## Issuance model
Enrollment via
`POST /v1/certificate-authorities/{caId}/enrollments`. Returns 200
with PEM immediately for auto-approved enrollments, or 201
Accepted with a tracking ID for approval-pending orders.
`GetOrderStatus` polls the enrollment endpoint.
## mTLS keypair caching (audit fix #10)
The parsed client certificate plus a precomputed `*http.Transport`
are cached on the connector after the first API call. Steady-state
calls reuse the cached transport — no per-call disk read or
`tls.X509KeyPair` parse.
Rotation is picked up automatically via mtime polling: when the
cert file's mtime advances beyond the last-loaded value, the next
API call re-parses and rebuilds the transport.
Operator workflow: `mv -f new.crt /etc/certctl/entrust/client.crt`
(mtime changes), no process restart required, takes effect on the
next API call. `os.Stat` errors during rotation surface as
connector errors rather than silently serving stale credentials.
## Revocation
CRL and OCSP are managed by Entrust. certctl records revocations
locally and notifies Entrust via
`PUT /v1/certificate-authorities/{caId}/certificates/{serial}/revoke`.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [GlobalSign Atlas HVCA](globalsign.md) — comparable mTLS-authenticated commercial CA
- [Async CA polling](../protocols/async-ca-polling.md) — the bounded-polling primitive
- [Approval workflow](../../operator/approval-workflow.md) — certctl-side two-person integrity (separate from Entrust's approval queue)
+122
View File
@@ -0,0 +1,122 @@
# GlobalSign Atlas HVCA Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the GlobalSign Atlas High Volume
> CA (HVCA) issuer connector. For the connector-development context
> (interface contract, registry, ports/adapters), see the
> [connector index](index.md).
## Overview
GlobalSign Atlas HVCA REST API with **dual authentication**: mTLS
for the TLS handshake AND API key/secret headers for request
authorization. Region-aware base URLs (EMEA, APAC, Americas).
Implementation lives at `internal/connector/issuer/globalsign/`
(mTLS keypair cache shared at
`internal/connector/issuer/mtlscache/`).
## When to use this connector
Use the GlobalSign Atlas HVCA connector when:
- You're a GlobalSign Atlas customer issuing high volumes of
publicly trusted certificates (the "HV" in HVCA).
- You want region-pinned issuance for compliance or latency
reasons (EMEA / APAC / Americas regional endpoints).
- You're prepared to manage both mTLS client certs AND
API key/secret credentials in tandem.
Look elsewhere when:
- You only need DV public-trust and your CA is reachable via ACME —
the [ACME connector](acme.md) is simpler.
- The dual-auth burden (mTLS + API key + API secret) is heavier
than your environment needs — DigiCert (API key only) or Entrust
(mTLS only) are simpler to operate.
## Configuration
| Setting | Required | Default | Description |
|---|---|---|---|
| `CERTCTL_GLOBALSIGN_API_URL` | Yes | — | Atlas HVCA API URL (region-specific) |
| `CERTCTL_GLOBALSIGN_API_KEY` | Yes | — | API key for request authentication |
| `CERTCTL_GLOBALSIGN_API_SECRET` | Yes | — | API secret for request authentication |
| `CERTCTL_GLOBALSIGN_CLIENT_CERT_PATH` | Yes | — | Path to mTLS client certificate PEM |
| `CERTCTL_GLOBALSIGN_CLIENT_KEY_PATH` | Yes | — | Path to mTLS client private key PEM |
| `CERTCTL_GLOBALSIGN_SERVER_CA_PATH` | No | system trust store | PEM bundle used to verify the Atlas API server certificate. Set this for private/lab Atlas deployments whose server TLS chain is not in the host's default trust bundle. |
| `CERTCTL_GLOBALSIGN_POLL_MAX_WAIT_SECONDS` | No | `600` (10m) | Bounded-polling deadline for `GetOrderStatus`. GlobalSign tracks orders by serial number rather than order ID; the polling shape is identical. |
## Authentication
Dual — mTLS client certificate for TLS handshake plus `X-API-Key`
and `X-API-Secret` headers on every request. Both must be valid
or the request fails.
## TLS verification
The connector always verifies the server certificate. When
`server_ca_path` is set, the PEM bundle at that path is used as
the trust anchor; otherwise the host's system trust store is
used. TLS 1.2 is the minimum protocol version.
## Issuance model
`POST /v2/certificates` returns a serial number. Certificate PEM
is available after validation completes. Typically resolves
within seconds for DV. `GetOrderStatus` polls the certificate
endpoint.
## mTLS keypair caching (audit fix #10)
The parsed client certificate plus a precomputed `*http.Transport`
(with `ServerCAPath` pinning preserved when configured) are cached
on the connector after the first API call. Steady-state calls
reuse the cached transport — no per-call disk read or
`tls.X509KeyPair` parse.
Rotation is picked up automatically via mtime polling: when the
cert file's mtime advances beyond the last-loaded value, the next
API call re-parses and rebuilds the transport.
Operator workflow: `mv -f new.crt /etc/certctl/globalsign/client.crt`
(mtime changes), no process restart required, takes effect on the
next API call. `os.Stat` errors during rotation surface as
connector errors rather than silently serving stale credentials.
## Revocation
CRL and OCSP are managed by GlobalSign. certctl records
revocations locally and notifies GlobalSign via
`PUT /v2/certificates/{serial}/revoke`.
## Operator playbook
### Rotating mTLS client material
Same flow as the [Entrust connector](entrust.md): place the new
cert at the configured path, mtime changes, next API call picks
up the new keypair. `ServerCAPath` pin (when configured) is
preserved across the rebuild.
### Rotating API key / secret
Rotate in the Atlas dashboard, then either restart certctl-server
or hot-swap via `PUT /api/v1/issuers/{id}`. The registry's
Rebuild path replaces the connector with the new credentials. The
mTLS transport cache stays warm across the swap (mTLS material
hasn't changed) — only the per-request headers are new.
### Region selection
Atlas HVCA has region-specific base URLs. Use the URL that
matches your account's contracted region; the connector does no
region-routing on its own.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [Entrust connector](entrust.md) — mTLS-only commercial alternative
- [DigiCert connector](digicert.md) — API-key-only commercial alternative
- [Async CA polling](../protocols/async-ca-polling.md) — the bounded-polling primitive
+89
View File
@@ -0,0 +1,89 @@
# Google CAS Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Google Cloud Certificate
> Authority Service (CAS) issuer connector. For the
> connector-development context (interface contract, registry,
> ports/adapters), see the [connector index](index.md).
## Overview
Google Cloud Certificate Authority Service is a managed private CA
on GCP. Issuance is synchronous via the CAS REST API with OAuth2
service-account auth.
Implementation lives at `internal/connector/issuer/googlecas/`.
## When to use this connector
Use the Google CAS connector when:
- Your workloads are GCP-native and you want the CA to live inside
your GCP project (for blast radius, IAM, and audit reasons).
- You want IAM-bound service-account auth instead of API keys to
rotate.
- You need GCP-native CRL distribution and audit logging served by
Google.
Look elsewhere when:
- You're not on GCP — AWS ACM Private CA or Azure Key Vault are
the cloud-native equivalents on those platforms.
- You need public-trust certificates — CAS is private only.
- You don't already pay for CAS (it has a non-trivial monthly
cost). Vault, step-ca, or the Local CA issuer are free
self-hosted alternatives.
## Configuration
| Setting | Required | Default | Description |
|---|---|---|---|
| `CERTCTL_GOOGLE_CAS_PROJECT` | Yes | — | GCP project ID |
| `CERTCTL_GOOGLE_CAS_LOCATION` | Yes | — | GCP region (e.g. `us-central1`) |
| `CERTCTL_GOOGLE_CAS_CA_POOL` | Yes | — | CA pool name |
| `CERTCTL_GOOGLE_CAS_CREDENTIALS` | Yes | — | Path to service account JSON |
| `CERTCTL_GOOGLE_CAS_TTL` | No | `8760h` | Default certificate TTL |
## Authentication
OAuth2 service account. The connector reads a service account
JSON file, signs a JWT with the private key, and exchanges it for
an access token at Google's token endpoint. Tokens are cached and
refreshed automatically (5 min before expiry) so the connector
doesn't pay token-mint latency on every request.
## Revocation
CRL and OCSP are managed by Google CAS directly. certctl records
revocations locally and notifies Google CAS via the revoke
endpoint. CAS's CRL distribution and audit logging serve the
resulting status to verifying clients.
## Operator playbook
### Service-account key rotation
1. Generate a new service-account key in the GCP IAM console.
2. Distribute the new JSON to the certctl host at the
`CERTCTL_GOOGLE_CAS_CREDENTIALS` path (overwrite or use a new
path).
3. Either restart certctl-server with the new env var or hot-swap
via `PUT /api/v1/issuers/{id}` so the registry's Rebuild path
replaces the connector.
4. Delete the old key in GCP IAM after the next successful
issuance proves the new key works.
### Required IAM roles
The service account needs `roles/privateca.certificateRequester`
(or a custom role with `privateca.certificates.create` and
`privateca.certificates.get`) on the CA pool. Add
`roles/privateca.certificateAuthorityUser` if the connector also
needs to read the issuing CA cert chain.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [AWS ACM PCA](aws-acm-pca.md) — AWS equivalent
- [Async CA polling](../protocols/async-ca-polling.md) — bounded-polling primitive (Google CAS is synchronous so doesn't consume it)
+8
View File
@@ -16,10 +16,18 @@ Connectors extend certctl to integrate with external systems for certificate iss
Issuer connectors:
- [ACME](acme.md) — RFC 8555 v2 client (Let's Encrypt, ZeroSSL, Sectigo, Buypass, GTS, SSL.com)
- [ADCS integration](adcs.md) — Active Directory Certificate Services as enterprise root via Local CA sub-CA mode
- [AWS ACM Private CA](aws-acm-pca.md) — managed private CA on AWS, IAM-authenticated
- [DigiCert CertCentral](digicert.md) — commercial public CA (DV / OV / EV)
- [EJBCA (Keyfactor)](ejbca.md) — self-hosted open-source / Keyfactor enterprise CA
- [Entrust Certificate Services](entrust.md) — Entrust CA Gateway with mTLS auth
- [GlobalSign Atlas HVCA](globalsign.md) — Atlas HVCA with dual mTLS + API key/secret auth
- [Google CAS](google-cas.md) — managed private CA on GCP, OAuth2 service-account auth
- [Local CA](local-ca.md) — Go `crypto/x509`-backed signer (self-signed, sub-CA, tree mode)
- [OpenSSL / Custom CA](openssl.md) — script-based shell-out for arbitrary CLI-driven CAs
- [Sectigo SCM](sectigo.md) — Sectigo Certificate Manager REST API
- [step-ca (Smallstep)](step-ca.md) — JWK-provisioner authenticated synchronous internal CA
- [Vault PKI](vault.md) — HashiCorp Vault PKI engine, synchronous issuance
Target connectors:
+170
View File
@@ -0,0 +1,170 @@
# Local CA Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Local CA issuer. For the
> connector-development context (interface contract, registry,
> ports/adapters), see the [connector index](index.md).
## Overview
The Local CA issuer signs certificates using Go's `crypto/x509`
library directly inside certctl-server. There is no external CA
service involved — certctl owns the signing key and emits
certificates synchronously.
Implementation lives at `internal/connector/issuer/local/`.
## When to use this connector
Use the Local CA when:
- You're standing up an internal-only PKI and don't want to operate
a separate CA service (Vault, step-ca, EJBCA).
- You want certctl to be the single point of administration:
signing key, profile policy, CRL and OCSP responder, and
lifecycle automation all live in one process.
- You want sub-CA mode to chain into an enterprise root (ADCS,
HSM-backed root, or another upstream CA) so existing trust
stores validate certctl-issued leaves automatically.
Look elsewhere when:
- You need a public-trust certificate — the Local CA is internal
only. Use ACME or DigiCert / Sectigo for public trust.
- You want signing material backed by an HSM or cloud KMS — that
is on the roadmap (the `internal/crypto/signer/` driver
abstraction exists; HSM, cloud KMS, and SSH-CA drivers don't
yet ship). Until those drivers ship, sub-CA mode pointing at a
hardware-protected root is the closest production posture.
## Modes
### Self-signed mode (default)
Creates a CA on first use (in memory), issues certificates with
proper serial numbers, validity periods, SANs, and key usage
extensions. Designed for development and demos — certificates are
self-signed and not trusted by browsers without operator-side
trust-store work.
### Sub-CA mode (production)
Loads a CA certificate and private key from disk
(`CERTCTL_CA_CERT_PATH` + `CERTCTL_CA_KEY_PATH`). The CA cert was
signed by an upstream CA (e.g. ADCS), so all issued certificates
chain to the enterprise root trust hierarchy. Clients that
already trust the enterprise root automatically trust
certctl-issued certs.
Supports RSA, ECDSA, and PKCS#8 key formats. If the paths are not
set, the connector falls back to self-signed mode. The loaded
certificate must have `IsCA=true` and `KeyUsageCertSign`.
### Tree mode (Rank 8 — multi-level CA hierarchy)
When `Issuer.HierarchyMode = "tree"` is set on the issuer row, the
connector reads the active CA hierarchy from the
`intermediate_cas` table and assembles `IssuanceResult.ChainPEM`
by walking the `parent_ca_id` ancestry from the issuing leaf CA up
to the root.
Tree mode is operator-managed via the admin-gated
`/api/v1/issuers/{id}/intermediates` and
`/api/v1/intermediates/{id}` endpoints (`POST` to create / sign
children, `GET` to list / inspect, `POST .../retire` to two-phase
retire). The signing path is shared with single-mode (cert is
signed via `c.caCert` + `c.caSigner` from the on-disk issuing CA
cert+key); only the chain bytes differ.
RFC 5280 §3.2 (self-signed root validation), §4.2.1.9 (path-length
tightening), and §4.2.1.10 (NameConstraints subset semantics) are
enforced at the service layer fail-closed. The default is
`single`, byte-identical to the pre-Rank-8 historical flow.
See [intermediate-ca-hierarchy.md](../intermediate-ca-hierarchy.md)
for the operator runbook covering 4-level FedRAMP boundary CA,
3-level financial-services policy CA, 2-level internal-PKI
patterns, and the migration runbook for flipping a single-mode
issuer to tree.
## Configuration
```json
{
"ca_common_name": "CertCtl Local CA",
"validity_days": 90,
"ca_cert_path": "/etc/certctl/ca/ca.pem",
"ca_key_path": "/etc/certctl/ca/ca-key.pem"
}
```
## CRL and OCSP (M15b)
The Local CA serves DER-encoded X.509 CRLs unauthenticated at
`GET /.well-known/pki/crl/{issuer_id}` (RFC 5280 §5, RFC 8615,
`Content-Type: application/pkix-crl`) with 24-hour validity.
An embedded OCSP responder at
`GET /.well-known/pki/ocsp/{issuer_id}/{serial}` (RFC 6960,
`Content-Type: application/ocsp-response`) returns signed OCSP
responses for issued certificates (good / revoked / unknown
status).
Both endpoints are reachable by relying parties with no certctl
API credentials, which is how standard TLS clients, browsers, and
hardware appliances consume these resources.
Certificates with profile TTL < 1 hour automatically skip
CRL/OCSP — expiry is treated as sufficient revocation for
short-lived credentials.
## Extended Key Usage support (M27)
The Local CA respects EKU constraints from certificate profiles
and adjusts key usage flags accordingly:
- **S/MIME** (`emailProtection` EKU) →
`DigitalSignature | ContentCommitment`.
- **TLS** (`serverAuth` / `clientAuth` EKU) →
`DigitalSignature | KeyEncipherment`.
This enables a single CA to issue TLS, S/MIME, code signing, and
timestamping certificates from one issuer row.
## MaxTTL enforcement (M11c)
When a certificate profile defines a maximum TTL, the Local CA
caps the `NotAfter` field to `min(validity_days, maxTTL)`. This
ensures certificates never exceed the profile's configured
lifetime regardless of the issuer's `validity_days` setting.
## L-014 file-on-disk threat-model carve-out
In file-driver mode (the default), the CA private key sits on the
certctl-server filesystem as a PEM at `CERTCTL_CA_KEY_PATH`. This
is a standard internal-PKI posture but means filesystem
compromise of the certctl host equals signing-key compromise.
Mitigations:
- **Filesystem permissions.** Mode 0600, owned by the certctl
service user. The connector preflight refuses to load a key
whose mode is wider than 0600.
- **Sub-CA rotation.** Rotate the certctl sub-CA cert+key
periodically (yearly is a sensible default) so a captured key
has a bounded blast-radius window.
- **Filesystem audit.** Add an `auditctl` watch on the key path;
any read/write attempt outside certctl-server's process is
logged.
- **Move to alternate signer drivers when they ship.** The
`internal/crypto/signer/` interface is the integration seam;
HSM (PKCS#11), cloud KMS, and SSH-CA drivers will close the
filesystem-residency leg without changing the rest of the
signing path.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [ADCS integration](adcs.md) — sub-CA mode rooted at ADCS
- [Intermediate CA hierarchy](../intermediate-ca-hierarchy.md) — tree mode operator runbook
- [CRL and OCSP](../protocols/crl-ocsp.md) — RFC 5280 / RFC 6960 endpoint reference
+157
View File
@@ -0,0 +1,157 @@
# 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 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").
## 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
+98
View File
@@ -0,0 +1,98 @@
# Sectigo SCM Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Sectigo Certificate Manager
> (SCM) issuer connector. For the connector-development context
> (interface contract, registry, ports/adapters), see the
> [connector index](index.md).
## Overview
The Sectigo connector integrates with Sectigo Certificate Manager's
REST API for ordering and managing DV, OV, and EV certificates.
Like DigiCert, it uses an async order model: submit an enrollment,
receive an `sslId`, then poll for completion.
Implementation lives at `internal/connector/issuer/sectigo/`.
## When to use this connector
Use the Sectigo SCM connector when:
- You're already a Sectigo Certificate Manager customer (formerly
Comodo CA / SecureTrust SCM).
- You need OV / EV certificates that Sectigo validates before
issuance.
- You want certctl to drive renewal lifecycle on top of Sectigo's
commercial issuance.
Look elsewhere when:
- You're using Sectigo through their ACME endpoint — the
[ACME connector](acme.md) is a simpler path.
- You only need DV certificates and want a free public-trust CA —
Let's Encrypt or ZeroSSL via the ACME connector.
## Configuration
| Variable | Default | Description |
|---|---|---|
| `CERTCTL_SECTIGO_CUSTOMER_URI` | — | Sectigo customer URI (organization identifier) |
| `CERTCTL_SECTIGO_LOGIN` | — | API account login |
| `CERTCTL_SECTIGO_PASSWORD` | — | API account password |
| `CERTCTL_SECTIGO_ORG_ID` | — | Organization ID (integer) |
| `CERTCTL_SECTIGO_CERT_TYPE` | — | Certificate type ID (integer, from `/ssl/v1/types`) |
| `CERTCTL_SECTIGO_TERM` | `365` | Certificate validity in days |
| `CERTCTL_SECTIGO_BASE_URL` | `https://cert-manager.com/api` | Sectigo API base URL |
| `CERTCTL_SECTIGO_POLL_MAX_WAIT_SECONDS` | `600` | Bounded-polling deadline for `GetOrderStatus` |
## Authentication
Three custom headers on every request: `customerUri`, `login`,
and `password`. No mTLS or OAuth2.
## Issuance model
`POST /ssl/v1/enroll` returns an `sslId`. DV certificates may
issue immediately; OV/EV certificates require Sectigo-side
validation and poll-based completion.
`GetOrderStatus` runs bounded internal polling
(5s/15s/45s/2m/5m capped, ±20% jitter, default 10-minute
deadline). The `collectNotReady` sentinel (cert approved but not
yet retrievable) rides the same backoff schedule. Bump
`CERTCTL_SECTIGO_POLL_MAX_WAIT_SECONDS` for OV/EV workflows where
human approval extends past 10 minutes — see
[async-ca-polling.md](../protocols/async-ca-polling.md) for the
schedule shape and tuning guidance.
## Revocation
CRL and OCSP are managed by Sectigo. certctl records revocations
locally and notifies Sectigo via `/ssl/v1/revoke/{sslId}`. Unlike
DigiCert (no auto-notify), Sectigo's revocation is part of the
connector's revoke path.
## Operator playbook
### Credential rotation
Rotate the API password in Sectigo's admin portal, then either
restart certctl-server with the new value in
`CERTCTL_SECTIGO_PASSWORD` or hot-swap via `PUT /api/v1/issuers/{id}`.
The registry's Rebuild path replaces the connector with the new
credentials. No certificate state is invalidated.
### Diagnosing slow OV/EV issuance
Sectigo's OV/EV vetting is human-driven and can take hours to
days. The same operational pattern as DigiCert applies: issue OV/EV
certs well ahead of expiry so the bounded poll deadline is short.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [Async CA polling](../protocols/async-ca-polling.md) — the bounded-polling primitive
- [DigiCert connector](digicert.md) — comparable commercial CA alternative
- [ACME connector](acme.md) — simpler path when Sectigo is reachable via ACME
+99
View File
@@ -0,0 +1,99 @@
# step-ca (Smallstep) Issuer Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the step-ca issuer connector.
> For the connector-development context (interface contract,
> registry, ports/adapters), see the [connector index](index.md).
## Overview
The step-ca connector integrates with Smallstep's step-ca private
CA using its native `/sign` API with JWK provisioner
authentication. Issuance is synchronous — submit a CSR plus a
provisioner-signed token, get back a signed certificate in the
same response.
This is simpler than ACME for internal PKI: no challenge solving,
no domain validation, just CSR + auth token → signed certificate.
For ACME-based step-ca usage, point the ACME connector at
step-ca's ACME directory URL instead.
Implementation lives at `internal/connector/issuer/stepca/`.
## When to use this connector
Use the step-ca connector when:
- You already run step-ca as your internal CA and want certctl to
drive lifecycle automation on top.
- You want synchronous issuance against an internal CA without
ACME's challenge dance.
- You want certctl to enforce profile / MaxTTL policy on step-ca-
issued certs.
Look elsewhere when:
- You want to use step-ca's ACME directory — that path goes
through the [ACME connector](acme.md) instead, which gives you
ACME features (ARI, EAB, profile selection) on top.
- You don't already run step-ca and want a simpler internal CA —
the [Local CA](local-ca.md) issuer is a one-process alternative.
## Configuration
```json
{
"ca_url": "https://ca.internal:9000",
"provisioner_name": "certctl",
"provisioner_key_path": "/etc/certctl/stepca/provisioner.json",
"provisioner_password": "...",
"root_cert_path": "/etc/certctl/stepca/root_ca.crt",
"validity_days": 90
}
```
Environment variables:
- `CERTCTL_STEPCA_URL` — step-ca server URL
- `CERTCTL_STEPCA_PROVISIONER` — JWK provisioner name
- `CERTCTL_STEPCA_KEY_PATH` — Path to provisioner private key
(JWK JSON)
- `CERTCTL_STEPCA_PASSWORD` — Provisioner key password
## Authentication: JWK provisioner
A JWK provisioner is created in step-ca with a passphrase-encrypted
private key (JSON Web Key format). certctl signs short-lived
proof-of-authorization tokens with the provisioner key for each
issuance request. The provisioner password is needed to decrypt the
JWK on disk; it is held in memory by certctl-server.
Rotation: rotate the JWK provisioner in step-ca, distribute the new
JWK + password to certctl, then either restart certctl-server or
hot-swap via `PUT /api/v1/issuers/{id}` so the registry's Rebuild
path replaces the connector with the new provisioner config.
## MaxTTL enforcement (M11c)
When a certificate profile defines a maximum TTL, the step-ca
connector caps the `NotAfter` field to ensure the issued
certificate does not exceed the profile limit, regardless of the
step-ca provisioner's own maximum.
## Revocation and CRL/OCSP
step-ca-issued certificates rely on step-ca's own CRL/OCSP
infrastructure. certctl's local CRL/OCSP endpoints
(`GET /.well-known/pki/crl/{issuer_id}` and
`GET /.well-known/pki/ocsp/{issuer_id}/{serial}`, served
unauthenticated per RFC 5280 §5 / RFC 6960 / RFC 8615) are
populated from step-ca's revocation data if available, but clients
should validate against step-ca's endpoints for the authoritative
status.
## Related docs
- [Connector index](index.md) — interface contract, registry, port/adapter wiring
- [ACME connector](acme.md) — alternative path to step-ca via its ACME directory URL
- [Local CA issuer](local-ca.md) — simpler internal-CA alternative when step-ca isn't already deployed