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).
4.6 KiB
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.
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
- 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). - Build a CSR with
Basic Constraints: CA=true, the operator's chosen path-length constraint, and key usages includingkeyCertSignandcRLSign. - Submit the CSR to ADCS using the Subordinate Certification Authority template (or a custom template that grants those key usages).
- Place the signed certctl-cert and the matching key at
CERTCTL_CA_CERT_PATH/CERTCTL_CA_KEY_PATH. - 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:
- Generate a new keypair (re-keying is recommended at sub-CA rotation time).
- CSR + ADCS signing cycle as above.
- Stage the new cert and key at fresh on-disk paths and follow the
intermediate-CA hierarchy
runbook for the cutover (rotate
CERTCTL_CA_CERT_PATH/CERTCTL_CA_KEY_PATHto the new files when ready). 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). 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 — the connector this integration uses
- Intermediate CA hierarchy — how certctl manages multi-level CA trees, including ADCS-rooted setups
- CRL and OCSP — how relying parties validate ADCS-rooted leaves
- Architecture —
internal/crypto/signer/driver interface for HSM / KMS / cloud-KMS alternatives to file-on-disk for the certctl sub-CA private key