Files
shankar0123 3275f9f1e0 ci: post-Phase-2-docs-overhaul cleanup of stale guards + missing config doc
CI run on the ecb8896 push surfaced two real failures rooted in the
2026-05-04 docs overhaul:

  1. G-3 env-docs-drift caught two phantom CERTCTL_* env vars I'd
     introduced in the Phase 4 follow-on connector pages
     (CERTCTL_CA_CERT_PATH_NEW in adcs.md was a placeholder I made
     up; CERTCTL_EJBCA_POLL_MAX_WAIT_SECONDS in ejbca.md does not
     exist in source). Both removed.

  2. QA-doc Part-count drift guard tried to grep
     docs/qa-test-guide.md and docs/testing-guide.md, both of which
     were renamed/deleted in Phase 2/Phase 5. The Part-count drift
     class died with testing-guide.md (Phase 5 prune dispersed its
     content); the seed-count drift class is still live but pointed
     at the wrong path.

Fixes:

- Removed the QA-doc Part-count drift guard from ci.yml (premise
  dead) plus its standalone scripts/qa-doc-part-count.sh peer.
- Retargeted the QA-doc seed-count drift guard from
  docs/qa-test-guide.md → docs/contributor/qa-test-suite.md (the
  Phase 2 target). Updated both ci.yml inline copy and
  scripts/qa-doc-seed-count.sh.
- Updated Makefile qa-stats: target to drop the testing-guide.md
  Parts metric (file is gone).
- Updated Makefile verify-docs: target to drop the part-count step.

G-3 was also failing in the second direction (env vars defined in
config.go but never documented anywhere). 16 vars surfaced —
features.md (deleted Phase 6) and testing-guide.md (deleted Phase 5)
had been their canonical home. Created
docs/reference/configuration.md as the new home: a compact
operator-facing env-var reference covering scheduler intervals, job
lifecycle, rate limiting, audit, deploy verify, database,
agent-side, and SCEP profile binding. Added to docs/README.md
Reference table.

Doc-side updates to qa-test-suite.md to reframe its references to
the deleted testing-guide.md (it's now self-contained: the
Part-by-Part Coverage Map IS the canonical Part inventory).

Cosmetic comment-only updates in ci.yml + scripts/ci-guards/*.sh +
scripts/dev-setup.sh to point at the new audience-organized doc
paths (docs/operator/security.md, docs/operator/tls.md,
docs/reference/architecture.md, etc.) instead of the pre-Phase-2
flat layout.

Verified: all 24 ci-guards/*.sh pass locally; qa-doc-seed-count.sh
clean. Net diff: 178 additions / 112 deletions across 13 files.
One file deleted (qa-doc-part-count.sh) and one file added
(docs/reference/configuration.md).
2026-05-05 04:56:26 +00:00

4.5 KiB

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.

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).

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. The connector consumes the shared bounded-polling primitive — see async-ca-polling.md for the schedule shape and tuning approach.

  • Connector index — interface contract, registry, port/adapter wiring
  • Async CA polling — bounded-polling primitive
  • Approval workflow — certctl-side two-person integrity (separate from EJBCA's approval queue, but addresses the same shape of risk on the certctl side)