From 430180360d08e1f8bd72191c5d07a43e355fda64 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Tue, 5 May 2026 03:53:52 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=204=20follow-on=20batch=201=20?= =?UTF-8?q?=E2=80=94=205=20issuer=20per-pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the first 5 issuer per-connector deep-dive pages: - vault.md (128 lines) — Vault PKI synchronous issuance, token TTL + auto-renewal loop, MaxTTL enforcement, rotation playbook - digicert.md (106 lines) — CertCentral DV/OV/EV with bounded async polling for vetting workflows - aws-acm-pca.md (165 lines) — managed private CA on AWS with full IAM policy, IRSA wiring, troubleshooting matrix - ejbca.md (116 lines) — open-source / Keyfactor EJBCA with mTLS or OAuth2 auth, mTLS keypair caching, approval-pending guidance - adcs.md (111 lines) — Active Directory Certificate Services as enterprise root via Local CA sub-CA mode, sub-CA rotation playbook Index updated with forward-list entries and the index-purpose blurb revised so the index now positions itself as 'navigate from here; deeper material lives in siblings' rather than 'docs to be extracted later'. Each per-page follows the WHAT/HOW/WHY pattern: what the connector is, how authentication and issuance work, and when to choose this vs an alternative. Cross-links to the connector index, async-ca-polling primitive, and adjacent operator runbooks. This is part 1 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: 5 files, 626 lines. No content removed from index.md (the index keeps its inline reference; per-pages add operator depth on top, matching the pattern set by apache/f5/iis/k8s/nginx in Phase 4 structural). --- docs/reference/connectors/adcs.md | 111 +++++++++++++++ docs/reference/connectors/aws-acm-pca.md | 165 +++++++++++++++++++++++ docs/reference/connectors/digicert.md | 106 +++++++++++++++ docs/reference/connectors/ejbca.md | 116 ++++++++++++++++ docs/reference/connectors/index.md | 23 +++- docs/reference/connectors/vault.md | 128 ++++++++++++++++++ 6 files changed, 643 insertions(+), 6 deletions(-) create mode 100644 docs/reference/connectors/adcs.md create mode 100644 docs/reference/connectors/aws-acm-pca.md create mode 100644 docs/reference/connectors/digicert.md create mode 100644 docs/reference/connectors/ejbca.md create mode 100644 docs/reference/connectors/vault.md diff --git a/docs/reference/connectors/adcs.md b/docs/reference/connectors/adcs.md new file mode 100644 index 0000000..303701a --- /dev/null +++ b/docs/reference/connectors/adcs.md @@ -0,0 +1,111 @@ +# Active Directory Certificate Services (ADCS) Integration — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for integrating certctl with Microsoft +> ADCS as the enterprise root. For the connector-development context +> (interface contract, registry, ports/adapters), see the +> [connector index](index.md). + +## Overview + +ADCS integration is **not** a separate connector. certctl integrates +with ADCS via the **sub-CA mode** of the Local CA issuer: certctl +operates as a subordinate CA whose signing certificate was issued by +ADCS, so all certctl-issued certificates chain back to the enterprise +ADCS root. + +This is the canonical pattern for Windows-shop deployments where +ADCS is already the root of trust and operators want certctl to +handle automation (lifecycle, renewal, deployment, alerts) without +ADCS having to support a non-Microsoft REST API surface. + +## When to use this integration + +Use ADCS sub-CA mode when: + +- ADCS is your enterprise root and you don't want to introduce a + parallel root of trust. +- You want all certctl-issued certificates to validate against the + ADCS chain that's already in your Windows trust stores, mobile + device profiles, and load-balancer configurations. +- You need certctl's automation surface (ACME, SCEP, EST, profile + policy, scheduler, deployment connectors) but want ADCS to remain + the signing authority for the root. + +Look elsewhere when: + +- You want certctl to issue from its own root of trust — use the + Local CA issuer in self-signed mode. +- ADCS is being decommissioned or replaced — the migration path + from ADCS to Vault PKI / step-ca / Local CA needs its own + rollout plan; that's not what this connector covers. + +## How sub-CA mode works + +The Local CA issuer loads a pre-signed CA certificate and key from +disk: + +- `CERTCTL_CA_CERT_PATH` — path to the certctl signing cert PEM + (the one ADCS issued). +- `CERTCTL_CA_KEY_PATH` — path to the matching private key PEM. + +Every leaf certctl issues is signed with this key, and the chain +returned to clients includes both the certctl signing cert and the +ADCS root (so verifying clients see a complete chain to the +enterprise root). + +The signing certificate certctl uses is just a normal CA cert with +`Basic Constraints: CA=true` and an appropriate path-length +constraint. ADCS issues this certificate using its standard +"Subordinate Certification Authority" template; the operator just +takes the resulting cert + key and points certctl at them. + +## Operator playbook + +### Provisioning the certctl sub-CA + +1. Generate a new keypair for certctl on the host that will run it + (or in the HSM / KMS the operator wants to delegate signing to, + via the `internal/crypto/signer/` driver interface when alternate + drivers are configured). +2. Build a CSR with `Basic Constraints: CA=true`, the operator's + chosen path-length constraint, and key usages including + `keyCertSign` and `cRLSign`. +3. Submit the CSR to ADCS using the Subordinate Certification + Authority template (or a custom template that grants those key + usages). +4. Place the signed certctl-cert and the matching key at + `CERTCTL_CA_CERT_PATH` / `CERTCTL_CA_KEY_PATH`. +5. Restart certctl-server (or Rebuild the issuer via the API). + Subsequent issuance chains to the ADCS root. + +### Rotating the sub-CA cert + +When the certctl sub-CA cert is approaching expiry: + +1. Generate a new keypair (re-keying is recommended at sub-CA + rotation time). +2. CSR + ADCS signing cycle as above. +3. Stage the new cert and key at fresh paths + (`CERTCTL_CA_CERT_PATH_NEW` etc.) and follow the + [intermediate-CA hierarchy + runbook](../intermediate-ca-hierarchy.md) for the cutover. The + key concern is overlap: both the old and new sub-CA certs must + chain to the ADCS root during the rollover so existing leaves + keep validating. + +### Revocation chain + +CRL and OCSP for ADCS-rooted leaves are handled by certctl's CRL +distribution point and OCSP responder +([crl-ocsp.md](../protocols/crl-ocsp.md)). The ADCS root publishes +its own CRL covering the certctl sub-CA cert; relying parties walk +both CDP entries to determine the full revocation status. + +## Related docs + +- [Local CA issuer](index.md#built-in-local-ca) — the connector this integration uses +- [Intermediate CA hierarchy](../intermediate-ca-hierarchy.md) — how certctl manages multi-level CA trees, including ADCS-rooted setups +- [CRL and OCSP](../protocols/crl-ocsp.md) — how relying parties validate ADCS-rooted leaves +- [Architecture](../architecture.md) — `internal/crypto/signer/` driver interface for HSM / KMS / cloud-KMS alternatives to file-on-disk for the certctl sub-CA private key diff --git a/docs/reference/connectors/aws-acm-pca.md b/docs/reference/connectors/aws-acm-pca.md new file mode 100644 index 0000000..1c8475a --- /dev/null +++ b/docs/reference/connectors/aws-acm-pca.md @@ -0,0 +1,165 @@ +# AWS ACM Private CA Issuer Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the AWS Certificate Manager +> Private Certificate Authority (ACM PCA) issuer connector. For the +> connector-development context (interface contract, registry, +> ports/adapters), see the [connector index](index.md). + +## Overview + +AWS ACM Private CA is a managed private CA on AWS. The connector +calls `IssueCertificate` (which is asynchronous at the ACM PCA API +level), then runs the SDK's `NewCertificateIssuedWaiter` until the +cert reaches `CERTIFICATE_ISSUED` state, then `GetCertificate` to +retrieve the PEM. Default waiter timeout is 5 minutes; tune by +editing `defaultWaiterTimeout` in +`internal/connector/issuer/awsacmpca/awsacmpca.go`. + +Implementation lives at `internal/connector/issuer/awsacmpca/`. + +## When to use this connector + +Use the AWS ACM PCA connector when: + +- Your workloads are AWS-native and you want the CA to live inside + your AWS account (for blast-radius, IAM, and audit reasons). +- You need ACM PCA's CRL distribution and OCSP responder to serve + status to relying parties without certctl being in the OCSP path. +- You want IAM-based access control (no API keys to rotate) for + certctl's signing path. + +Look elsewhere when: + +- You're not on AWS — Google CAS or Azure Key Vault are the cloud- + native equivalents on those platforms. +- You need public-trust certificates — ACM PCA is private only. +- You don't already pay for ACM PCA (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_AWS_PCA_REGION` | Yes | — | AWS region (e.g. `us-east-1`) | +| `CERTCTL_AWS_PCA_CA_ARN` | Yes | — | ARN of the ACM Private CA | +| `CERTCTL_AWS_PCA_SIGNING_ALGORITHM` | No | `SHA256WITHRSA` | Signing algorithm | +| `CERTCTL_AWS_PCA_VALIDITY_DAYS` | No | `365` | Certificate validity in days | +| `CERTCTL_AWS_PCA_TEMPLATE_ARN` | No | — | Optional certificate template ARN | + +Supported signing algorithms: `SHA256WITHRSA`, `SHA384WITHRSA`, +`SHA512WITHRSA`, `SHA256WITHECDSA`, `SHA384WITHECDSA`, +`SHA512WITHECDSA`. + +## Authentication + +Standard AWS credential chain via +`aws-sdk-go-v2/config.LoadDefaultConfig()`. Resolves credentials in +this order: + +1. Environment variables (`AWS_ACCESS_KEY_ID`, + `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`). +2. Shared config files (`~/.aws/config`, `~/.aws/credentials`, + profile via `AWS_PROFILE`). +3. IAM Roles for Service Accounts (IRSA) on EKS. +4. EC2 instance profiles. +5. ECS task roles. +6. SSO. + +certctl never stores AWS credentials directly — set them in the +certctl process's environment or via the IAM role attached to the +host. + +## Minimal IAM policy + +The IAM principal that certctl authenticates as needs the following +actions against the CA's ARN: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "acm-pca:IssueCertificate", + "acm-pca:GetCertificate", + "acm-pca:RevokeCertificate", + "acm-pca:GetCertificateAuthorityCertificate" + ], + "Resource": "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012" + } + ] +} +``` + +Replace the `Resource` ARN with your own CA ARN. If you use a +`TemplateArn` (subordinate-CA template), the policy needs no +additional permissions — `IssueCertificate` covers it. + +## Worked example: add the issuer via API + +```bash +curl -k -X POST https://localhost:8443/api/v1/issuers \ + -H 'Content-Type: application/json' \ + -d '{ + "id": "iss-aws-prod", + "name": "AWS ACM PCA (prod)", + "type": "AWSACMPCA", + "config": { + "region": "us-east-1", + "ca_arn": "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012", + "signing_algorithm": "SHA256WITHRSA", + "validity_days": 90 + } + }' +``` + +The certctl server process must have AWS credentials available +before the issuer is created (or before any subsequent issuance +call). For a local dev run with shared-config creds: +`export AWS_PROFILE=my-profile` before `docker compose up`. For an +EKS deployment: attach an IRSA-bound IAM role to the certctl pod's +service account. + +## Troubleshooting + +### `AccessDeniedException: User ... is not authorized to perform: acm-pca:IssueCertificate` + +The IAM principal certctl is using lacks the required actions. +Apply the IAM policy above (scoped to your CA ARN) to the +role/user. The principal can be inspected with +`aws sts get-caller-identity` from the certctl host. + +### `ResourceNotFoundException: Could not find Certificate Authority` + +The `CAArn` doesn't match any CA in the configured region. Common +causes: region mismatch (CA is in `us-west-2`, certctl region is +set to `us-east-1`), CA was deleted, ARN typo. Verify with +`aws acm-pca describe-certificate-authority --certificate-authority-arn --region `. + +### `acmpca waiter (waiting for issuance): exceeded max wait time` + +The cert was submitted but didn't reach `CERTIFICATE_ISSUED` state +within 5 minutes. Check the CA's CloudWatch metrics for backlog; +check the CA's audit reports for any policy violations on the +request. If the wait is consistently slow, edit +`defaultWaiterTimeout` in +`internal/connector/issuer/awsacmpca/awsacmpca.go` and rebuild. + +## Revocation + +CRL and OCSP are managed by AWS ACM PCA directly. certctl records +revocations locally and notifies AWS via the `RevokeCertificate` +API with RFC 5280 reason mapping (e.g. `keyCompromise` → +`KEY_COMPROMISE`). AWS ACM PCA's CRL distribution point and OCSP +responder serve the resulting status to verifying clients — +certctl is **not** in the OCSP path for this connector. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, port/adapter wiring +- [Async CA polling](../protocols/async-ca-polling.md) — bounded-polling primitive (ACM PCA uses the SDK waiter, not certctl's polling, but the same operator concerns apply) +- [Disaster recovery runbook](../../operator/runbooks/disaster-recovery.md) — what happens to ACM PCA-issued certs if the CA is deleted diff --git a/docs/reference/connectors/digicert.md b/docs/reference/connectors/digicert.md new file mode 100644 index 0000000..2b6bf34 --- /dev/null +++ b/docs/reference/connectors/digicert.md @@ -0,0 +1,106 @@ +# DigiCert CertCentral Issuer Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the DigiCert CertCentral issuer +> connector. For the connector-development context (interface +> contract, registry, ports/adapters), see the +> [connector index](index.md). + +## Overview + +The DigiCert connector integrates with DigiCert's CertCentral REST +API for ordering and managing certificates from DigiCert's commercial +public CA. It supports Domain Validated (DV), Organization Validated +(OV), and Extended Validated (EV) certificates, with async order +processing for OV/EV. + +Implementation lives at `internal/connector/issuer/digicert/`. + +## When to use this connector + +Use the DigiCert connector when: + +- You're already a DigiCert CertCentral customer and want certctl to + drive issuance, renewal, and deployment from the same platform that + manages your internal PKI. +- You need OV or EV certificates that require DigiCert to validate + organization details before issuance. +- You want one tool that covers both internal CAs (Vault, Local, + step-ca) and a public-trust commercial CA. + +Look elsewhere when: + +- You only need DV certificates and Let's Encrypt / ZeroSSL is an + acceptable issuer — use the ACME connector instead. +- You need self-hosted PKI with no commercial CA dependency — use + Vault PKI, step-ca, or the Local CA issuer. + +## Configuration + +| Variable | Default | Description | +|---|---|---| +| `CERTCTL_DIGICERT_API_KEY` | — | DigiCert API key (sent in `X-DC-DEVKEY` header) | +| `CERTCTL_DIGICERT_ORG_ID` | — | DigiCert organization ID | +| `CERTCTL_DIGICERT_PRODUCT_TYPE` | `ssl_basic` | Certificate product (e.g. `ssl_basic`, `ssl_plus`, `ssl_ev`) | +| `CERTCTL_DIGICERT_BASE_URL` | `https://www.digicert.com/services/v2` | DigiCert API base URL | +| `CERTCTL_DIGICERT_POLL_MAX_WAIT_SECONDS` | `600` | Bounded-polling deadline for `GetOrderStatus` | + +## Authentication + +API key passed via `X-DC-DEVKEY` header. The organization ID is sent +in the request body (not the header). No mTLS or OAuth2 required. + +## Issuance model + +- **DV certificates** — typically issue immediately; the + `/order/certificate/create` API may return the PEM in the same + response. +- **OV / EV certificates** — require DigiCert-side validation + (vetting org documents, checking domain ownership). The API + returns 201 with an order ID; certctl's `GetOrderStatus` polls + until the certificate is retrievable. + +`GetOrderStatus` runs bounded internal polling (5s/15s/45s/2m/5m +capped, ±20% jitter, default 10-minute deadline). For OV/EV orders +where humans approve enrollments, bump +`CERTCTL_DIGICERT_POLL_MAX_WAIT_SECONDS` to a value that comfortably +covers the approval window — see +[async-ca-polling.md](../protocols/async-ca-polling.md) for the +schedule shape and tuning guidance. + +## Revocation + +CRL and OCSP are managed by DigiCert. Clients should validate +certificate status against DigiCert's infrastructure. certctl +records the revocation locally (audit row + cert state) but does +**not** call DigiCert's revoke endpoint — operators revoke through +DigiCert's dashboard or the CertCentral REST API directly. This +keeps the certctl revocation flow simple at the cost of one extra +manual step on revocation. + +## Operator playbook + +### API key rotation + +Rotate the API key in DigiCert's dashboard, then either restart +certctl-server with the new value in `CERTCTL_DIGICERT_API_KEY` or +hot-swap via `PUT /api/v1/issuers/{id}` so the registry's Rebuild +path replaces the connector with the new key. No certificate +state is invalidated by the rotation — the new key just signs +future API calls. + +### Diagnosing slow OV/EV issuance + +DigiCert's OV/EV vetting is a human process and can take hours to +days. Bumping `CERTCTL_DIGICERT_POLL_MAX_WAIT_SECONDS` lets a +single tick wait through the full approval window, but the better +operational pattern is to issue OV/EV certs well ahead of expiry +so the bounded poll deadline is short. The renewal scheduler's +"alert at T-30 days" default exists for exactly this reason. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, port/adapter wiring +- [Async CA polling](../protocols/async-ca-polling.md) — the bounded-polling primitive +- [ACME server](../protocols/acme-server.md) — alternative issuer for DV-only workflows diff --git a/docs/reference/connectors/ejbca.md b/docs/reference/connectors/ejbca.md new file mode 100644 index 0000000..f506336 --- /dev/null +++ b/docs/reference/connectors/ejbca.md @@ -0,0 +1,116 @@ +# EJBCA (Keyfactor) Issuer Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the EJBCA issuer connector. For +> the connector-development context (interface contract, registry, +> ports/adapters), see the [connector index](index.md). + +## Overview + +The EJBCA connector calls the EJBCA REST API for self-hosted +open-source and Keyfactor enterprise CAs. It supports dual +authentication: mTLS (default) or OAuth2 Bearer token, selectable +via configuration. + +Implementation lives at `internal/connector/issuer/ejbca/`. + +## When to use this connector + +Use the EJBCA connector when: + +- You already run EJBCA Community Edition or Keyfactor EJBCA + Enterprise as your internal CA and want certctl to drive the + lifecycle automation (renewal, deployment, alerts) on top. +- You need EJBCA's certificate-profile and end-entity-profile + policy enforcement — those policies stay in EJBCA and certctl + passes the profile names through. +- You need approval-pending workflows (humans approve enrollments) + — EJBCA supports the 201-Accepted async path. + +Look elsewhere when: + +- You want a simpler internal CA without EJBCA's operational weight + — Vault PKI, step-ca, or the Local CA issuer are lighter. +- You need a managed CA (no servers to run) — Google CAS or AWS + ACM PCA on cloud, or DigiCert / Sectigo for commercial PKI. + +## Configuration + +| Setting | Required | Default | Description | +|---|---|---|---| +| `CERTCTL_EJBCA_API_URL` | Yes | — | EJBCA REST API base URL | +| `CERTCTL_EJBCA_AUTH_MODE` | No | `mtls` | Auth mode: `mtls` or `oauth2` | +| `CERTCTL_EJBCA_CLIENT_CERT_PATH` | mTLS | — | Path to client certificate PEM (mTLS mode) | +| `CERTCTL_EJBCA_CLIENT_KEY_PATH` | mTLS | — | Path to client key PEM (mTLS mode) | +| `CERTCTL_EJBCA_TOKEN` | OAuth2 | — | Bearer token (oauth2 mode) | +| `CERTCTL_EJBCA_CA_NAME` | Yes | — | EJBCA CA name | +| `CERTCTL_EJBCA_CERT_PROFILE` | No | — | EJBCA certificate profile | +| `CERTCTL_EJBCA_EE_PROFILE` | No | — | EJBCA end-entity profile | + +## Authentication + +Configurable via `auth_mode`: + +- **`mtls`** — client certificate and key are loaded for the TLS + handshake. This is the default and the more common deployment + mode for EJBCA. +- **`oauth2`** — the token is sent as `Authorization: Bearer + {token}`. Use when EJBCA is fronted by an OAuth2-aware reverse + proxy or when integrating with Keyfactor's identity provider. + +The mTLS keypair is cached on the connector after the first API +call and reused for the lifetime of the process; rotation is +picked up automatically via mtime polling on the cert file (see +the mtls keypair caching note in the [connector +index](index.md#built-in-ejbca-keyfactor)). + +## Issuance model + +`POST /v1/certificate/pkcs10enroll` with base64-encoded CSR. +Returns base64-encoded certificate PEM. EJBCA 9.3+ creates +end-entity and issues cert in a single call. Approval-pending +enrollments return 201 with a tracking ID; certctl's +`GetOrderStatus` polls until the certificate is available. + +## Revocation + +EJBCA requires both issuer DN and serial number for revocation. +The connector stores these as a composite `OrderID` in +`issuer_dn::serial` format. + +CRL and OCSP are managed by the EJBCA instance. certctl records +revocations locally and notifies EJBCA via +`PUT /v1/certificate/{issuer_dn}/{serial}/revoke`. + +## Operator playbook + +### mTLS rotation without downtime + +`mv -f new.crt /etc/certctl/ejbca/client.crt` (mtime changes), no +process restart required. The next API call re-parses the file +and rebuilds the `*http.Transport`. `os.Stat` errors during +rotation surface as connector errors rather than silently serving +stale credentials. + +### Switching from mTLS to OAuth2 + +Update the issuer config via `PUT /api/v1/issuers/{id}` with the +new `auth_mode: oauth2` and `token`. The registry's Rebuild path +replaces the connector without restart. Prior issuance state +(serial numbers, cert state) is unaffected. + +### Diagnosing approval-pending hangs + +If `GetOrderStatus` consistently times out, the operator approval +queue in EJBCA is the most common cause. Bump +`CERTCTL_EJBCA_POLL_MAX_WAIT_SECONDS` so a single tick can wait +through the full approval window — see +[async-ca-polling.md](../protocols/async-ca-polling.md) for the +schedule shape. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, port/adapter wiring +- [Async CA polling](../protocols/async-ca-polling.md) — bounded-polling primitive +- [Approval workflow](../../operator/approval-workflow.md) — certctl-side two-person integrity (separate from EJBCA's approval queue, but addresses the same shape of risk on the certctl side) diff --git a/docs/reference/connectors/index.md b/docs/reference/connectors/index.md index ceba2d1..ea76375 100644 --- a/docs/reference/connectors/index.md +++ b/docs/reference/connectors/index.md @@ -2,17 +2,28 @@ > Last reviewed: 2026-05-05 > -> This is the canonical connector reference. Per-connector deep-dive -> pages exist for the targets that have unique operational quirks -> (apache, f5, iis, k8s, nginx — see siblings in this directory). Other -> connectors are documented inline in this file and will be extracted -> to their own pages as operational depth grows. Cross-link from this -> index to per-connector pages where they exist. +> This is the canonical connector reference: interface contracts, +> registry, deployment primitive, network scanner, cloud discovery. +> Each built-in connector below has a sibling per-page that goes +> deeper on operator-grade material (vendor edges, troubleshooting, +> rotation playbooks, when-to-use vs alternatives). Use this index +> to navigate; jump to the sibling pages for hands-on operator +> material. Connectors extend certctl to integrate with external systems for certificate issuance, deployment, and notifications. This guide covers the connector interfaces, built-in implementations, and how to build your own. **Per-connector deep-dive pages** (siblings in this directory): +Issuer connectors: + +- [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 +- [Vault PKI](vault.md) — HashiCorp Vault PKI engine, synchronous issuance + +Target connectors: + - [Apache](apache.md) — Apache httpd connector deep dive - [F5 BIG-IP](f5.md) — F5 connector deep dive (proxy agent + iControl REST) - [IIS](iis.md) — Microsoft IIS connector deep dive (local PowerShell + WinRM modes) diff --git a/docs/reference/connectors/vault.md b/docs/reference/connectors/vault.md new file mode 100644 index 0000000..3ff2543 --- /dev/null +++ b/docs/reference/connectors/vault.md @@ -0,0 +1,128 @@ +# Vault PKI Issuer Connector — Operator Deep-Dive + +> Last reviewed: 2026-05-05 +> +> Operator-grade documentation for the HashiCorp Vault PKI issuer +> connector. For the connector-development context (interface contract, +> registry, ports/adapters), see the +> [connector index](index.md). + +## Overview + +The Vault PKI connector integrates with HashiCorp Vault's PKI secrets +engine using its native `/sign` API with token-based authentication. +The flow is purely synchronous — Vault returns the signed certificate +in the same HTTP response that submits the CSR — so there is no +challenge-solving or async polling on the certctl side. + +Implementation lives at `internal/connector/issuer/vault/`. The +factory key is `Vault`; the registry binds it under whatever issuer +ID the operator picks (e.g. `iss-vault`). + +## When to use this connector + +Use the Vault PKI connector when: + +- Your organization already runs Vault as the system of record for + internal certificates. +- You want a synchronous, low-latency issuance path with no challenge + flow (no DNS records, no HTTP-01). +- You want certctl to manage the lifecycle (renewal scheduling, + deployment, alerts) while Vault keeps the signing material. + +Look elsewhere when: + +- Public-trust certificates are required — Vault PKI is internal-only. + Use ACME (Let's Encrypt, ZeroSSL, Sectigo) or DigiCert / Sectigo SCM + for public-trust workloads. +- The Vault PKI engine is not already deployed and you don't want to + run Vault. The Local CA issuer is a simpler self-contained path for + small internal CAs. + +## Configuration + +| Variable | Default | Description | +|---|---|---| +| `CERTCTL_VAULT_ADDR` | — | Vault server address (e.g. `https://vault.internal:8200`) | +| `CERTCTL_VAULT_TOKEN` | — | Vault auth token with permissions on the PKI mount | +| `CERTCTL_VAULT_MOUNT` | `pki` | PKI secrets engine mount path | +| `CERTCTL_VAULT_ROLE` | — | PKI role name for certificate signing | +| `CERTCTL_VAULT_TTL` | `8760h` | Certificate validity period (TTL) | + +Vault issues certificates synchronously via the +`/v1/{mount}/sign/{role}` API with `X-Vault-Token` header +authentication. The issued certificate is parsed to extract serial +number, validity dates, and chain information. + +## Token TTL and automatic renewal + +This was Top-10 fix #5 from the 2026-05-03 issuer-coverage audit. + +certctl-server periodically calls `POST /v1/auth/token/renew-self` at +half the token's TTL to keep the integration alive without manual +rotation. The cadence is read from a one-shot `lookup-self` at +startup and re-derived on every successful renewal — so a short +bootstrap token that gets renewed up to a longer Max TTL shifts to +the longer cadence automatically. + +The renewal loop emits the +`certctl_vault_token_renewals_total{result="success"|"failure"|"not_renewable"}` +Prometheus counter so operators see expiry trouble in Grafana before +issuance breaks. + +When Vault returns `renewable: false` (configured Max TTL reached), +the loop logs a WARN, increments `{result="not_renewable"}`, and +exits. The operator must rotate the Vault token and either restart +certctl-server or use the GUI / MCP issuer-update path to swap the +token in place — the registry's Rebuild path re-Starts the lifecycle +on the new connector. + +Per-tick failures (e.g. transient 5xx, brief network blips) bump +`{result="failure"}` and the loop keeps ticking. Only the explicit +`renewable: false` case stops it. + +## MaxTTL enforcement (M11c) + +When a certificate profile defines a maximum TTL, the Vault connector +overrides the TTL string in the signing request to ensure the issued +certificate does not exceed the profile limit. This is applied +**before** Vault's own role-level max TTL — so the effective limit is +the minimum of (profile.MaxTTL, role.MaxLeaseTTL). + +## Revocation and CRL/OCSP + +CRL and OCSP are managed by Vault itself. Clients should validate +certificate status against Vault's own CRL/OCSP endpoints +(`GET /v1/{mount}/crl` and Vault's OCSP responder). certctl does not +generate local CRL/OCSP for Vault-issued certificates. Revocation is +recorded locally (audit row + cert state) but Vault is the +authoritative source for relying parties. + +## Operator playbook + +### Token rotation without downtime + +Two paths: + +1. **Restart-driven.** Update `CERTCTL_VAULT_TOKEN` env var on the + server, restart certctl-server. The renewal loop picks up the new + token's lookup-self response and resumes ticking. +2. **Hot-swap via API/GUI.** `PUT /api/v1/issuers/{id}` with the + updated config; the registry's Rebuild path replaces the connector + without restart. Use this when Vault's Max TTL has been reached + and the existing token can no longer be renewed. + +### Diagnosing renewal failures + +Watch +`certctl_vault_token_renewals_total{result="not_renewable"}` and +`{result="failure"}`. Sustained failures with no `not_renewable` +generally indicate Vault unreachability or token-policy drift; a +spike in `not_renewable` is the canonical signal that a Max TTL +boundary was hit and operator action is required. + +## Related docs + +- [Connector index](index.md) — interface contract, registry, port/adapter wiring +- [Issuer hierarchy primitive](../intermediate-ca-hierarchy.md) — how Vault sits as a sub-CA under another issuer +- [Async CA polling](../protocols/async-ca-polling.md) — the bounded-polling primitive used by other issuers; Vault is synchronous so does not consume it